diff options
author | Helmut Schaa <helmut.schaa@googlemail.com> | 2011-01-30 13:19:08 +0100 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2011-01-31 15:06:23 -0500 |
commit | 5846a550b5838ea7fe8e280caff159a5ddb5c7e1 (patch) | |
tree | 5c4e2210b56c4252c0e4d1b4882efe17aec8ef18 /drivers/net/wireless/rt2x00/rt61pci.c | |
parent | a9d61e9e779579c66c0d4c8af203d51dbca1473c (diff) | |
download | lwn-5846a550b5838ea7fe8e280caff159a5ddb5c7e1.tar.gz lwn-5846a550b5838ea7fe8e280caff159a5ddb5c7e1.zip |
rt2x00: Convert rt61pci to use tasklets
Fix interrupt processing on slow machines by using individual tasklets
for each different device interrupt. This ensures that while a RX or TX
status tasklet is scheduled only the according device interrupt is
masked and other interrupts such as TBTT can still be processed.
Also, this allows us to use tasklet_hi_schedule for TBTT processing
which is required to not send out beacons with a wrong DTIM count (due
to delayed periodic beacon updates). Furthermore, this improves the
latency between the TBTT and sending out buffered multi- and broadcast
traffic.
As a nice bonus, the interrupt handling overhead should be much lower.
Compile-tested only.
Signed-off-by: Helmut Schaa <helmut.schaa@googlemail.com>
Acked-by: Gertjan van Wingerde <gwingerde@gmail.com>
Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/rt2x00/rt61pci.c')
-rw-r--r-- | drivers/net/wireless/rt2x00/rt61pci.c | 175 |
1 files changed, 130 insertions, 45 deletions
diff --git a/drivers/net/wireless/rt2x00/rt61pci.c b/drivers/net/wireless/rt2x00/rt61pci.c index f14cc452eb0c..351055d4b89b 100644 --- a/drivers/net/wireless/rt2x00/rt61pci.c +++ b/drivers/net/wireless/rt2x00/rt61pci.c @@ -1142,6 +1142,11 @@ static void rt61pci_start_queue(struct data_queue *queue) rt2x00pci_register_write(rt2x00dev, TXRX_CSR0, reg); break; case QID_BEACON: + /* + * Allow the tbtt tasklet to be scheduled. + */ + tasklet_enable(&rt2x00dev->tbtt_tasklet); + rt2x00pci_register_read(rt2x00dev, TXRX_CSR9, ®); rt2x00_set_field32(®, TXRX_CSR9_TSF_TICKING, 1); rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 1); @@ -1221,6 +1226,11 @@ static void rt61pci_stop_queue(struct data_queue *queue) rt2x00_set_field32(®, TXRX_CSR9_TBTT_ENABLE, 0); rt2x00_set_field32(®, TXRX_CSR9_BEACON_GEN, 0); rt2x00pci_register_write(rt2x00dev, TXRX_CSR9, reg); + + /* + * Wait for possibly running tbtt tasklets. + */ + tasklet_disable(&rt2x00dev->tbtt_tasklet); break; default: break; @@ -1710,6 +1720,7 @@ static void rt61pci_toggle_irq(struct rt2x00_dev *rt2x00dev, int mask = (state == STATE_RADIO_IRQ_OFF) || (state == STATE_RADIO_IRQ_OFF_ISR); u32 reg; + unsigned long flags; /* * When interrupts are being enabled, the interrupt registers @@ -1721,12 +1732,21 @@ static void rt61pci_toggle_irq(struct rt2x00_dev *rt2x00dev, rt2x00pci_register_read(rt2x00dev, MCU_INT_SOURCE_CSR, ®); 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); } /* * Only toggle the interrupts bits we are going to use. * Non-checked interrupt bits are disabled by default. */ + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); + rt2x00pci_register_read(rt2x00dev, INT_MASK_CSR, ®); rt2x00_set_field32(®, INT_MASK_CSR_TXDONE, mask); rt2x00_set_field32(®, INT_MASK_CSR_RXDONE, mask); @@ -1746,6 +1766,17 @@ static void rt61pci_toggle_irq(struct rt2x00_dev *rt2x00dev, rt2x00_set_field32(®, MCU_INT_MASK_CSR_7, mask); rt2x00_set_field32(®, MCU_INT_MASK_CSR_TWAKEUP, mask); rt2x00pci_register_write(rt2x00dev, MCU_INT_MASK_CSR, reg); + + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); + + if (state == STATE_RADIO_IRQ_OFF) { + /* + * Ensure that all tasklets are finished. + */ + tasklet_disable(&rt2x00dev->txstatus_tasklet); + tasklet_disable(&rt2x00dev->rxdone_tasklet); + tasklet_disable(&rt2x00dev->autowake_tasklet); + } } static int rt61pci_enable_radio(struct rt2x00_dev *rt2x00dev) @@ -2223,61 +2254,80 @@ static void rt61pci_wakeup(struct rt2x00_dev *rt2x00dev) rt61pci_config(rt2x00dev, &libconf, IEEE80211_CONF_CHANGE_PS); } -static irqreturn_t rt61pci_interrupt_thread(int irq, void *dev_instance) +static void rt61pci_enable_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) { - struct rt2x00_dev *rt2x00dev = dev_instance; - u32 reg = rt2x00dev->irqvalue[0]; - u32 reg_mcu = rt2x00dev->irqvalue[1]; + unsigned long flags; + u32 reg; /* - * Handle interrupts, walk through all bits - * and run the tasks, the bits are checked in order of - * priority. + * Enable a single interrupt. The interrupt mask register + * access needs locking. */ + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); - /* - * 1 - Rx ring done interrupt. - */ - if (rt2x00_get_field32(reg, INT_SOURCE_CSR_RXDONE)) - rt2x00pci_rxdone(rt2x00dev); + rt2x00pci_register_read(rt2x00dev, INT_MASK_CSR, ®); + rt2x00_set_field32(®, irq_field, 0); + rt2x00pci_register_write(rt2x00dev, INT_MASK_CSR, reg); - /* - * 2 - Tx ring done interrupt. - */ - if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TXDONE)) - rt61pci_txdone(rt2x00dev); + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); +} - /* - * 3 - Handle MCU command done. - */ - if (reg_mcu) - rt2x00pci_register_write(rt2x00dev, - M2H_CMD_DONE_CSR, 0xffffffff); +static void rt61pci_enable_mcu_interrupt(struct rt2x00_dev *rt2x00dev, + struct rt2x00_field32 irq_field) +{ + unsigned long flags; + u32 reg; /* - * 4 - MCU Autowakeup interrupt. + * Enable a single MCU interrupt. The interrupt mask register + * access needs locking. */ - if (rt2x00_get_field32(reg_mcu, MCU_INT_SOURCE_CSR_TWAKEUP)) - rt61pci_wakeup(rt2x00dev); + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); - /* - * 5 - Beacon done interrupt. - */ - if (rt2x00_get_field32(reg, INT_SOURCE_CSR_BEACON_DONE)) - rt2x00lib_beacondone(rt2x00dev); + rt2x00pci_register_read(rt2x00dev, MCU_INT_MASK_CSR, ®); + rt2x00_set_field32(®, irq_field, 0); + rt2x00pci_register_write(rt2x00dev, MCU_INT_MASK_CSR, reg); - /* Enable interrupts again. */ - rt2x00dev->ops->lib->set_device_state(rt2x00dev, - STATE_RADIO_IRQ_ON_ISR); - return IRQ_HANDLED; + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); } +static void rt61pci_txstatus_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt61pci_txdone(rt2x00dev); + rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_TXDONE); +} + +static void rt61pci_tbtt_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2x00lib_beacondone(rt2x00dev); + rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_BEACON_DONE); +} + +static void rt61pci_rxdone_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt2x00pci_rxdone(rt2x00dev); + rt61pci_enable_interrupt(rt2x00dev, INT_MASK_CSR_RXDONE); +} + +static void rt61pci_autowake_tasklet(unsigned long data) +{ + struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data; + rt61pci_wakeup(rt2x00dev); + rt2x00pci_register_write(rt2x00dev, + M2H_CMD_DONE_CSR, 0xffffffff); + rt61pci_enable_mcu_interrupt(rt2x00dev, MCU_INT_MASK_CSR_TWAKEUP); +} static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance) { struct rt2x00_dev *rt2x00dev = dev_instance; - u32 reg_mcu; - u32 reg; + u32 reg_mcu, mask_mcu; + u32 reg, mask; + unsigned long flags; /* * Get the interrupt sources & saved to local variable. @@ -2295,14 +2345,46 @@ static irqreturn_t rt61pci_interrupt(int irq, void *dev_instance) if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) return IRQ_HANDLED; - /* Store irqvalues for use in the interrupt thread. */ - rt2x00dev->irqvalue[0] = reg; - rt2x00dev->irqvalue[1] = reg_mcu; + /* + * Schedule tasklets for interrupt handling. + */ + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_RXDONE)) + tasklet_schedule(&rt2x00dev->rxdone_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_TXDONE)) + tasklet_schedule(&rt2x00dev->txstatus_tasklet); + + if (rt2x00_get_field32(reg, INT_SOURCE_CSR_BEACON_DONE)) + tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet); + + if (rt2x00_get_field32(reg_mcu, MCU_INT_SOURCE_CSR_TWAKEUP)) + tasklet_schedule(&rt2x00dev->autowake_tasklet); + + /* + * Since INT_MASK_CSR and INT_SOURCE_CSR use the same bits + * for interrupts and interrupt masks we can just use the value of + * INT_SOURCE_CSR to create the interrupt mask. + */ + mask = reg; + mask_mcu = reg_mcu; + + /* + * Disable all interrupts for which a tasklet was scheduled right now, + * the tasklet will reenable the appropriate interrupts. + */ + spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags); + + rt2x00pci_register_read(rt2x00dev, INT_MASK_CSR, ®); + reg |= mask; + rt2x00pci_register_write(rt2x00dev, INT_MASK_CSR, reg); - /* Disable interrupts, will be enabled again in the interrupt thread. */ - rt2x00dev->ops->lib->set_device_state(rt2x00dev, - STATE_RADIO_IRQ_OFF_ISR); - return IRQ_WAKE_THREAD; + rt2x00pci_register_read(rt2x00dev, MCU_INT_MASK_CSR, ®); + reg |= mask_mcu; + rt2x00pci_register_write(rt2x00dev, MCU_INT_MASK_CSR, reg); + + spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags); + + return IRQ_HANDLED; } /* @@ -2896,7 +2978,10 @@ static const struct ieee80211_ops rt61pci_mac80211_ops = { static const struct rt2x00lib_ops rt61pci_rt2x00_ops = { .irq_handler = rt61pci_interrupt, - .irq_handler_thread = rt61pci_interrupt_thread, + .txstatus_tasklet = rt61pci_txstatus_tasklet, + .tbtt_tasklet = rt61pci_tbtt_tasklet, + .rxdone_tasklet = rt61pci_rxdone_tasklet, + .autowake_tasklet = rt61pci_autowake_tasklet, .probe_hw = rt61pci_probe_hw, .get_firmware_name = rt61pci_get_firmware_name, .check_firmware = rt61pci_check_firmware, |