[rt2x00-users] [PATCH 10/12] rt2x00: Convert rt2400pci interrupt handling to use tasklets

Gertjan van Wingerde gwingerde at gmail.com
Sat Jan 15 09:00:08 EST 2011


On 01/14/11 10:41, Helmut Schaa wrote:
> 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 at googlemail.com>

Acked-by: Gertjan van Wingerde <gwingerde at gmail.com>

> ---
>  drivers/net/wireless/rt2x00/rt2400pci.c |  154 +++++++++++++++++++++++--------
>  1 files changed, 115 insertions(+), 39 deletions(-)
> 
> diff --git a/drivers/net/wireless/rt2x00/rt2400pci.c b/drivers/net/wireless/rt2x00/rt2400pci.c
> index cb28e1b..b324917 100644
> --- a/drivers/net/wireless/rt2x00/rt2400pci.c
> +++ b/drivers/net/wireless/rt2x00/rt2400pci.c
> @@ -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);
> @@ -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;
> @@ -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
> @@ -972,12 +983,20 @@ 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);
> @@ -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_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)
> @@ -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.
> @@ -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;
>  }
>  
>  /*
> @@ -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,




More information about the users mailing list