Skip to content

Commit

Permalink
rt2x00: Convert rt2400pci interrupt handling to use tasklets
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Helmut Schaa authored and John W. Linville committed Jan 31, 2011
1 parent 16222a0 commit bcf3cfd
Showing 1 changed file with 115 additions and 39 deletions.
154 changes: 115 additions & 39 deletions drivers/net/wireless/rt2x00/rt2400pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,11 @@ static void rt2400pci_start_queue(struct data_queue *queue)
rt2x00pci_register_write(rt2x00dev, RXCSR0, reg);
break;
case QID_BEACON:
/*
* Allow the tbtt tasklet to be scheduled.
*/
tasklet_enable(&rt2x00dev->tbtt_tasklet);

rt2x00pci_register_read(rt2x00dev, CSR14, &reg);
rt2x00_set_field32(&reg, CSR14_TSF_COUNT, 1);
rt2x00_set_field32(&reg, CSR14_TBCN, 1);
Expand Down Expand Up @@ -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_BEACON_GEN, 0);
rt2x00pci_register_write(rt2x00dev, CSR14, reg);

/*
* Wait for possibly running tbtt tasklets.
*/
tasklet_disable(&rt2x00dev->tbtt_tasklet);
break;
default:
break;
Expand Down Expand Up @@ -964,6 +974,7 @@ static void rt2400pci_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
Expand All @@ -972,19 +983,38 @@ static void rt2400pci_toggle_irq(struct rt2x00_dev *rt2x00dev,
if (state == STATE_RADIO_IRQ_ON) {
rt2x00pci_register_read(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.
* Non-checked interrupt bits are disabled by default.
*/
spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);

rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00_set_field32(&reg, CSR8_TBCN_EXPIRE, mask);
rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, mask);
rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, mask);
rt2x00_set_field32(&reg, CSR8_TXDONE_PRIORING, mask);
rt2x00_set_field32(&reg, CSR8_RXDONE, mask);
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)
Expand Down Expand Up @@ -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;
u32 reg = rt2x00dev->irqvalue[0];
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 - Beacon timer expired interrupt.
*/
if (rt2x00_get_field32(reg, CSR7_TBCN_EXPIRE))
rt2x00lib_beacondone(rt2x00dev);
rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00_set_field32(&reg, irq_field, 0);
rt2x00pci_register_write(rt2x00dev, CSR8, reg);

/*
* 2 - Rx ring done interrupt.
*/
if (rt2x00_get_field32(reg, CSR7_RXDONE))
rt2x00pci_rxdone(rt2x00dev);
spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);
}

/*
* 3 - Atim ring transmit done interrupt.
*/
if (rt2x00_get_field32(reg, CSR7_TXDONE_ATIMRING))
rt2400pci_txdone(rt2x00dev, QID_ATIM);
static void rt2400pci_txstatus_tasklet(unsigned long data)
{
struct rt2x00_dev *rt2x00dev = (struct rt2x00_dev *)data;
u32 reg;
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_AC_VO);
rt2400pci_txdone(rt2x00dev, QID_ATIM);
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))
rt2400pci_txdone(rt2x00dev, QID_AC_VI);
spin_lock_irqsave(&rt2x00dev->irqmask_lock, flags);

/* Enable interrupts again. */
rt2x00dev->ops->lib->set_device_state(rt2x00dev,
STATE_RADIO_IRQ_ON_ISR);
return IRQ_HANDLED;
rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
rt2x00_set_field32(&reg, CSR8_TXDONE_TXRING, 0);
rt2x00_set_field32(&reg, CSR8_TXDONE_ATIMRING, 0);
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)
{
struct rt2x00_dev *rt2x00dev = dev_instance;
u32 reg;
u32 reg, mask;
unsigned long flags;

/*
* Get the interrupt sources & saved to local variable.
Expand All @@ -1350,14 +1394,44 @@ static irqreturn_t rt2400pci_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;
mask = 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);

if (rt2x00_get_field32(reg, CSR7_TXDONE_ATIMRING) ||
rt2x00_get_field32(reg, CSR7_TXDONE_PRIORING) ||
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);
}

/* Disable interrupts, will be enabled again in the interrupt thread. */
rt2x00dev->ops->lib->set_device_state(rt2x00dev,
STATE_RADIO_IRQ_OFF_ISR);
/*
* 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);

return IRQ_WAKE_THREAD;
rt2x00pci_register_read(rt2x00dev, CSR8, &reg);
reg |= mask;
rt2x00pci_register_write(rt2x00dev, CSR8, reg);

spin_unlock_irqrestore(&rt2x00dev->irqmask_lock, flags);



return IRQ_HANDLED;
}

/*
Expand Down Expand Up @@ -1651,7 +1725,9 @@ static const struct ieee80211_ops rt2400pci_mac80211_ops = {

static const struct rt2x00lib_ops rt2400pci_rt2x00_ops = {
.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,
.initialize = rt2x00pci_initialize,
.uninitialize = rt2x00pci_uninitialize,
Expand Down

0 comments on commit bcf3cfd

Please sign in to comment.