From 935809ebb639edadbee0bc04e31e6b36fed33106 Mon Sep 17 00:00:00 2001
From: Felix Fietkau <nbd@openwrt.org>
Date: Wed, 20 Oct 2010 00:46:37 +0000
Subject: [PATCH] ath9k: add a locking fix that might prevent random memory
 corruption during hardware resets

SVN-Revision: 23542
---
 .../patches/530-ath9k_locking_fix.patch       | 237 ++++++++++++++++++
 1 file changed, 237 insertions(+)
 create mode 100644 package/mac80211/patches/530-ath9k_locking_fix.patch

diff --git a/package/mac80211/patches/530-ath9k_locking_fix.patch b/package/mac80211/patches/530-ath9k_locking_fix.patch
new file mode 100644
index 0000000000..fef36ecccd
--- /dev/null
+++ b/package/mac80211/patches/530-ath9k_locking_fix.patch
@@ -0,0 +1,237 @@
+--- a/drivers/net/wireless/ath/ath9k/ath9k.h
++++ b/drivers/net/wireless/ath/ath9k/ath9k.h
+@@ -309,8 +309,8 @@ struct ath_rx {
+ 	u8 rxotherant;
+ 	u32 *rxlink;
+ 	unsigned int rxfilter;
+-	spinlock_t rxflushlock;
+ 	spinlock_t rxbuflock;
++	spinlock_t pcu_lock;
+ 	struct list_head rxbuf;
+ 	struct ath_descdma rxdma;
+ 	struct ath_buf *rx_bufptr;
+--- a/drivers/net/wireless/ath/ath9k/main.c
++++ b/drivers/net/wireless/ath/ath9k/main.c
+@@ -239,6 +239,9 @@ int ath_set_channel(struct ath_softc *sc
+ 	 */
+ 	ath9k_hw_disable_interrupts(ah);
+ 	ath_drain_all_txq(sc, false);
++
++	spin_lock_bh(&sc->rx.pcu_lock);
++
+ 	stopped = ath_stoprecv(sc);
+ 
+ 	/* XXX: do not flush receive queue here. We don't want
+@@ -266,6 +269,7 @@ int ath_set_channel(struct ath_softc *sc
+ 			  "reset status %d\n",
+ 			  channel->center_freq, r);
+ 		spin_unlock_bh(&sc->sc_resetlock);
++		spin_unlock_bh(&sc->rx.pcu_lock);
+ 		goto ps_restore;
+ 	}
+ 	spin_unlock_bh(&sc->sc_resetlock);
+@@ -274,9 +278,12 @@ int ath_set_channel(struct ath_softc *sc
+ 		ath_print(common, ATH_DBG_FATAL,
+ 			  "Unable to restart recv logic\n");
+ 		r = -EIO;
++		spin_unlock_bh(&sc->rx.pcu_lock);
+ 		goto ps_restore;
+ 	}
+ 
++	spin_unlock_bh(&sc->rx.pcu_lock);
++
+ 	ath_update_txpow(sc);
+ 	ath9k_hw_set_interrupts(ah, ah->imask);
+ 
+@@ -610,7 +617,7 @@ void ath9k_tasklet(unsigned long data)
+ 		rxmask = (ATH9K_INT_RX | ATH9K_INT_RXEOL | ATH9K_INT_RXORN);
+ 
+ 	if (status & rxmask) {
+-		spin_lock_bh(&sc->rx.rxflushlock);
++		spin_lock_bh(&sc->rx.pcu_lock);
+ 
+ 		/* Check for high priority Rx first */
+ 		if ((ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) &&
+@@ -618,7 +625,7 @@ void ath9k_tasklet(unsigned long data)
+ 			ath_rx_tasklet(sc, 0, true);
+ 
+ 		ath_rx_tasklet(sc, 0, false);
+-		spin_unlock_bh(&sc->rx.rxflushlock);
++		spin_unlock_bh(&sc->rx.pcu_lock);
+ 	}
+ 
+ 	if (status & ATH9K_INT_TX) {
+@@ -876,6 +883,7 @@ void ath_radio_enable(struct ath_softc *
+ 	if (!ah->curchan)
+ 		ah->curchan = ath_get_curchannel(sc, sc->hw);
+ 
++	spin_lock_bh(&sc->rx.pcu_lock);
+ 	spin_lock_bh(&sc->sc_resetlock);
+ 	r = ath9k_hw_reset(ah, ah->curchan, ah->caldata, false);
+ 	if (r) {
+@@ -890,8 +898,10 @@ void ath_radio_enable(struct ath_softc *
+ 	if (ath_startrecv(sc) != 0) {
+ 		ath_print(common, ATH_DBG_FATAL,
+ 			  "Unable to restart recv logic\n");
++		spin_unlock_bh(&sc->rx.pcu_lock);
+ 		return;
+ 	}
++	spin_unlock_bh(&sc->rx.pcu_lock);
+ 
+ 	if (sc->sc_flags & SC_OP_BEACONS)
+ 		ath_beacon_config(sc, NULL);	/* restart beacons */
+@@ -930,6 +940,9 @@ void ath_radio_disable(struct ath_softc 
+ 	ath9k_hw_disable_interrupts(ah);
+ 
+ 	ath_drain_all_txq(sc, false);	/* clear pending tx frames */
++
++	spin_lock_bh(&sc->rx.pcu_lock);
++
+ 	ath_stoprecv(sc);		/* turn off frame recv */
+ 	ath_flushrecv(sc);		/* flush recv queue */
+ 
+@@ -947,6 +960,9 @@ void ath_radio_disable(struct ath_softc 
+ 	spin_unlock_bh(&sc->sc_resetlock);
+ 
+ 	ath9k_hw_phy_disable(ah);
++
++	spin_unlock_bh(&sc->rx.pcu_lock);
++
+ 	ath9k_hw_configpcipowersave(ah, 1, 1);
+ 	ath9k_ps_restore(sc);
+ 	ath9k_setpower(sc, ATH9K_PM_FULL_SLEEP);
+@@ -966,6 +982,9 @@ int ath_reset(struct ath_softc *sc, bool
+ 
+ 	ath9k_hw_disable_interrupts(ah);
+ 	ath_drain_all_txq(sc, retry_tx);
++
++	spin_lock_bh(&sc->rx.pcu_lock);
++
+ 	ath_stoprecv(sc);
+ 	ath_flushrecv(sc);
+ 
+@@ -980,6 +999,8 @@ int ath_reset(struct ath_softc *sc, bool
+ 		ath_print(common, ATH_DBG_FATAL,
+ 			  "Unable to start recv logic\n");
+ 
++	spin_unlock_bh(&sc->rx.pcu_lock);
++
+ 	/*
+ 	 * We may be doing a reset in response to a request
+ 	 * that changes the channel so update any state that
+@@ -1142,6 +1163,7 @@ static int ath9k_start(struct ieee80211_
+ 	 * be followed by initialization of the appropriate bits
+ 	 * and then setup of the interrupt mask.
+ 	 */
++	spin_lock_bh(&sc->rx.pcu_lock);
+ 	spin_lock_bh(&sc->sc_resetlock);
+ 	r = ath9k_hw_reset(ah, init_channel, ah->caldata, false);
+ 	if (r) {
+@@ -1150,6 +1172,7 @@ static int ath9k_start(struct ieee80211_
+ 			  "(freq %u MHz)\n", r,
+ 			  curchan->center_freq);
+ 		spin_unlock_bh(&sc->sc_resetlock);
++		spin_unlock_bh(&sc->rx.pcu_lock);
+ 		goto mutex_unlock;
+ 	}
+ 	spin_unlock_bh(&sc->sc_resetlock);
+@@ -1171,8 +1194,10 @@ static int ath9k_start(struct ieee80211_
+ 		ath_print(common, ATH_DBG_FATAL,
+ 			  "Unable to start recv logic\n");
+ 		r = -EIO;
++		spin_unlock_bh(&sc->rx.pcu_lock);
+ 		goto mutex_unlock;
+ 	}
++	spin_unlock_bh(&sc->rx.pcu_lock);
+ 
+ 	/* Setup our intr mask. */
+ 	ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL |
+@@ -1371,12 +1396,14 @@ static void ath9k_stop(struct ieee80211_
+ 	 * before setting the invalid flag. */
+ 	ath9k_hw_disable_interrupts(ah);
+ 
++	spin_lock_bh(&sc->rx.pcu_lock);
+ 	if (!(sc->sc_flags & SC_OP_INVALID)) {
+ 		ath_drain_all_txq(sc, false);
+ 		ath_stoprecv(sc);
+ 		ath9k_hw_phy_disable(ah);
+ 	} else
+ 		sc->rx.rxlink = NULL;
++	spin_unlock_bh(&sc->rx.pcu_lock);
+ 
+ 	/* disable HAL and put h/w to sleep */
+ 	ath9k_hw_disable(ah);
+--- a/drivers/net/wireless/ath/ath9k/recv.c
++++ b/drivers/net/wireless/ath/ath9k/recv.c
+@@ -297,19 +297,17 @@ static void ath_edma_start_recv(struct a
+ 	ath_rx_addbuffer_edma(sc, ATH9K_RX_QUEUE_LP,
+ 			      sc->rx.rx_edma[ATH9K_RX_QUEUE_LP].rx_fifo_hwsize);
+ 
+-	spin_unlock_bh(&sc->rx.rxbuflock);
+-
+ 	ath_opmode_init(sc);
+ 
+ 	ath9k_hw_startpcureceive(sc->sc_ah, (sc->sc_flags & SC_OP_OFFCHANNEL));
++
++	spin_unlock_bh(&sc->rx.rxbuflock);
+ }
+ 
+ static void ath_edma_stop_recv(struct ath_softc *sc)
+ {
+-	spin_lock_bh(&sc->rx.rxbuflock);
+ 	ath_rx_remove_buffer(sc, ATH9K_RX_QUEUE_HP);
+ 	ath_rx_remove_buffer(sc, ATH9K_RX_QUEUE_LP);
+-	spin_unlock_bh(&sc->rx.rxbuflock);
+ }
+ 
+ int ath_rx_init(struct ath_softc *sc, int nbufs)
+@@ -319,8 +317,8 @@ int ath_rx_init(struct ath_softc *sc, in
+ 	struct ath_buf *bf;
+ 	int error = 0;
+ 
+-	spin_lock_init(&sc->rx.rxflushlock);
+ 	sc->sc_flags &= ~SC_OP_RXFLUSH;
++	spin_lock_init(&sc->rx.pcu_lock);
+ 	spin_lock_init(&sc->rx.rxbuflock);
+ 
+ 	if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) {
+@@ -506,9 +504,9 @@ int ath_startrecv(struct ath_softc *sc)
+ 	ath9k_hw_rxena(ah);
+ 
+ start_recv:
+-	spin_unlock_bh(&sc->rx.rxbuflock);
+ 	ath_opmode_init(sc);
+ 	ath9k_hw_startpcureceive(ah, (sc->sc_flags & SC_OP_OFFCHANNEL));
++	spin_unlock_bh(&sc->rx.rxbuflock);
+ 
+ 	return 0;
+ }
+@@ -518,6 +516,7 @@ bool ath_stoprecv(struct ath_softc *sc)
+ 	struct ath_hw *ah = sc->sc_ah;
+ 	bool stopped;
+ 
++	spin_lock_bh(&sc->rx.rxbuflock);
+ 	ath9k_hw_stoppcurecv(ah);
+ 	ath9k_hw_setrxfilter(ah, 0);
+ 	stopped = ath9k_hw_stopdmarecv(ah);
+@@ -526,19 +525,18 @@ bool ath_stoprecv(struct ath_softc *sc)
+ 		ath_edma_stop_recv(sc);
+ 	else
+ 		sc->rx.rxlink = NULL;
++	spin_unlock_bh(&sc->rx.rxbuflock);
+ 
+ 	return stopped;
+ }
+ 
+ void ath_flushrecv(struct ath_softc *sc)
+ {
+-	spin_lock_bh(&sc->rx.rxflushlock);
+ 	sc->sc_flags |= SC_OP_RXFLUSH;
+ 	if (sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)
+ 		ath_rx_tasklet(sc, 1, true);
+ 	ath_rx_tasklet(sc, 1, false);
+ 	sc->sc_flags &= ~SC_OP_RXFLUSH;
+-	spin_unlock_bh(&sc->rx.rxflushlock);
+ }
+ 
+ static bool ath_beacon_dtim_pending_cab(struct sk_buff *skb)
-- 
GitLab