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: Rajkumar Manoharan <rmanohar@qca.qualcomm.com> Signed-off-by: Sujith Manoharan <c_manoha@qca.qualcomm.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
6dcc344469
commit
124b979bae
7 changed files with 41 additions and 34 deletions
|
@ -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)
|
||||||
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)
|
||||||
} 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;
|
||||||
#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 {
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
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,
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
|
|
||||||
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)
|
||||||
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);
|
||||||
|
|
Loading…
Reference in a new issue