ath9k: fix rx descriptor related race condition
authorFelix Fietkau <nbd@openwrt.org>
Sat, 10 Aug 2013 13:59:15 +0000 (15:59 +0200)
committerBen Hutchings <ben@decadent.org.uk>
Sat, 26 Oct 2013 20:05:58 +0000 (21:05 +0100)
commit e96542e55a2aacf4bdeccfe2f17b77c4895b4df2 upstream.

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.

Signed-off-by: Felix Fietkau <nbd@openwrt.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
drivers/net/wireless/ath/ath9k/ath9k.h
drivers/net/wireless/ath/ath9k/recv.c

index 1c269f5..7c70cf2 100644 (file)
@@ -77,10 +77,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
  *
@@ -308,6 +304,7 @@ struct ath_rx {
        struct ath_buf *rx_bufptr;
        struct ath_rx_edma rx_edma[ATH9K_RX_QUEUE_MAX];
 
+       struct ath_buf *buf_hold;
        struct sk_buff *frag;
 };
 
index d171a72..8326c14 100644 (file)
@@ -78,8 +78,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;
@@ -106,6 +104,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 */
@@ -153,7 +159,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);
@@ -492,6 +497,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);
@@ -742,6 +748,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;
 
        /*
@@ -1974,7 +1983,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);