ath9k: fix rx descriptor related race condition
authorFelix Fietkau <nbd@openwrt.org>
Sat, 10 Aug 2013 13:59:15 +0000 (15:59 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Thu, 15 Aug 2013 20:07:56 +0000 (16:07 -0400)
Similar to a race condition that exists in the tx path, the hardware
might re-read the 'next' pointer of a descriptor of the last completed
frame. This only affects non-EDMA (pre-AR93xx) devices.

To deal with this race, defer clearing and re-linking a completed rx
descriptor until the next one has been processed.

Cc: stable@vger.kernel.org
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/ath/ath9k/ath9k.h
drivers/net/wireless/ath/ath9k/recv.c

index 505c615..0d69b13 100644 (file)
@@ -79,10 +79,6 @@ struct ath_config {
                       sizeof(struct ath_buf_state));           \
        } while (0)
 
-#define ATH_RXBUF_RESET(_bf) do {              \
-               (_bf)->bf_stale = false;        \
-       } while (0)
-
 /**
  * enum buffer_type - Buffer type flags
  *
@@ -315,6 +311,7 @@ struct ath_rx {
        struct ath_descdma rxdma;
        struct ath_rx_edma rx_edma[ATH9K_RX_QUEUE_MAX];
 
+       struct ath_buf *buf_hold;
        struct sk_buff *frag;
 
        u32 ampdu_ref;
index 62dff97..2dd851a 100644 (file)
@@ -42,8 +42,6 @@ static void ath_rx_buf_link(struct ath_softc *sc, struct ath_buf *bf)
        struct ath_desc *ds;
        struct sk_buff *skb;
 
-       ATH_RXBUF_RESET(bf);
-
        ds = bf->bf_desc;
        ds->ds_link = 0; /* link to null */
        ds->ds_data = bf->bf_buf_addr;
@@ -70,6 +68,14 @@ static void ath_rx_buf_link(struct ath_softc *sc, struct ath_buf *bf)
        sc->rx.rxlink = &ds->ds_link;
 }
 
+static void ath_rx_buf_relink(struct ath_softc *sc, struct ath_buf *bf)
+{
+       if (sc->rx.buf_hold)
+               ath_rx_buf_link(sc, sc->rx.buf_hold);
+
+       sc->rx.buf_hold = bf;
+}
+
 static void ath_setdefantenna(struct ath_softc *sc, u32 antenna)
 {
        /* XXX block beacon interrupts */
@@ -117,7 +123,6 @@ static bool ath_rx_edma_buf_link(struct ath_softc *sc,
 
        skb = bf->bf_mpdu;
 
-       ATH_RXBUF_RESET(bf);
        memset(skb->data, 0, ah->caps.rx_status_len);
        dma_sync_single_for_device(sc->dev, bf->bf_buf_addr,
                                ah->caps.rx_status_len, DMA_TO_DEVICE);
@@ -432,6 +437,7 @@ int ath_startrecv(struct ath_softc *sc)
        if (list_empty(&sc->rx.rxbuf))
                goto start_recv;
 
+       sc->rx.buf_hold = NULL;
        sc->rx.rxlink = NULL;
        list_for_each_entry_safe(bf, tbf, &sc->rx.rxbuf, list) {
                ath_rx_buf_link(sc, bf);
@@ -677,6 +683,9 @@ static struct ath_buf *ath_get_next_rx_buf(struct ath_softc *sc,
        }
 
        bf = list_first_entry(&sc->rx.rxbuf, struct ath_buf, list);
+       if (bf == sc->rx.buf_hold)
+               return NULL;
+
        ds = bf->bf_desc;
 
        /*
@@ -1387,7 +1396,7 @@ requeue:
                if (edma) {
                        ath_rx_edma_buf_link(sc, qtype);
                } else {
-                       ath_rx_buf_link(sc, bf);
+                       ath_rx_buf_relink(sc, bf);
                        ath9k_hw_rxena(ah);
                }
        } while (1);