[PATCH] bcm43xx: preemptible periodic work
authorMichael Buesch <mb@bu3sch.de>
Mon, 5 Jun 2006 18:24:21 +0000 (20:24 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Thu, 15 Jun 2006 19:48:13 +0000 (15:48 -0400)
Make the heavy periodic work preemptible to avoid disabling
local IRQs for several msecs.

Signed-off-by: Michael Buesch <mb@buesch.de>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/bcm43xx/bcm43xx_main.c
drivers/net/wireless/bcm43xx/bcm43xx_phy.c
drivers/net/wireless/bcm43xx/bcm43xx_pio.c
drivers/net/wireless/bcm43xx/bcm43xx_pio.h

index 835a2df1fe30469f5affb05dfdd8cbc1651593cc..77d0e390b021e91595dab374c6fe0f5d2d51f8c4 100644 (file)
@@ -498,11 +498,21 @@ static inline u32 bcm43xx_interrupt_disable(struct bcm43xx_private *bcm, u32 mas
        return old_mask;
 }
 
+/* Synchronize IRQ top- and bottom-half.
+ * IRQs must be masked before calling this.
+ * This must not be called with the irq_lock held.
+ */
+static void bcm43xx_synchronize_irq(struct bcm43xx_private *bcm)
+{
+       synchronize_irq(bcm->irq);
+       tasklet_disable(&bcm->isr_tasklet);
+}
+
 /* Make sure we don't receive more data from the device. */
 static int bcm43xx_disable_interrupts_sync(struct bcm43xx_private *bcm, u32 *oldstate)
 {
-       u32 old;
        unsigned long flags;
+       u32 old;
 
        bcm43xx_lock_irqonly(bcm, flags);
        if (unlikely(bcm43xx_status(bcm) != BCM43xx_STAT_INITIALIZED)) {
@@ -510,8 +520,9 @@ static int bcm43xx_disable_interrupts_sync(struct bcm43xx_private *bcm, u32 *old
                return -EBUSY;
        }
        old = bcm43xx_interrupt_disable(bcm, BCM43xx_IRQ_ALL);
-       tasklet_disable(&bcm->isr_tasklet);
        bcm43xx_unlock_irqonly(bcm, flags);
+       bcm43xx_synchronize_irq(bcm);
+
        if (oldstate)
                *oldstate = old;
 
@@ -3108,14 +3119,10 @@ static void bcm43xx_periodic_every15sec(struct bcm43xx_private *bcm)
        //TODO for APHY (temperature?)
 }
 
-static void bcm43xx_periodic_work_handler(void *d)
+static void do_periodic_work(struct bcm43xx_private *bcm)
 {
-       struct bcm43xx_private *bcm = d;
-       unsigned long flags;
        unsigned int state;
 
-       bcm43xx_lock_irqsafe(bcm, flags);
-
        state = bcm->periodic_state;
        if (state % 8 == 0)
                bcm43xx_periodic_every120sec(bcm);
@@ -3123,13 +3130,79 @@ static void bcm43xx_periodic_work_handler(void *d)
                bcm43xx_periodic_every60sec(bcm);
        if (state % 2 == 0)
                bcm43xx_periodic_every30sec(bcm);
-       bcm43xx_periodic_every15sec(bcm);
+       if (state % 1 == 0)
+               bcm43xx_periodic_every15sec(bcm);
        bcm->periodic_state = state + 1;
 
        schedule_delayed_work(&bcm->periodic_work, HZ * 15);
+}
 
-       mmiowb();
-       bcm43xx_unlock_irqsafe(bcm, flags);
+/* Estimate a "Badness" value based on the periodic work
+ * state-machine state. "Badness" is worse (bigger), if the
+ * periodic work will take longer.
+ */
+static int estimate_periodic_work_badness(unsigned int state)
+{
+       int badness = 0;
+
+       if (state % 8 == 0) /* every 120 sec */
+               badness += 10;
+       if (state % 4 == 0) /* every 60 sec */
+               badness += 5;
+       if (state % 2 == 0) /* every 30 sec */
+               badness += 1;
+       if (state % 1 == 0) /* every 15 sec */
+               badness += 1;
+
+#define BADNESS_LIMIT  4
+       return badness;
+}
+
+static void bcm43xx_periodic_work_handler(void *d)
+{
+       struct bcm43xx_private *bcm = d;
+       unsigned long flags;
+       u32 savedirqs = 0;
+       int badness;
+
+       badness = estimate_periodic_work_badness(bcm->periodic_state);
+       if (badness > BADNESS_LIMIT) {
+               /* Periodic work will take a long time, so we want it to
+                * be preemtible.
+                */
+               bcm43xx_lock_irqonly(bcm, flags);
+               netif_stop_queue(bcm->net_dev);
+               if (bcm43xx_using_pio(bcm))
+                       bcm43xx_pio_freeze_txqueues(bcm);
+               savedirqs = bcm43xx_interrupt_disable(bcm, BCM43xx_IRQ_ALL);
+               bcm43xx_unlock_irqonly(bcm, flags);
+               bcm43xx_lock_noirq(bcm);
+               bcm43xx_synchronize_irq(bcm);
+       } else {
+               /* Periodic work should take short time, so we want low
+                * locking overhead.
+                */
+               bcm43xx_lock_irqsafe(bcm, flags);
+       }
+
+       do_periodic_work(bcm);
+
+       if (badness > BADNESS_LIMIT) {
+               bcm43xx_lock_irqonly(bcm, flags);
+               if (likely(bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED)) {
+                       tasklet_enable(&bcm->isr_tasklet);
+                       bcm43xx_interrupt_enable(bcm, savedirqs);
+                       if (bcm43xx_using_pio(bcm))
+                               bcm43xx_pio_thaw_txqueues(bcm);
+               }
+               netif_wake_queue(bcm->net_dev);
+               mmiowb();
+               bcm43xx_unlock_irqonly(bcm, flags);
+               bcm43xx_unlock_noirq(bcm);
+       } else {
+               mmiowb();
+               bcm43xx_unlock_irqsafe(bcm, flags);
+       }
 }
 
 static void bcm43xx_periodic_tasks_delete(struct bcm43xx_private *bcm)
@@ -3670,9 +3743,11 @@ static int bcm43xx_net_open(struct net_device *net_dev)
 static int bcm43xx_net_stop(struct net_device *net_dev)
 {
        struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
+       int err;
 
        ieee80211softmac_stop(net_dev);
-       bcm43xx_disable_interrupts_sync(bcm, NULL);
+       err = bcm43xx_disable_interrupts_sync(bcm, NULL);
+       assert(!err);
        bcm43xx_free_board(bcm);
 
        return 0;
index 952c2f8b8b5e47f355aaa1994bfd800437576ff3..f8200deecc8a68020dfee39f87b5952c86f00988 100644 (file)
@@ -1410,7 +1410,10 @@ static inline
 u16 bcm43xx_phy_lo_g_deviation_subval(struct bcm43xx_private *bcm, u16 control)
 {
        struct bcm43xx_phyinfo *phy = bcm43xx_current_phy(bcm);
+       u16 ret;
+       unsigned long flags;
 
+       local_irq_save(flags);
        if (phy->connected) {
                bcm43xx_phy_write(bcm, 0x15, 0xE300);
                control <<= 8;
@@ -1430,8 +1433,10 @@ u16 bcm43xx_phy_lo_g_deviation_subval(struct bcm43xx_private *bcm, u16 control)
                bcm43xx_phy_write(bcm, 0x0015, control | 0xFFE0);
                udelay(8);
        }
+       ret = bcm43xx_phy_read(bcm, 0x002D);
+       local_irq_restore(flags);
 
-       return bcm43xx_phy_read(bcm, 0x002D);
+       return ret;
 }
 
 static u32 bcm43xx_phy_lo_g_singledeviation(struct bcm43xx_private *bcm, u16 control)
index e77e9d859f875a90aaefea5892ded253121a1d39..574085c461526927d97a1b896f2dbbb2f4df8b8e 100644 (file)
@@ -264,6 +264,8 @@ static void tx_tasklet(unsigned long d)
 
        bcm43xx_lock_irqonly(bcm, flags);
 
+       if (queue->tx_frozen)
+               goto out_unlock;
        txctl = bcm43xx_pio_read(queue, BCM43xx_PIO_TXCTL);
        if (txctl & BCM43xx_PIO_TXCTL_SUSPEND)
                goto out_unlock;
@@ -633,5 +635,40 @@ void bcm43xx_pio_tx_resume(struct bcm43xx_pioqueue *queue)
                          bcm43xx_pio_read(queue, BCM43xx_PIO_TXCTL)
                          & ~BCM43xx_PIO_TXCTL_SUSPEND);
        bcm43xx_power_saving_ctl_bits(queue->bcm, -1, -1);
-       tasklet_schedule(&queue->txtask);
+       if (!list_empty(&queue->txqueue))
+               tasklet_schedule(&queue->txtask);
+}
+
+void bcm43xx_pio_freeze_txqueues(struct bcm43xx_private *bcm)
+{
+       struct bcm43xx_pio *pio;
+
+       assert(bcm43xx_using_pio(bcm));
+       pio = bcm43xx_current_pio(bcm);
+       pio->queue0->tx_frozen = 1;
+       pio->queue1->tx_frozen = 1;
+       pio->queue2->tx_frozen = 1;
+       pio->queue3->tx_frozen = 1;
 }
+
+void bcm43xx_pio_thaw_txqueues(struct bcm43xx_private *bcm)
+{
+       struct bcm43xx_pio *pio;
+
+       assert(bcm43xx_using_pio(bcm));
+       pio = bcm43xx_current_pio(bcm);
+       pio->queue0->tx_frozen = 0;
+       pio->queue1->tx_frozen = 0;
+       pio->queue2->tx_frozen = 0;
+       pio->queue3->tx_frozen = 0;
+       if (!list_empty(&pio->queue0->txqueue))
+               tasklet_schedule(&pio->queue0->txtask);
+       if (!list_empty(&pio->queue1->txqueue))
+               tasklet_schedule(&pio->queue1->txtask);
+       if (!list_empty(&pio->queue2->txqueue))
+               tasklet_schedule(&pio->queue2->txtask);
+       if (!list_empty(&pio->queue3->txqueue))
+               tasklet_schedule(&pio->queue3->txtask);
+}
+
+
index dfc78209e3a392feb33347f798345c338dc0917c..bc78a3c2cafbe22130a8c66d286cf5f898305ab6 100644 (file)
@@ -54,6 +54,7 @@ struct bcm43xx_pioqueue {
        u16 mmio_base;
 
        u8 tx_suspended:1,
+          tx_frozen:1,
           need_workarounds:1; /* Workarounds needed for core.rev < 3 */
 
        /* Adjusted size of the device internal TX buffer. */
@@ -108,8 +109,12 @@ void bcm43xx_pio_handle_xmitstatus(struct bcm43xx_private *bcm,
                                   struct bcm43xx_xmitstatus *status);
 void bcm43xx_pio_rx(struct bcm43xx_pioqueue *queue);
 
+/* Suspend a TX queue on hardware level. */
 void bcm43xx_pio_tx_suspend(struct bcm43xx_pioqueue *queue);
 void bcm43xx_pio_tx_resume(struct bcm43xx_pioqueue *queue);
+/* Suspend (freeze) the TX tasklet (software level). */
+void bcm43xx_pio_freeze_txqueues(struct bcm43xx_private *bcm);
+void bcm43xx_pio_thaw_txqueues(struct bcm43xx_private *bcm);
 
 #else /* CONFIG_BCM43XX_PIO */
 
@@ -145,6 +150,14 @@ static inline
 void bcm43xx_pio_tx_resume(struct bcm43xx_pioqueue *queue)
 {
 }
+static inline
+void bcm43xx_pio_freeze_txqueues(struct bcm43xx_private *bcm)
+{
+}
+static inline
+void bcm43xx_pio_thaw_txqueues(struct bcm43xx_private *bcm)
+{
+}
 
 #endif /* CONFIG_BCM43XX_PIO */
 #endif /* BCM43xx_PIO_H_ */