rt2x00: Fix PCI interrupt processing race on SMP systems

When toggle_irq is called for PCI devices to disable device interrupts
it used tasklet_disable to wait for a possibly running tasklet to finish.
However, on SMP systems the tasklet might still be scheduled on another CPU.
Instead, use tasklet_kill to ensure that all scheduled tasklets are finished
before returning from toggle_irq.

Furthermore, it was possible that a tasklet reenabled its interrupt even
though interrupts have been disabled already. Fix this by checking the
DEVICE_STATE_ENABLED_RADIO flag before reenabling single interrupts
during tasklet processing.

While at it also enable/kill the TBTT and PRETBTT tasklets in the
toggle_irq callback and only use tasklet_kill in stop_queue to wait
for a currently scheduled beacon update before returning.

Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com>
Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Helmut Schaa 2011-08-06 13:13:48 +02:00 committed by John W. Linville
parent c3ccb3341e
commit abc1199411
5 changed files with 64 additions and 95 deletions

View File

@ -645,11 +645,6 @@ static void rt2400pci_start_queue(struct data_queue *queue)
rt2x00pci_register_write(rt2x00dev, RXCSR0, reg); rt2x00pci_register_write(rt2x00dev, RXCSR0, reg);
break; break;
case QID_BEACON: case QID_BEACON:
/*
* Allow the tbtt tasklet to be scheduled.
*/
tasklet_enable(&rt2x00dev->tbtt_tasklet);
rt2x00pci_register_read(rt2x00dev, CSR14, &reg); rt2x00pci_register_read(rt2x00dev, CSR14, &reg);
rt2x00_set_field32(&reg, CSR14_TSF_COUNT, 1); rt2x00_set_field32(&reg, CSR14_TSF_COUNT, 1);
rt2x00_set_field32(&reg, CSR14_TBCN, 1); rt2x00_set_field32(&reg, CSR14_TBCN, 1);
@ -715,7 +710,7 @@ static void rt2400pci_stop_queue(struct data_queue *queue)
/* /*
* Wait for possibly running tbtt tasklets. * Wait for possibly running tbtt tasklets.
*/ */
tasklet_disable(&rt2x00dev->tbtt_tasklet); tasklet_kill(&rt2x00dev->tbtt_tasklet);
break; break;
default: default:
break; break;
@ -982,12 +977,6 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
if (state == STATE_RADIO_IRQ_ON) { if (state == STATE_RADIO_IRQ_ON) {
rt2x00pci_register_read(rt2x00dev, CSR7, &reg); rt2x00pci_register_read(rt2x00dev, CSR7, &reg);
rt2x00pci_register_write(rt2x00dev, CSR7, reg); rt2x00pci_register_write(rt2x00dev, CSR7, reg);
/*
* Enable tasklets.
*/
tasklet_enable(&rt2x00dev->txstatus_tasklet);
tasklet_enable(&rt2x00dev->rxdone_tasklet);
} }
/* /*
@ -1011,8 +1000,9 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
* Ensure that all tasklets are finished before * Ensure that all tasklets are finished before
* disabling the interrupts. * disabling the interrupts.
*/ */
tasklet_disable(&rt2x00dev->txstatus_tasklet); tasklet_kill(&rt2x00dev->txstatus_tasklet);
tasklet_disable(&rt2x00dev->rxdone_tasklet); tasklet_kill(&rt2x00dev->rxdone_tasklet);
tasklet_kill(&rt2x00dev->tbtt_tasklet);
} }
} }
@ -1347,22 +1337,25 @@ static void rt2400pci_txstatus_tasklet(unsigned long data)
/* /*
* Enable all TXDONE interrupts again. * Enable all TXDONE interrupts again.
*/ */
spin_lock_irq(&rt2x00dev->irqmask_lock); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) {
spin_lock_irq(&rt2x00dev->irqmask_lock);
rt2x00pci_register_read(rt2x00dev, CSR8, &reg); rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, 0); rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, 0);
rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, 0); rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, 0);
rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, 0); rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, 0);
rt2x00pci_register_write(rt2x00dev, CSR8, reg); rt2x00pci_register_write(rt2x00dev, CSR8, reg);
spin_unlock_irq(&rt2x00dev->irqmask_lock); spin_unlock_irq(&rt2x00dev->irqmask_lock);
}
} }
static void rt2400pci_tbtt_tasklet(unsigned long data) static void rt2400pci_tbtt_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00lib_beacondone(rt2x00dev); rt2x00lib_beacondone(rt2x00dev);
rt2400pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2400pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE);
} }
static void rt2400pci_rxdone_tasklet(unsigned long data) static void rt2400pci_rxdone_tasklet(unsigned long data)
@ -1370,7 +1363,7 @@ static void rt2400pci_rxdone_tasklet(unsigned long data)
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
if (rt2x00pci_rxdone(rt2x00dev)) if (rt2x00pci_rxdone(rt2x00dev))
tasklet_schedule(&rt2x00dev->rxdone_tasklet); tasklet_schedule(&rt2x00dev->rxdone_tasklet);
else else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2400pci_enable_interrupt(rt2x00dev, CSR8_RXDONE); rt2400pci_enable_interrupt(rt2x00dev, CSR8_RXDONE);
} }

View File

@ -735,11 +735,6 @@ static void rt2500pci_start_queue(struct data_queue *queue)
rt2x00pci_register_write(rt2x00dev, RXCSR0, reg); rt2x00pci_register_write(rt2x00dev, RXCSR0, reg);
break; break;
case QID_BEACON: case QID_BEACON:
/*
* Allow the tbtt tasklet to be scheduled.
*/
tasklet_enable(&rt2x00dev->tbtt_tasklet);
rt2x00pci_register_read(rt2x00dev, CSR14, &reg); rt2x00pci_register_read(rt2x00dev, CSR14, &reg);
rt2x00_set_field32(&reg, CSR14_TSF_COUNT, 1); rt2x00_set_field32(&reg, CSR14_TSF_COUNT, 1);
rt2x00_set_field32(&reg, CSR14_TBCN, 1); rt2x00_set_field32(&reg, CSR14_TBCN, 1);
@ -805,7 +800,7 @@ static void rt2500pci_stop_queue(struct data_queue *queue)
/* /*
* Wait for possibly running tbtt tasklets. * Wait for possibly running tbtt tasklets.
*/ */
tasklet_disable(&rt2x00dev->tbtt_tasklet); tasklet_kill(&rt2x00dev->tbtt_tasklet);
break; break;
default: default:
break; break;
@ -1137,12 +1132,6 @@ static void rt2500pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
if (state == STATE_RADIO_IRQ_ON) { if (state == STATE_RADIO_IRQ_ON) {
rt2x00pci_register_read(rt2x00dev, CSR7, &reg); rt2x00pci_register_read(rt2x00dev, CSR7, &reg);
rt2x00pci_register_write(rt2x00dev, CSR7, reg); rt2x00pci_register_write(rt2x00dev, CSR7, reg);
/*
* Enable tasklets.
*/
tasklet_enable(&rt2x00dev->txstatus_tasklet);
tasklet_enable(&rt2x00dev->rxdone_tasklet);
} }
/* /*
@ -1165,8 +1154,9 @@ static void rt2500pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
/* /*
* Ensure that all tasklets are finished. * Ensure that all tasklets are finished.
*/ */
tasklet_disable(&rt2x00dev->txstatus_tasklet); tasklet_kill(&rt2x00dev->txstatus_tasklet);
tasklet_disable(&rt2x00dev->rxdone_tasklet); tasklet_kill(&rt2x00dev->rxdone_tasklet);
tasklet_kill(&rt2x00dev->tbtt_tasklet);
} }
} }
@ -1479,22 +1469,25 @@ static void rt2500pci_txstatus_tasklet(unsigned long data)
/* /*
* Enable all TXDONE interrupts again. * Enable all TXDONE interrupts again.
*/ */
spin_lock_irq(&rt2x00dev->irqmask_lock); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) {
spin_lock_irq(&rt2x00dev->irqmask_lock);
rt2x00pci_register_read(rt2x00dev, CSR8, &reg); rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, 0); rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, 0);
rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, 0); rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, 0);
rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, 0); rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, 0);
rt2x00pci_register_write(rt2x00dev, CSR8, reg); rt2x00pci_register_write(rt2x00dev, CSR8, reg);
spin_unlock_irq(&rt2x00dev->irqmask_lock); spin_unlock_irq(&rt2x00dev->irqmask_lock);
}
} }
static void rt2500pci_tbtt_tasklet(unsigned long data) static void rt2500pci_tbtt_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00lib_beacondone(rt2x00dev); rt2x00lib_beacondone(rt2x00dev);
rt2500pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2500pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE);
} }
static void rt2500pci_rxdone_tasklet(unsigned long data) static void rt2500pci_rxdone_tasklet(unsigned long data)
@ -1502,7 +1495,7 @@ static void rt2500pci_rxdone_tasklet(unsigned long data)
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
if (rt2x00pci_rxdone(rt2x00dev)) if (rt2x00pci_rxdone(rt2x00dev))
tasklet_schedule(&rt2x00dev->rxdone_tasklet); tasklet_schedule(&rt2x00dev->rxdone_tasklet);
else else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2500pci_enable_interrupt(rt2x00dev, CSR8_RXDONE); rt2500pci_enable_interrupt(rt2x00dev, CSR8_RXDONE);
} }

View File

@ -200,13 +200,6 @@ static void rt2800pci_start_queue(struct data_queue *queue)
rt2x00pci_register_write(rt2x00dev, MAC_SYS_CTRL, reg); rt2x00pci_register_write(rt2x00dev, MAC_SYS_CTRL, reg);
break; break;
case QID_BEACON: case QID_BEACON:
/*
* Allow beacon tasklets to be scheduled for periodic
* beacon updates.
*/
tasklet_enable(&rt2x00dev->tbtt_tasklet);
tasklet_enable(&rt2x00dev->pretbtt_tasklet);
rt2x00pci_register_read(rt2x00dev, BCN_TIME_CFG, &reg); rt2x00pci_register_read(rt2x00dev, BCN_TIME_CFG, &reg);
rt2x00_set_field32(&reg, BCN_TIME_CFG_TSF_TICKING, 1); rt2x00_set_field32(&reg, BCN_TIME_CFG_TSF_TICKING, 1);
rt2x00_set_field32(&reg, BCN_TIME_CFG_TBTT_ENABLE, 1); rt2x00_set_field32(&reg, BCN_TIME_CFG_TBTT_ENABLE, 1);
@ -269,10 +262,13 @@ static void rt2800pci_stop_queue(struct data_queue *queue)
rt2x00pci_register_write(rt2x00dev, INT_TIMER_EN, reg); rt2x00pci_register_write(rt2x00dev, INT_TIMER_EN, reg);
/* /*
* Wait for tbtt tasklets to finish. * Wait for current invocation to finish. The tasklet
* won't be scheduled anymore afterwards since we disabled
* the TBTT and PRE TBTT timer.
*/ */
tasklet_disable(&rt2x00dev->tbtt_tasklet); tasklet_kill(&rt2x00dev->tbtt_tasklet);
tasklet_disable(&rt2x00dev->pretbtt_tasklet); tasklet_kill(&rt2x00dev->pretbtt_tasklet);
break; break;
default: default:
break; break;
@ -437,14 +433,6 @@ static void rt2800pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
if (state == STATE_RADIO_IRQ_ON) { if (state == STATE_RADIO_IRQ_ON) {
rt2x00pci_register_read(rt2x00dev, INT_SOURCE_CSR, &reg); rt2x00pci_register_read(rt2x00dev, INT_SOURCE_CSR, &reg);
rt2x00pci_register_write(rt2x00dev, INT_SOURCE_CSR, reg); rt2x00pci_register_write(rt2x00dev, INT_SOURCE_CSR, reg);
/*
* Enable tasklets. The beacon related tasklets are
* enabled when the beacon queue is started.
*/
tasklet_enable(&rt2x00dev->txstatus_tasklet);
tasklet_enable(&rt2x00dev->rxdone_tasklet);
tasklet_enable(&rt2x00dev->autowake_tasklet);
} }
spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);
@ -472,12 +460,13 @@ static void rt2800pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
if (state == STATE_RADIO_IRQ_OFF) { if (state == STATE_RADIO_IRQ_OFF) {
/* /*
* Ensure that all tasklets are finished before * Wait for possibly running tasklets to finish.
* disabling the interrupts.
*/ */
tasklet_disable(&rt2x00dev->txstatus_tasklet); tasklet_kill(&rt2x00dev->txstatus_tasklet);
tasklet_disable(&rt2x00dev->rxdone_tasklet); tasklet_kill(&rt2x00dev->rxdone_tasklet);
tasklet_disable(&rt2x00dev->autowake_tasklet); tasklet_kill(&rt2x00dev->autowake_tasklet);
tasklet_kill(&rt2x00dev->tbtt_tasklet);
tasklet_kill(&rt2x00dev->pretbtt_tasklet);
} }
} }
@ -813,14 +802,16 @@ static void rt2800pci_pretbtt_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00lib_pretbtt(rt2x00dev); rt2x00lib_pretbtt(rt2x00dev);
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_PRE_TBTT); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_PRE_TBTT);
} }
static void rt2800pci_tbtt_tasklet(unsigned long data) static void rt2800pci_tbtt_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00lib_beacondone(rt2x00dev); rt2x00lib_beacondone(rt2x00dev);
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TBTT); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TBTT);
} }
static void rt2800pci_rxdone_tasklet(unsigned long data) static void rt2800pci_rxdone_tasklet(unsigned long data)
@ -828,7 +819,7 @@ static void rt2800pci_rxdone_tasklet(unsigned long data)
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
if (rt2x00pci_rxdone(rt2x00dev)) if (rt2x00pci_rxdone(rt2x00dev))
tasklet_schedule(&rt2x00dev->rxdone_tasklet); tasklet_schedule(&rt2x00dev->rxdone_tasklet);
else else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RX_DONE); rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RX_DONE);
} }
@ -836,7 +827,8 @@ static void rt2800pci_autowake_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2800pci_wakeup(rt2x00dev); rt2800pci_wakeup(rt2x00dev);
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_AUTO_WAKEUP); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt2800pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_AUTO_WAKEUP);
} }
static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev) static void rt2800pci_txstatus_interrupt(struct rt2x00_dev *rt2x00dev)

View File

@ -946,7 +946,6 @@ static int rt2x00lib_probe_hw(struct rt2x00_dev *rt2x00dev)
tasklet_init(&rt2x00dev->taskletname, \ tasklet_init(&rt2x00dev->taskletname, \
rt2x00dev->ops->lib->taskletname, \ rt2x00dev->ops->lib->taskletname, \
(unsigned long)rt2x00dev); \ (unsigned long)rt2x00dev); \
tasklet_disable(&rt2x00dev->taskletname); \
} }
RT2X00_TASKLET_INIT(txstatus_tasklet); RT2X00_TASKLET_INIT(txstatus_tasklet);

View File

@ -1142,11 +1142,6 @@ static void rt61pci_start_queue(struct data_queue *queue)
rt2x00pci_register_write(rt2x00dev, TXRX_CSR0, reg); rt2x00pci_register_write(rt2x00dev, TXRX_CSR0, reg);
break; break;
case QID_BEACON: case QID_BEACON:
/*
* Allow the tbtt tasklet to be scheduled.
*/
tasklet_enable(&rt2x00dev->tbtt_tasklet);
rt2x00pci_register_read(rt2x00dev, TXRX_CSR9, &reg); rt2x00pci_register_read(rt2x00dev, TXRX_CSR9, &reg);
rt2x00_set_field32(&reg, TXRX_CSR9_TSF_TICKING, 1); rt2x00_set_field32(&reg, TXRX_CSR9_TSF_TICKING, 1);
rt2x00_set_field32(&reg, TXRX_CSR9_TBTT_ENABLE, 1); rt2x00_set_field32(&reg, TXRX_CSR9_TBTT_ENABLE, 1);
@ -1230,7 +1225,7 @@ static void rt61pci_stop_queue(struct data_queue *queue)
/* /*
* Wait for possibly running tbtt tasklets. * Wait for possibly running tbtt tasklets.
*/ */
tasklet_disable(&rt2x00dev->tbtt_tasklet); tasklet_kill(&rt2x00dev->tbtt_tasklet);
break; break;
default: default:
break; break;
@ -1731,13 +1726,6 @@ static void rt61pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
rt2x00pci_register_read(rt2x00dev, MCU_INT_SOURCE_CSR, &reg); rt2x00pci_register_read(rt2x00dev, MCU_INT_SOURCE_CSR, &reg);
rt2x00pci_register_write(rt2x00dev, MCU_INT_SOURCE_CSR, reg); rt2x00pci_register_write(rt2x00dev, MCU_INT_SOURCE_CSR, reg);
/*
* Enable tasklets.
*/
tasklet_enable(&rt2x00dev->txstatus_tasklet);
tasklet_enable(&rt2x00dev->rxdone_tasklet);
tasklet_enable(&rt2x00dev->autowake_tasklet);
} }
/* /*
@ -1772,9 +1760,10 @@ static void rt61pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
/* /*
* Ensure that all tasklets are finished. * Ensure that all tasklets are finished.
*/ */
tasklet_disable(&rt2x00dev->txstatus_tasklet); tasklet_kill(&rt2x00dev->txstatus_tasklet);
tasklet_disable(&rt2x00dev->rxdone_tasklet); tasklet_kill(&rt2x00dev->rxdone_tasklet);
tasklet_disable(&rt2x00dev->autowake_tasklet); tasklet_kill(&rt2x00dev->autowake_tasklet);
tasklet_kill(&rt2x00dev->tbtt_tasklet);
} }
} }
@ -2300,22 +2289,24 @@ static void rt61pci_txstatus_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt61pci_txdone(rt2x00dev); rt61pci_txdone(rt2x00dev);
rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TXDONE); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TXDONE);
} }
static void rt61pci_tbtt_tasklet(unsigned long data) static void rt61pci_tbtt_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00lib_beacondone(rt2x00dev); rt2x00lib_beacondone(rt2x00dev);
rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_BEACON_DONE); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_BEACON_DONE);
} }
static void rt61pci_rxdone_tasklet(unsigned long data) static void rt61pci_rxdone_tasklet(unsigned long data)
{ {
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
if (rt2x00pci_rxdone(rt2x00dev)) if (rt2x00pci_rxdone(rt2x00dev))
rt2x00pci_rxdone(rt2x00dev); tasklet_schedule(&rt2x00dev->rxdone_tasklet);
else else if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RXDONE); rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RXDONE);
} }
@ -2325,7 +2316,8 @@ static void rt61pci_autowake_tasklet(unsigned long data)
rt61pci_wakeup(rt2x00dev); rt61pci_wakeup(rt2x00dev);
rt2x00pci_register_write(rt2x00dev, rt2x00pci_register_write(rt2x00dev,
M2H_CMD_DONE_CSR, 0xffffffff); M2H_CMD_DONE_CSR, 0xffffffff);
rt61pci_enable_mcu_interrupt(rt2x00dev, MCU_INT_MASK_CSR_TWAKEUP); if (test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
rt61pci_enable_mcu_interrupt(rt2x00dev, MCU_INT_MASK_CSR_TWAKEUP);
} }
static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance) static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance)