diff options
author | Rajkumar Manoharan <rmanohar@qca.qualcomm.com> | 2012-07-17 07:46:42 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2012-07-17 15:11:40 -0400 |
commit | 124b979baeb2d7a0593be8d392f43725578478c1 (patch) | |
tree | 3b6f3d1d1915f3dba8f2405a8e79f1bf7958b22e /drivers | |
parent | 6dcc344469d60a1f0d72cc638967e8c83c6e166e (diff) |
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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/wireless/ath/ath9k/ath9k.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/beacon.c | 5 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/debug.h | 24 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/link.c | 13 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/main.c | 17 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/mci.c | 2 | ||||
-rw-r--r-- | drivers/net/wireless/ath/ath9k/xmit.c | 13 |
7 files changed, 41 insertions, 34 deletions
diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h index c8af0db97c4f..b09285c36c4a 100644 --- a/drivers/net/wireless/ath/ath9k/ath9k.h +++ b/drivers/net/wireless/ath/ath9k/ath9k.h | |||
@@ -452,6 +452,7 @@ void ath_stop_ani(struct ath_softc *sc); | |||
452 | void ath_check_ani(struct ath_softc *sc); | 452 | void ath_check_ani(struct ath_softc *sc); |
453 | int ath_update_survey_stats(struct ath_softc *sc); | 453 | int ath_update_survey_stats(struct ath_softc *sc); |
454 | void ath_update_survey_nf(struct ath_softc *sc, int channel); | 454 | void ath_update_survey_nf(struct ath_softc *sc, int channel); |
455 | void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type); | ||
455 | 456 | ||
456 | /**********/ | 457 | /**********/ |
457 | /* BTCOEX */ | 458 | /* BTCOEX */ |
diff --git a/drivers/net/wireless/ath/ath9k/beacon.c b/drivers/net/wireless/ath/ath9k/beacon.c index 006ae99d2f59..76f07d8c272d 100644 --- a/drivers/net/wireless/ath/ath9k/beacon.c +++ b/drivers/net/wireless/ath/ath9k/beacon.c | |||
@@ -317,11 +317,12 @@ void ath9k_beacon_tasklet(unsigned long data) | |||
317 | bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); | 317 | bool edma = !!(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA); |
318 | int slot; | 318 | int slot; |
319 | 319 | ||
320 | if (work_pending(&sc->hw_reset_work)) { | 320 | if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) { |
321 | ath_dbg(common, RESET, | 321 | ath_dbg(common, RESET, |
322 | "reset work is pending, skip beaconing now\n"); | 322 | "reset work is pending, skip beaconing now\n"); |
323 | return; | 323 | return; |
324 | } | 324 | } |
325 | |||
325 | /* | 326 | /* |
326 | * Check if the previous beacon has gone out. If | 327 | * Check if the previous beacon has gone out. If |
327 | * not don't try to post another, skip this period | 328 | * not don't try to post another, skip this period |
@@ -345,7 +346,7 @@ void ath9k_beacon_tasklet(unsigned long data) | |||
345 | } else if (sc->beacon.bmisscnt >= BSTUCK_THRESH) { | 346 | } else if (sc->beacon.bmisscnt >= BSTUCK_THRESH) { |
346 | ath_dbg(common, BSTUCK, "beacon is officially stuck\n"); | 347 | ath_dbg(common, BSTUCK, "beacon is officially stuck\n"); |
347 | sc->beacon.bmisscnt = 0; | 348 | sc->beacon.bmisscnt = 0; |
348 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | 349 | ath9k_queue_reset(sc, RESET_TYPE_BEACON_STUCK); |
349 | } | 350 | } |
350 | 351 | ||
351 | return; | 352 | return; |
diff --git a/drivers/net/wireless/ath/ath9k/debug.h b/drivers/net/wireless/ath/ath9k/debug.h index d0f851cea43a..8b9d080d89da 100644 --- a/drivers/net/wireless/ath/ath9k/debug.h +++ b/drivers/net/wireless/ath/ath9k/debug.h | |||
@@ -32,6 +32,19 @@ struct ath_buf; | |||
32 | #define RESET_STAT_INC(sc, type) do { } while (0) | 32 | #define RESET_STAT_INC(sc, type) do { } while (0) |
33 | #endif | 33 | #endif |
34 | 34 | ||
35 | enum ath_reset_type { | ||
36 | RESET_TYPE_BB_HANG, | ||
37 | RESET_TYPE_BB_WATCHDOG, | ||
38 | RESET_TYPE_FATAL_INT, | ||
39 | RESET_TYPE_TX_ERROR, | ||
40 | RESET_TYPE_TX_HANG, | ||
41 | RESET_TYPE_PLL_HANG, | ||
42 | RESET_TYPE_MAC_HANG, | ||
43 | RESET_TYPE_BEACON_STUCK, | ||
44 | RESET_TYPE_MCI, | ||
45 | __RESET_TYPE_MAX | ||
46 | }; | ||
47 | |||
35 | #ifdef CONFIG_ATH9K_DEBUGFS | 48 | #ifdef CONFIG_ATH9K_DEBUGFS |
36 | 49 | ||
37 | /** | 50 | /** |
@@ -209,17 +222,6 @@ struct ath_rx_stats { | |||
209 | u32 rx_frags; | 222 | u32 rx_frags; |
210 | }; | 223 | }; |
211 | 224 | ||
212 | enum ath_reset_type { | ||
213 | RESET_TYPE_BB_HANG, | ||
214 | RESET_TYPE_BB_WATCHDOG, | ||
215 | RESET_TYPE_FATAL_INT, | ||
216 | RESET_TYPE_TX_ERROR, | ||
217 | RESET_TYPE_TX_HANG, | ||
218 | RESET_TYPE_PLL_HANG, | ||
219 | RESET_TYPE_MAC_HANG, | ||
220 | __RESET_TYPE_MAX | ||
221 | }; | ||
222 | |||
223 | struct ath_stats { | 225 | struct ath_stats { |
224 | struct ath_interrupt_stats istats; | 226 | struct ath_interrupt_stats istats; |
225 | struct ath_tx_stats txstats[ATH9K_NUM_TX_QUEUES]; | 227 | struct ath_tx_stats txstats[ATH9K_NUM_TX_QUEUES]; |
diff --git a/drivers/net/wireless/ath/ath9k/link.c b/drivers/net/wireless/ath/ath9k/link.c index 42fc0a374c61..d4549e9aac5c 100644 --- a/drivers/net/wireless/ath/ath9k/link.c +++ b/drivers/net/wireless/ath/ath9k/link.c | |||
@@ -50,8 +50,7 @@ void ath_tx_complete_poll_work(struct work_struct *work) | |||
50 | if (needreset) { | 50 | if (needreset) { |
51 | ath_dbg(ath9k_hw_common(sc->sc_ah), RESET, | 51 | ath_dbg(ath9k_hw_common(sc->sc_ah), RESET, |
52 | "tx hung, resetting the chip\n"); | 52 | "tx hung, resetting the chip\n"); |
53 | RESET_STAT_INC(sc, RESET_TYPE_TX_HANG); | 53 | ath9k_queue_reset(sc, RESET_TYPE_TX_HANG); |
54 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | ||
55 | return; | 54 | return; |
56 | } | 55 | } |
57 | 56 | ||
@@ -69,6 +68,7 @@ void ath_hw_check(struct work_struct *work) | |||
69 | unsigned long flags; | 68 | unsigned long flags; |
70 | int busy; | 69 | int busy; |
71 | u8 is_alive, nbeacon = 1; | 70 | u8 is_alive, nbeacon = 1; |
71 | enum ath_reset_type type; | ||
72 | 72 | ||
73 | ath9k_ps_wakeup(sc); | 73 | ath9k_ps_wakeup(sc); |
74 | is_alive = ath9k_hw_check_alive(sc->sc_ah); | 74 | is_alive = ath9k_hw_check_alive(sc->sc_ah); |
@@ -78,7 +78,7 @@ void ath_hw_check(struct work_struct *work) | |||
78 | else if (!is_alive && AR_SREV_9300(sc->sc_ah)) { | 78 | else if (!is_alive && AR_SREV_9300(sc->sc_ah)) { |
79 | ath_dbg(common, RESET, | 79 | ath_dbg(common, RESET, |
80 | "DCU stuck is detected. Schedule chip reset\n"); | 80 | "DCU stuck is detected. Schedule chip reset\n"); |
81 | RESET_STAT_INC(sc, RESET_TYPE_MAC_HANG); | 81 | type = RESET_TYPE_MAC_HANG; |
82 | goto sched_reset; | 82 | goto sched_reset; |
83 | } | 83 | } |
84 | 84 | ||
@@ -90,7 +90,7 @@ void ath_hw_check(struct work_struct *work) | |||
90 | busy, sc->hw_busy_count + 1); | 90 | busy, sc->hw_busy_count + 1); |
91 | if (busy >= 99) { | 91 | if (busy >= 99) { |
92 | if (++sc->hw_busy_count >= 3) { | 92 | if (++sc->hw_busy_count >= 3) { |
93 | RESET_STAT_INC(sc, RESET_TYPE_BB_HANG); | 93 | type = RESET_TYPE_BB_HANG; |
94 | goto sched_reset; | 94 | goto sched_reset; |
95 | } | 95 | } |
96 | } else if (busy >= 0) { | 96 | } else if (busy >= 0) { |
@@ -102,7 +102,7 @@ void ath_hw_check(struct work_struct *work) | |||
102 | goto out; | 102 | goto out; |
103 | 103 | ||
104 | sched_reset: | 104 | sched_reset: |
105 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | 105 | ath9k_queue_reset(sc, type); |
106 | out: | 106 | out: |
107 | ath9k_ps_restore(sc); | 107 | ath9k_ps_restore(sc); |
108 | } | 108 | } |
@@ -119,8 +119,7 @@ static bool ath_hw_pll_rx_hang_check(struct ath_softc *sc, u32 pll_sqsum) | |||
119 | count++; | 119 | count++; |
120 | if (count == 3) { | 120 | if (count == 3) { |
121 | ath_dbg(common, RESET, "PLL WAR, resetting the chip\n"); | 121 | ath_dbg(common, RESET, "PLL WAR, resetting the chip\n"); |
122 | RESET_STAT_INC(sc, RESET_TYPE_PLL_HANG); | 122 | ath9k_queue_reset(sc, RESET_TYPE_PLL_HANG); |
123 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | ||
124 | count = 0; | 123 | count = 0; |
125 | return true; | 124 | return true; |
126 | } | 125 | } |
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index 1caaf922ba9c..6049d8b82855 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c | |||
@@ -363,6 +363,7 @@ void ath9k_tasklet(unsigned long data) | |||
363 | struct ath_softc *sc = (struct ath_softc *)data; | 363 | struct ath_softc *sc = (struct ath_softc *)data; |
364 | struct ath_hw *ah = sc->sc_ah; | 364 | struct ath_hw *ah = sc->sc_ah; |
365 | struct ath_common *common = ath9k_hw_common(ah); | 365 | struct ath_common *common = ath9k_hw_common(ah); |
366 | enum ath_reset_type type; | ||
366 | unsigned long flags; | 367 | unsigned long flags; |
367 | u32 status = sc->intrstatus; | 368 | u32 status = sc->intrstatus; |
368 | u32 rxmask; | 369 | u32 rxmask; |
@@ -372,18 +373,13 @@ void ath9k_tasklet(unsigned long data) | |||
372 | 373 | ||
373 | if ((status & ATH9K_INT_FATAL) || | 374 | if ((status & ATH9K_INT_FATAL) || |
374 | (status & ATH9K_INT_BB_WATCHDOG)) { | 375 | (status & ATH9K_INT_BB_WATCHDOG)) { |
375 | #ifdef CONFIG_ATH9K_DEBUGFS | ||
376 | enum ath_reset_type type; | ||
377 | 376 | ||
378 | if (status & ATH9K_INT_FATAL) | 377 | if (status & ATH9K_INT_FATAL) |
379 | type = RESET_TYPE_FATAL_INT; | 378 | type = RESET_TYPE_FATAL_INT; |
380 | else | 379 | else |
381 | type = RESET_TYPE_BB_WATCHDOG; | 380 | type = RESET_TYPE_BB_WATCHDOG; |
382 | 381 | ||
383 | RESET_STAT_INC(sc, type); | 382 | ath9k_queue_reset(sc, type); |
384 | #endif | ||
385 | set_bit(SC_OP_HW_RESET, &sc->sc_flags); | ||
386 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | ||
387 | goto out; | 383 | goto out; |
388 | } | 384 | } |
389 | 385 | ||
@@ -584,6 +580,15 @@ static int ath_reset(struct ath_softc *sc, bool retry_tx) | |||
584 | return r; | 580 | return r; |
585 | } | 581 | } |
586 | 582 | ||
583 | void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type) | ||
584 | { | ||
585 | #ifdef CONFIG_ATH9K_DEBUGFS | ||
586 | RESET_STAT_INC(sc, type); | ||
587 | #endif | ||
588 | set_bit(SC_OP_HW_RESET, &sc->sc_flags); | ||
589 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | ||
590 | } | ||
591 | |||
587 | void ath_reset_work(struct work_struct *work) | 592 | void ath_reset_work(struct work_struct *work) |
588 | { | 593 | { |
589 | struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work); | 594 | struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work); |
diff --git a/drivers/net/wireless/ath/ath9k/mci.c b/drivers/net/wireless/ath/ath9k/mci.c index 87acff7fdaae..fb536e7e661b 100644 --- a/drivers/net/wireless/ath/ath9k/mci.c +++ b/drivers/net/wireless/ath/ath9k/mci.c | |||
@@ -202,7 +202,7 @@ static void ath_mci_cal_msg(struct ath_softc *sc, u8 opcode, u8 *rx_payload) | |||
202 | case MCI_GPM_BT_CAL_REQ: | 202 | case MCI_GPM_BT_CAL_REQ: |
203 | if (mci_hw->bt_state == MCI_BT_AWAKE) { | 203 | if (mci_hw->bt_state == MCI_BT_AWAKE) { |
204 | ar9003_mci_state(ah, MCI_STATE_SET_BT_CAL_START); | 204 | ar9003_mci_state(ah, MCI_STATE_SET_BT_CAL_START); |
205 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | 205 | ath9k_queue_reset(sc, RESET_TYPE_MCI); |
206 | } | 206 | } |
207 | ath_dbg(common, MCI, "MCI State : %d\n", mci_hw->bt_state); | 207 | ath_dbg(common, MCI, "MCI State : %d\n", mci_hw->bt_state); |
208 | break; | 208 | break; |
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c index 310c95e33cb1..2c9da6b2ecb1 100644 --- a/drivers/net/wireless/ath/ath9k/xmit.c +++ b/drivers/net/wireless/ath/ath9k/xmit.c | |||
@@ -589,10 +589,8 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq, | |||
589 | 589 | ||
590 | rcu_read_unlock(); | 590 | rcu_read_unlock(); |
591 | 591 | ||
592 | if (needreset) { | 592 | if (needreset) |
593 | RESET_STAT_INC(sc, RESET_TYPE_TX_ERROR); | 593 | ath9k_queue_reset(sc, RESET_TYPE_TX_ERROR); |
594 | ieee80211_queue_work(sc->hw, &sc->hw_reset_work); | ||
595 | } | ||
596 | } | 594 | } |
597 | 595 | ||
598 | static bool ath_lookup_legacy(struct ath_buf *bf) | 596 | 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 | struct ath_atx_ac *ac, *ac_tmp, *last_ac; | 1587 | struct ath_atx_ac *ac, *ac_tmp, *last_ac; |
1590 | struct ath_atx_tid *tid, *last_tid; | 1588 | struct ath_atx_tid *tid, *last_tid; |
1591 | 1589 | ||
1592 | if (work_pending(&sc->hw_reset_work) || list_empty(&txq->axq_acq) || | 1590 | if (test_bit(SC_OP_HW_RESET, &sc->sc_flags) || |
1591 | list_empty(&txq->axq_acq) || | ||
1593 | txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) | 1592 | txq->axq_ampdu_depth >= ATH_AGGR_MIN_QDEPTH) |
1594 | return; | 1593 | return; |
1595 | 1594 | ||
@@ -2196,7 +2195,7 @@ static void ath_tx_processq(struct ath_softc *sc, struct ath_txq *txq) | |||
2196 | 2195 | ||
2197 | ath_txq_lock(sc, txq); | 2196 | ath_txq_lock(sc, txq); |
2198 | for (;;) { | 2197 | for (;;) { |
2199 | if (work_pending(&sc->hw_reset_work)) | 2198 | if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) |
2200 | break; | 2199 | break; |
2201 | 2200 | ||
2202 | if (list_empty(&txq->axq_q)) { | 2201 | if (list_empty(&txq->axq_q)) { |
@@ -2279,7 +2278,7 @@ void ath_tx_edma_tasklet(struct ath_softc *sc) | |||
2279 | int status; | 2278 | int status; |
2280 | 2279 | ||
2281 | for (;;) { | 2280 | for (;;) { |
2282 | if (work_pending(&sc->hw_reset_work)) | 2281 | if (test_bit(SC_OP_HW_RESET, &sc->sc_flags)) |
2283 | break; | 2282 | break; |
2284 | 2283 | ||
2285 | status = ath9k_hw_txprocdesc(ah, NULL, (void *)&ts); | 2284 | status = ath9k_hw_txprocdesc(ah, NULL, (void *)&ts); |