Skip to content

Commit

Permalink
ath9k: fix a DMA related race condition on reset
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Felix Fietkau authored and John W. Linville committed Dec 7, 2010
1 parent 8410516 commit 080e1a2
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 19 deletions.
2 changes: 1 addition & 1 deletion drivers/net/wireless/ath/ath9k/ath9k.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp);
struct ath_txq *ath_txq_setup(struct ath_softc *sc, int qtype, int subtype);
void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq);
int ath_tx_setup(struct ath_softc *sc, int haltype);
void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx);
bool ath_drain_all_txq(struct ath_softc *sc, bool retry_tx);
void ath_draintxq(struct ath_softc *sc,
struct ath_txq *txq, bool retry_tx);
void ath_tx_node_init(struct ath_softc *sc, struct ath_node *an);
Expand Down
5 changes: 3 additions & 2 deletions drivers/net/wireless/ath/ath9k/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,12 @@ int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw,
* the relevant bits of the h/w.
*/
ath9k_hw_set_interrupts(ah, 0);
ath_drain_all_txq(sc, false);
stopped = ath_drain_all_txq(sc, false);

spin_lock_bh(&sc->rx.pcu_lock);

stopped = ath_stoprecv(sc);
if (!ath_stoprecv(sc))
stopped = false;

/* XXX: do not flush receive queue here. We don't want
* to flush data frames already in queue because of
Expand Down
22 changes: 6 additions & 16 deletions drivers/net/wireless/ath/ath9k/xmit.c
Original file line number Diff line number Diff line change
Expand Up @@ -1120,15 +1120,15 @@ void ath_draintxq(struct ath_softc *sc, struct ath_txq *txq, bool retry_tx)
}
}

void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
bool ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
{
struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_txq *txq;
int i, npend = 0;

if (sc->sc_flags & SC_OP_INVALID)
return;
return true;

/* Stop beacon queue */
ath9k_hw_stoptxdma(sc->sc_ah, sc->beacon.beaconq);
Expand All @@ -1142,25 +1142,15 @@ void ath_drain_all_txq(struct ath_softc *sc, bool retry_tx)
}
}

if (npend) {
int r;

ath_print(common, ATH_DBG_FATAL,
"Failed to stop TX DMA. Resetting hardware!\n");

spin_lock_bh(&sc->sc_resetlock);
r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false);
if (r)
ath_print(common, ATH_DBG_FATAL,
"Unable to reset hardware; reset status %d\n",
r);
spin_unlock_bh(&sc->sc_resetlock);
}
if (npend)
ath_print(common, ATH_DBG_FATAL, "Failed to stop TX DMA!\n");

for (i = 0; i < ATH9K_NUM_TX_QUEUES; i++) {
if (ATH_TXQ_SETUP(sc, i))
ath_draintxq(sc, &sc->tx.txq[i], retry_tx);
}

return !npend;
}

void ath_tx_cleanupq(struct ath_softc *sc, struct ath_txq *txq)
Expand Down

0 comments on commit 080e1a2

Please sign in to comment.