Commit bcf3cfd0 authored by Helmut Schaa's avatar Helmut Schaa Committed by John W. Linville

rt2x00: Convert rt2400pci interrupt handling 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: default avatarHelmut Schaa <helmut.schaa@googlemail.com>
Acked-by: default avatarGertjan van Wingerde <gwingerde@gmail.com>
Signed-off-by: default avatarIvo van Doorn <IvDoorn@gmail.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 16222a0d
...@@ -645,6 +645,11 @@ static void rt2400pci_start_queue(struct data_queue *queue) ...@@ -645,6 +645,11 @@ 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);
...@@ -706,6 +711,11 @@ static void rt2400pci_stop_queue(struct data_queue *queue) ...@@ -706,6 +711,11 @@ static void rt2400pci_stop_queue(struct data_queue *queue)
rt2x00_set_field32(&reg, CSR14_TBCN, 0); rt2x00_set_field32(&reg, CSR14_TBCN, 0);
rt2x00_set_field32(&reg, CSR14_BEACON_GEN, 0); rt2x00_set_field32(&reg, CSR14_BEACON_GEN, 0);
rt2x00pci_register_write(rt2x00dev, CSR14, reg); rt2x00pci_register_write(rt2x00dev, CSR14, reg);
/*
* Wait for possibly running tbtt tasklets.
*/
tasklet_disable(&rt2x00dev->tbtt_tasklet);
break; break;
default: default:
break; break;
...@@ -964,6 +974,7 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev, ...@@ -964,6 +974,7 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
int mask = (state == STATE_RADIO_IRQ_OFF) || int mask = (state == STATE_RADIO_IRQ_OFF) ||
(state == STATE_RADIO_IRQ_OFF_ISR); (state == STATE_RADIO_IRQ_OFF_ISR);
u32 reg; u32 reg;
unsigned long flags;
/* /*
* When interrupts are being enabled, the interrupt registers * When interrupts are being enabled, the interrupt registers
...@@ -972,12 +983,20 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev, ...@@ -972,12 +983,20 @@ 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);
} }
/* /*
* Only toggle the interrupts bits we are going to use. * Only toggle the interrupts bits we are going to use.
* Non-checked interrupt bits are disabled by default. * Non-checked interrupt bits are disabled by default.
*/ */
spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);
rt2x00pci_register_read(rt2x00dev, CSR8, &reg); rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00_set_field32(&reg, CSR8_TBCN_EXPIRE, mask); rt2x00_set_field32(&reg, CSR8_TBCN_EXPIRE, mask);
rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, mask); rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, mask);
...@@ -985,6 +1004,17 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev, ...@@ -985,6 +1004,17 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, mask); rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, mask);
rt2x00_set_field32(&reg, CSR8_RXDONE, mask); rt2x00_set_field32(&reg, CSR8_RXDONE, mask);
rt2x00pci_register_write(rt2x00dev, CSR8, reg); rt2x00pci_register_write(rt2x00dev, CSR8, reg);
spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);
if (state == STATE_RADIO_IRQ_OFF) {
/*
* Ensure that all tasklets are finished before
* disabling the interrupts.
*/
tasklet_disable(&rt2x00dev->txstatus_tasklet);
tasklet_disable(&rt2x00dev->rxdone_tasklet);
}
} }
static int rt2400pci_enable_radio(struct rt2x00_dev *rt2x00dev) static int rt2400pci_enable_radio(struct rt2x00_dev *rt2x00dev)
...@@ -1285,57 +1315,71 @@ static void rt2400pci_txdone(struct rt2x00_dev *rt2x00dev, ...@@ -1285,57 +1315,71 @@ static void rt2400pci_txdone(struct rt2x00_dev *rt2x00dev,
} }
} }
static irqreturn_t rt2400pci_interrupt_thread(int irq, void *dev_instance) static void rt2400pci_enable_interrupt(struct rt2x00_dev *rt2x00dev,
struct rt2x00_field32 irq_field)
{ {
struct rt2x00_dev *rt2x00dev = dev_instance; unsigned long flags;
u32 reg = rt2x00dev->irqvalue[0]; u32 reg;
/* /*
* Handle interrupts, walk through all bits * Enable a single interrupt. The interrupt mask register
* and run the tasks, the bits are checked in order of * access needs locking.
* priority.
*/ */
spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);
/* rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
* 1 - Beacon timer expired interrupt. rt2x00_set_field32(&reg, irq_field, 0);
*/ rt2x00pci_register_write(rt2x00dev, CSR8, reg);
if (rt2x00_get_field32(reg, CSR7_TBCN_EXPIRE))
rt2x00lib_beacondone(rt2x00dev);
/* spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);
* 2 - Rx ring done interrupt. }
*/
if (rt2x00_get_field32(reg, CSR7_RXDONE))
rt2x00pci_rxdone(rt2x00dev);
/* static void rt2400pci_txstatus_tasklet(unsigned long data)
* 3 - Atim ring transmit done interrupt. {
*/ struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
if (rt2x00_get_field32(reg, CSR7_TXDONE_ATIMRING)) u32 reg;
rt2400pci_txdone(rt2x00dev, QID_ATIM); unsigned long flags;
/* /*
* 4 - Priority ring transmit done interrupt. * Handle all tx queues.
*/ */
if (rt2x00_get_field32(reg, CSR7_TXDONE_PRIORING)) rt2400pci_txdone(rt2x00dev, QID_ATIM);
rt2400pci_txdone(rt2x00dev, QID_AC_VO); rt2400pci_txdone(rt2x00dev, QID_AC_VO);
rt2400pci_txdone(rt2x00dev, QID_AC_VI);
/* /*
* 5 - Tx ring transmit done interrupt. * Enable all TXDONE interrupts again.
*/ */
if (rt2x00_get_field32(reg, CSR7_TXDONE_TXRING)) spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);
rt2400pci_txdone(rt2x00dev, QID_AC_VI);
/* Enable interrupts again. */ rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00dev->ops->lib->set_device_state(rt2x00dev, rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, 0);
STATE_RADIO_IRQ_ON_ISR); rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, 0);
return IRQ_HANDLED; rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, 0);
rt2x00pci_register_write(rt2x00dev, CSR8, reg);
spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);
}
static void rt2400pci_tbtt_tasklet(unsigned long data)
{
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00lib_beacondone(rt2x00dev);
rt2400pci_enable_interrupt(rt2x00dev, CSR8_TBCN_EXPIRE);
}
static void rt2400pci_rxdone_tasklet(unsigned long data)
{
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
rt2x00pci_rxdone(rt2x00dev);
rt2400pci_enable_interrupt(rt2x00dev, CSR8_RXDONE);
} }
static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance) static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance)
{ {
struct rt2x00_dev *rt2x00dev = dev_instance; struct rt2x00_dev *rt2x00dev = dev_instance;
u32 reg; u32 reg, mask;
unsigned long flags;
/* /*
* Get the interrupt sources & saved to local variable. * Get the interrupt sources & saved to local variable.
...@@ -1350,14 +1394,44 @@ static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance) ...@@ -1350,14 +1394,44 @@ static irqreturn_t rt2400pci_interrupt(int irq, void *dev_instance)
if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags)) if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
return IRQ_HANDLED; return IRQ_HANDLED;
/* Store irqvalues for use in the interrupt thread. */ mask = reg;
rt2x00dev->irqvalue[0] = reg;
/*
* Schedule tasklets for interrupt handling.
*/
if (rt2x00_get_field32(reg, CSR7_TBCN_EXPIRE))
tasklet_hi_schedule(&rt2x00dev->tbtt_tasklet);
if (rt2x00_get_field32(reg, CSR7_RXDONE))
tasklet_schedule(&rt2x00dev->rxdone_tasklet);
/* Disable interrupts, will be enabled again in the interrupt thread. */ if (rt2x00_get_field32(reg, CSR7_TXDONE_ATIMRING) ||
rt2x00dev->ops->lib->set_device_state(rt2x00dev, rt2x00_get_field32(reg, CSR7_TXDONE_PRIORING) ||
STATE_RADIO_IRQ_OFF_ISR); rt2x00_get_field32(reg, CSR7_TXDONE_TXRING)) {
tasklet_schedule(&rt2x00dev->txstatus_tasklet);
/*
* Mask out all txdone interrupts.
*/
rt2x00_set_field32(&mask, CSR8_TXDONE_TXRING, 1);
rt2x00_set_field32(&mask, CSR8_TXDONE_ATIMRING, 1);
rt2x00_set_field32(&mask, CSR8_TXDONE_PRIORING, 1);
}
return IRQ_WAKE_THREAD; /*
* 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, CSR8, &reg);
reg |= mask;
rt2x00pci_register_write(rt2x00dev, CSR8, reg);
spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);
return IRQ_HANDLED;
} }
/* /*
...@@ -1651,7 +1725,9 @@ static const struct ieee80211_ops rt2400pci_mac80211_ops = { ...@@ -1651,7 +1725,9 @@ static const struct ieee80211_ops rt2400pci_mac80211_ops = {
static const struct rt2x00lib_ops rt2400pci_rt2x00_ops = { static const struct rt2x00lib_ops rt2400pci_rt2x00_ops = {
.irq_handler = rt2400pci_interrupt, .irq_handler = rt2400pci_interrupt,
.irq_handler_thread = rt2400pci_interrupt_thread, .txstatus_tasklet = rt2400pci_txstatus_tasklet,
.tbtt_tasklet = rt2400pci_tbtt_tasklet,
.rxdone_tasklet = rt2400pci_rxdone_tasklet,
.probe_hw = rt2400pci_probe_hw, .probe_hw = rt2400pci_probe_hw,
.initialize = rt2x00pci_initialize, .initialize = rt2x00pci_initialize,
.uninitialize = rt2x00pci_uninitialize, .uninitialize = rt2x00pci_uninitialize,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment