b43: Work around mac80211 race condition
authorLarry Finger <Larry.Finger@lwfinger.net>
Wed, 29 Jul 2009 15:54:06 +0000 (10:54 -0500)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 4 Aug 2009 20:43:26 +0000 (16:43 -0400)
As shown in http://thread.gmane.org/gmane.linux.kernel.wireless.general/36497,
mac80211 has a bug that allows a call to the TX routine after the queues have
been stopped. This situation will only occur under extreme stress. Although
b43 does not crash when this condition occurs, it does generate a WARN_ON and
also logs a queue overrun message. This patch recognizes b43 is not at fault
and logs a message only when the most verbose debugging mode is enabled. In
the unlikely event that the queue is not stopped when the DMA queue becomes
full, then a warning is issued.

During testing of this patch with one output stream running repeated tcpperf
writes and a second running a flood ping, this routine was entered with
the DMA ring stopped about once per hour. The condition where the DMA queue is
full but the ring has not been stopped has never been seen by me.

Signed-off-by: Larry Finger <Larry.Finger@lwfinger.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/b43/dma.c

index 7964cc32b258bb80599e54be2e1c6c478f356119..41a0e9c2b339d9043d0136adfa479fc7ebf17d3a 100644 (file)
@@ -1334,13 +1334,22 @@ int b43_dma_tx(struct b43_wldev *dev, struct sk_buff *skb)
        spin_lock_irqsave(&ring->lock, flags);
 
        B43_WARN_ON(!ring->tx);
-       /* Check if the queue was stopped in mac80211,
-        * but we got called nevertheless.
-        * That would be a mac80211 bug. */
-       B43_WARN_ON(ring->stopped);
 
-       if (unlikely(free_slots(ring) < TX_SLOTS_PER_FRAME)) {
-               b43warn(dev->wl, "DMA queue overflow\n");
+       if (unlikely(ring->stopped)) {
+               /* We get here only because of a bug in mac80211.
+                * Because of a race, one packet may be queued after
+                * the queue is stopped, thus we got called when we shouldn't.
+                * For now, just refuse the transmit. */
+               if (b43_debug(dev, B43_DBG_DMAVERBOSE))
+                       b43err(dev->wl, "Packet after queue stopped\n");
+               err = -ENOSPC;
+               goto out_unlock;
+       }
+
+       if (unlikely(WARN_ON(free_slots(ring) < TX_SLOTS_PER_FRAME))) {
+               /* If we get here, we have a real error with the queue
+                * full, but queues not stopped. */
+               b43err(dev->wl, "DMA queue overflow\n");
                err = -ENOSPC;
                goto out_unlock;
        }