aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net
diff options
context:
space:
mode:
authorFelix Fietkau <nbd@openwrt.org>2010-12-05 14:17:53 -0500
committerJohn W. Linville <linville@tuxdriver.com>2010-12-07 14:57:05 -0500
commit080e1a259acea10b6df8e2a8e49b47481940220a (patch)
tree1e5b3972ea040ed40a255529da5e522a6909d5be /drivers/net
parent841051602e3fa18ea468fe5a177aa92b6eb44b56 (diff)
ath9k: fix a DMA related race condition on reset
When ath_drain_all_txq fails to stop DMA, it issues a hw reset. This reset happens at a very problematic point in time, when the hardware rx path has not been stopped yet. This could lead to memory corruption, hardware hangs or other issues. To fix these issues, simply remove the reset entirely and check the tx DMA stop status to prevent problems with fast channel changes. Signed-off-by: Felix Fietkau <nbd@openwrt.org> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/wireless/ath/ath9k/ath9k.h2
-rw-r--r--drivers/net/wireless/ath/ath9k/main.c5
-rw-r--r--drivers/net/wireless/ath/ath9k/xmit.c22
3 files changed, 10 insertions, 19 deletions
diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h
index 0d0bec3628e..0963071e8f9 100644
--- a/drivers/net/wireless/ath/ath9k/ath9k.h
+++ b/drivers/net/wireless/ath/ath9k/ath9k.h
@@ -329,7 +329,7 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp);
329struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype); 329struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype);
330void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq); 330void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq);
331int ath_tx_setup(struct ath_softc *sc, int haltype); 331int ath_tx_setup(struct ath_softc *sc, int haltype);
332void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx); 332bool ath_drain_all_txq(struct ath_softc *sc, bool retry_tx);
333void ath_draintxq(struct ath_softc *sc, 333void ath_draintxq(struct ath_softc *sc,
334 struct ath_txq *txq, bool retry_tx); 334 struct ath_txq *txq, bool retry_tx);
335void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an); 335void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an);
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index dace215b693..928ef68ab40 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -244,11 +244,12 @@ int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
244 * the relevant bits of the h/w. 244 * the relevant bits of the h/w.
245 */ 245 */
246 ath9k_hw_set_interrupts(ah, 0); 246 ath9k_hw_set_interrupts(ah, 0);
247 ath_drain_all_txq(sc, false); 247 stopped = ath_drain_all_txq(sc, false);
248 248
249 spin_lock_bh(&sc->rx.pcu_lock); 249 spin_lock_bh(&sc->rx.pcu_lock);
250 250
251 stopped = ath_stoprecv(sc); 251 if (!ath_stoprecv(sc))
252 stopped = false;
252 253
253 /* XXX: do not flush receive queue here. We don't want 254 /* XXX: do not flush receive queue here. We don't want
254 * to flush data frames already in queue because of 255 * to flush data frames already in queue because of
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c
index f2ade2402ce..aff04789f79 100644
--- a/drivers/net/wireless/ath/ath9k/xmit.c
+++ b/drivers/net/wireless/ath/ath9k/xmit.c
@@ -1120,7 +1120,7 @@ void ath_draintxq(struct ath_softc *sc, struct ath_txq *txq, bool retry_tx)
1120 } 1120 }
1121} 1121}
1122 1122
1123void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx) 1123bool ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
1124{ 1124{
1125 struct ath_hw *ah = sc->sc_ah; 1125 struct ath_hw *ah = sc->sc_ah;
1126 struct ath_common *common = ath9k_hw_common(sc->sc_ah); 1126 struct ath_common *common = ath9k_hw_common(sc->sc_ah);
@@ -1128,7 +1128,7 @@ void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
1128 int i, npend = 0; 1128 int i, npend = 0;
1129 1129
1130 if (sc->sc_flags & SC_OP_INVALID) 1130 if (sc->sc_flags & SC_OP_INVALID)
1131 return; 1131 return true;
1132 1132
1133 /* Stop beacon queue */ 1133 /* Stop beacon queue */
1134 ath9k_hw_stoptxdma(sc->sc_ah, sc->beacon.beaconq); 1134 ath9k_hw_stoptxdma(sc->sc_ah, sc->beacon.beaconq);
@@ -1142,25 +1142,15 @@ void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
1142 } 1142 }
1143 } 1143 }
1144 1144
1145 if (npend) { 1145 if (npend)
1146 int r; 1146 ath_print(common, ATH_DBG_FATAL, "Failed to stop TX DMA!\n");
1147
1148 ath_print(common, ATH_DBG_FATAL,
1149 "Failed to stop TX DMA. Resetting hardware!\n");
1150
1151 spin_lock_bh(&sc->sc_resetlock);
1152 r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false);
1153 if (r)
1154 ath_print(common, ATH_DBG_FATAL,
1155 "Unable to reset hardware; reset status %d\n",
1156 r);
1157 spin_unlock_bh(&sc->sc_resetlock);
1158 }
1159 1147
1160 for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) { 1148 for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
1161 if (ATH_TXQ_SETUP(sc, i)) 1149 if (ATH_TXQ_SETUP(sc, i))
1162 ath_draintxq(sc, &sc->tx.txq[i], retry_tx); 1150 ath_draintxq(sc, &sc->tx.txq[i], retry_tx);
1163 } 1151 }
1152
1153 return !npend;
1164} 1154}
1165 1155
1166void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq) 1156void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq)