Commit 124b979b authored by Rajkumar Manoharan's avatar Rajkumar Manoharan Committed by John W. Linville

ath9k: Fix race in reset-work usage

Using work_pending() to defer certain operations when
a HW-reset work has been queued is racy since the check
would return false when the work item is actually in
execution. Use SC_OP_HW_RESET instead to fix this race.
Also, unify the reset debug statistics maintenance.
Signed-off-by: default avatarRajkumar Manoharan <rmanohar@qca.qualcomm.com>
Signed-off-by: default avatarSujith Manoharan <c_manoha@qca.qualcomm.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 6dcc3444
...@@ -452,6 +452,7 @@ void ath_stop_ani(struct ath_softc *sc); ...@@ -452,6 +452,7 @@ void ath_stop_ani(struct ath_softc *sc);
void ath_check_ani(struct ath_softc *sc); void ath_check_ani(struct ath_softc *sc);
int ath_update_survey_stats(struct ath_softc *sc); int ath_update_survey_stats(struct ath_softc *sc);
void ath_update_survey_nf(struct ath_softc *sc, int channel); void ath_update_survey_nf(struct ath_softc *sc, int channel);
void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type);
/**********/ /**********/
/* BTCOEX */ /* BTCOEX */
......
...@@ -317,11 +317,12 @@ void ath9k_beacon_tasklet(unsigned long data) ...@@ -317,11 +317,12 @@ void ath9k_beacon_tasklet(unsigned long data)
bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA);
int slot; int slot;
if (work_pending(&sc->hw_reset_work)) { if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) {
ath_dbg(common, RESET, ath_dbg(common, RESET,
"reset work is pending, skip beaconing now\n"); "reset work is pending, skip beaconing now\n");
return; return;
} }
/* /*
* Check if the previous beacon has gone out. If * Check if the previous beacon has gone out. If
* not don't try to post another, skip this period * not don't try to post another, skip this period
...@@ -345,7 +346,7 @@ void ath9k_beacon_tasklet(unsigned long data) ...@@ -345,7 +346,7 @@ void ath9k_beacon_tasklet(unsigned long data)
} else if (sc->beacon.bmisscnt >= BSTUCK_THRESH) { } else if (sc->beacon.bmisscnt >= BSTUCK_THRESH) {
ath_dbg(common, BSTUCK, "beacon is officially stuck\n"); ath_dbg(common, BSTUCK, "beacon is officially stuck\n");
sc->beacon.bmisscnt = 0; sc->beacon.bmisscnt = 0;
ieee80211_queue_work(sc->hw, &sc->hw_reset_work); ath9k_queue_reset(sc, RESET_TYPE_BEACON_STUCK);
} }
return; return;
......
...@@ -32,6 +32,19 @@ struct ath_buf; ...@@ -32,6 +32,19 @@ struct ath_buf;
#define RESET_STAT_INC(sc, type) do { } while (0) #define RESET_STAT_INC(sc, type) do { } while (0)
#endif #endif
enum ath_reset_type {
RESET_TYPE_BB_HANG,
RESET_TYPE_BB_WATCHDOG,
RESET_TYPE_FATAL_INT,
RESET_TYPE_TX_ERROR,
RESET_TYPE_TX_HANG,
RESET_TYPE_PLL_HANG,
RESET_TYPE_MAC_HANG,
RESET_TYPE_BEACON_STUCK,
RESET_TYPE_MCI,
__RESET_TYPE_MAX
};
#ifdef CONFIG_ATH9K_DEBUGFS #ifdef CONFIG_ATH9K_DEBUGFS
/** /**
...@@ -209,17 +222,6 @@ struct ath_rx_stats { ...@@ -209,17 +222,6 @@ struct ath_rx_stats {
u32 rx_frags; u32 rx_frags;
}; };
enum ath_reset_type {
RESET_TYPE_BB_HANG,
RESET_TYPE_BB_WATCHDOG,
RESET_TYPE_FATAL_INT,
RESET_TYPE_TX_ERROR,
RESET_TYPE_TX_HANG,
RESET_TYPE_PLL_HANG,
RESET_TYPE_MAC_HANG,
__RESET_TYPE_MAX
};
struct ath_stats { struct ath_stats {
struct ath_interrupt_stats istats; struct ath_interrupt_stats istats;
struct ath_tx_stats txstats[ATH9K_NUM_TX_QUEUES]; struct ath_tx_stats txstats[ATH9K_NUM_TX_QUEUES];
......
...@@ -50,8 +50,7 @@ void ath_tx_complete_poll_work(struct work_struct *work) ...@@ -50,8 +50,7 @@ void ath_tx_complete_poll_work(struct work_struct *work)
if (needreset) { if (needreset) {
ath_dbg(ath9k_hw_common(sc->sc_ah), RESET, ath_dbg(ath9k_hw_common(sc->sc_ah), RESET,
"tx hung, resetting the chip\n"); "tx hung, resetting the chip\n");
RESET_STAT_INC(sc, RESET_TYPE_TX_HANG); ath9k_queue_reset(sc, RESET_TYPE_TX_HANG);
ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
return; return;
} }
...@@ -69,6 +68,7 @@ void ath_hw_check(struct work_struct *work) ...@@ -69,6 +68,7 @@ void ath_hw_check(struct work_struct *work)
unsigned long flags; unsigned long flags;
int busy; int busy;
u8 is_alive, nbeacon = 1; u8 is_alive, nbeacon = 1;
enum ath_reset_type type;
ath9k_ps_wakeup(sc); ath9k_ps_wakeup(sc);
is_alive = ath9k_hw_check_alive(sc->sc_ah); is_alive = ath9k_hw_check_alive(sc->sc_ah);
...@@ -78,7 +78,7 @@ void ath_hw_check(struct work_struct *work) ...@@ -78,7 +78,7 @@ void ath_hw_check(struct work_struct *work)
else if (!is_alive && AR_SREV_9300(sc->sc_ah)) { else if (!is_alive && AR_SREV_9300(sc->sc_ah)) {
ath_dbg(common, RESET, ath_dbg(common, RESET,
"DCU stuck is detected. Schedule chip reset\n"); "DCU stuck is detected. Schedule chip reset\n");
RESET_STAT_INC(sc, RESET_TYPE_MAC_HANG); type = RESET_TYPE_MAC_HANG;
goto sched_reset; goto sched_reset;
} }
...@@ -90,7 +90,7 @@ void ath_hw_check(struct work_struct *work) ...@@ -90,7 +90,7 @@ void ath_hw_check(struct work_struct *work)
busy, sc->hw_busy_count + 1); busy, sc->hw_busy_count + 1);
if (busy >= 99) { if (busy >= 99) {
if (++sc->hw_busy_count >= 3) { if (++sc->hw_busy_count >= 3) {
RESET_STAT_INC(sc, RESET_TYPE_BB_HANG); type = RESET_TYPE_BB_HANG;
goto sched_reset; goto sched_reset;
} }
} else if (busy >= 0) { } else if (busy >= 0) {
...@@ -102,7 +102,7 @@ void ath_hw_check(struct work_struct *work) ...@@ -102,7 +102,7 @@ void ath_hw_check(struct work_struct *work)
goto out; goto out;
sched_reset: sched_reset:
ieee80211_queue_work(sc->hw, &sc->hw_reset_work); ath9k_queue_reset(sc, type);
out: out:
ath9k_ps_restore(sc); ath9k_ps_restore(sc);
} }
...@@ -119,8 +119,7 @@ static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum) ...@@ -119,8 +119,7 @@ static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum)
count++; count++;
if (count == 3) { if (count == 3) {
ath_dbg(common, RESET, "PLL WAR, resetting the chip\n"); ath_dbg(common, RESET, "PLL WAR, resetting the chip\n");
RESET_STAT_INC(sc, RESET_TYPE_PLL_HANG); ath9k_queue_reset(sc, RESET_TYPE_PLL_HANG);
ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
count = 0; count = 0;
return true; return true;
} }
......
...@@ -363,6 +363,7 @@ void ath9k_tasklet(unsigned long data) ...@@ -363,6 +363,7 @@ void ath9k_tasklet(unsigned long data)
struct ath_softc *sc = (struct ath_softc *)data; struct ath_softc *sc = (struct ath_softc *)data;
struct ath_hw *ah = sc->sc_ah; struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(ah); struct ath_common *common = ath9k_hw_common(ah);
enum ath_reset_type type;
unsigned long flags; unsigned long flags;
u32 status = sc->intrstatus; u32 status = sc->intrstatus;
u32 rxmask; u32 rxmask;
...@@ -372,18 +373,13 @@ void ath9k_tasklet(unsigned long data) ...@@ -372,18 +373,13 @@ void ath9k_tasklet(unsigned long data)
if ((status & ATH9K_INT_FATAL) || if ((status & ATH9K_INT_FATAL) ||
(status & ATH9K_INT_BB_WATCHDOG)) { (status & ATH9K_INT_BB_WATCHDOG)) {
#ifdef CONFIG_ATH9K_DEBUGFS
enum ath_reset_type type;
if (status & ATH9K_INT_FATAL) if (status & ATH9K_INT_FATAL)
type = RESET_TYPE_FATAL_INT; type = RESET_TYPE_FATAL_INT;
else else
type = RESET_TYPE_BB_WATCHDOG; type = RESET_TYPE_BB_WATCHDOG;
RESET_STAT_INC(sc, type); ath9k_queue_reset(sc, type);
#endif
set_bit(SC_OP_HW_RESET, &sc->sc_flags);
ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
goto out; goto out;
} }
...@@ -584,6 +580,15 @@ static int ath_reset(struct ath_softc *sc, bool retry_tx) ...@@ -584,6 +580,15 @@ static int ath_reset(struct ath_softc *sc, bool retry_tx)
return r; return r;
} }
void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type)
{
#ifdef CONFIG_ATH9K_DEBUGFS
RESET_STAT_INC(sc, type);
#endif
set_bit(SC_OP_HW_RESET, &sc->sc_flags);
ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
}
void ath_reset_work(struct work_struct *work) void ath_reset_work(struct work_struct *work)
{ {
struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work); struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work);
......
...@@ -202,7 +202,7 @@ static void ath_mci_cal_msg(struct ath_softc *sc, u8 opcode, u8 *rx_payload) ...@@ -202,7 +202,7 @@ static void ath_mci_cal_msg(struct ath_softc *sc, u8 opcode, u8 *rx_payload)
case MCI_GPM_BT_CAL_REQ: case MCI_GPM_BT_CAL_REQ:
if (mci_hw->bt_state == MCI_BT_AWAKE) { if (mci_hw->bt_state == MCI_BT_AWAKE) {
ar9003_mci_state(ah, MCI_STATE_SET_BT_CAL_START); ar9003_mci_state(ah, MCI_STATE_SET_BT_CAL_START);
ieee80211_queue_work(sc->hw, &sc->hw_reset_work); ath9k_queue_reset(sc, RESET_TYPE_MCI);
} }
ath_dbg(common, MCI, "MCI State : %d\n", mci_hw->bt_state); ath_dbg(common, MCI, "MCI State : %d\n", mci_hw->bt_state);
break; break;
......
...@@ -589,10 +589,8 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq, ...@@ -589,10 +589,8 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
rcu_read_unlock(); rcu_read_unlock();
if (needreset) { if (needreset)
RESET_STAT_INC(sc, RESET_TYPE_TX_ERROR); ath9k_queue_reset(sc, RESET_TYPE_TX_ERROR);
ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
}
} }
static bool ath_lookup_legacy(struct ath_buf *bf) static bool ath_lookup_legacy(struct ath_buf *bf)
...@@ -1589,7 +1587,8 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq) ...@@ -1589,7 +1587,8 @@ void ath_txq_schedule(struct ath_softc *sc, struct ath_txq *txq)
struct ath_atx_ac *ac, *ac_tmp, *last_ac; struct ath_atx_ac *ac, *ac_tmp, *last_ac;
struct ath_atx_tid *tid, *last_tid; struct ath_atx_tid *tid, *last_tid;
if (work_pending(&sc->hw_reset_work) || list_empty(&txq->axq_acq) || if (test_bit(SC_OP_HW_RESET, &sc->sc_flags) ||
list_empty(&txq->axq_acq) ||
txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH)
return; return;
...@@ -2196,7 +2195,7 @@ static void ath_tx_processq(struct ath_softc *sc, struct ath_txq *txq) ...@@ -2196,7 +2195,7 @@ static void ath_tx_processq(struct ath_softc *sc, struct ath_txq *txq)
ath_txq_lock(sc, txq); ath_txq_lock(sc, txq);
for (;;) { for (;;) {
if (work_pending(&sc->hw_reset_work)) if (test_bit(SC_OP_HW_RESET, &sc->sc_flags))
break; break;
if (list_empty(&txq->axq_q)) { if (list_empty(&txq->axq_q)) {
...@@ -2279,7 +2278,7 @@ void ath_tx_edma_tasklet(struct ath_softc *sc) ...@@ -2279,7 +2278,7 @@ void ath_tx_edma_tasklet(struct ath_softc *sc)
int status; int status;
for (;;) { for (;;) {
if (work_pending(&sc->hw_reset_work)) if (test_bit(SC_OP_HW_RESET, &sc->sc_flags))
break; break;
status = ath9k_hw_txprocdesc(ah, NULL, (void *)&ts); status = ath9k_hw_txprocdesc(ah, NULL, (void *)&ts);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment