diff options
author | Felix Fietkau <nbd@openwrt.org> | 2011-08-29 18:41:18 +0000 |
---|---|---|
committer | Felix Fietkau <nbd@openwrt.org> | 2011-08-29 18:41:18 +0000 |
commit | d9a80fd97235f90e86d7cb08089e158536869e30 (patch) | |
tree | 699a21ecd648b61b951c1142e123896f6708679f /package/mac80211/patches/582-ath9k_merge_reset_functions.patch | |
parent | 88027313b163121206fd04e136398b1deefcd037 (diff) | |
download | master-187ad058-d9a80fd97235f90e86d7cb08089e158536869e30.tar.gz master-187ad058-d9a80fd97235f90e86d7cb08089e158536869e30.tar.bz2 master-187ad058-d9a80fd97235f90e86d7cb08089e158536869e30.zip |
ath9k: fix a few crash issues on hardware reset
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@28122 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'package/mac80211/patches/582-ath9k_merge_reset_functions.patch')
-rw-r--r-- | package/mac80211/patches/582-ath9k_merge_reset_functions.patch | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/package/mac80211/patches/582-ath9k_merge_reset_functions.patch b/package/mac80211/patches/582-ath9k_merge_reset_functions.patch new file mode 100644 index 0000000000..5484829b16 --- /dev/null +++ b/package/mac80211/patches/582-ath9k_merge_reset_functions.patch @@ -0,0 +1,446 @@ +--- a/drivers/net/wireless/ath/ath9k/main.c ++++ b/drivers/net/wireless/ath/ath9k/main.c +@@ -212,83 +212,57 @@ static int ath_update_survey_stats(struc + return ret; + } + +-/* +- * Set/change channels. If the channel is really being changed, it's done +- * by reseting the chip. To accomplish this we must first cleanup any pending +- * DMA, then restart stuff. +-*/ +-static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, +- struct ath9k_channel *hchan) ++static void __ath_cancel_work(struct ath_softc *sc) + { +- struct ath_hw *ah = sc->sc_ah; +- struct ath_common *common = ath9k_hw_common(ah); +- struct ieee80211_conf *conf = &common->hw->conf; +- bool fastcc = true, stopped; +- struct ieee80211_channel *channel = hw->conf.channel; +- struct ath9k_hw_cal_data *caldata = NULL; +- int r; +- +- if (sc->sc_flags & SC_OP_INVALID) +- return -EIO; +- +- sc->hw_busy_count = 0; +- +- del_timer_sync(&common->ani.timer); + cancel_work_sync(&sc->paprd_work); + cancel_work_sync(&sc->hw_check_work); + cancel_delayed_work_sync(&sc->tx_complete_work); + cancel_delayed_work_sync(&sc->hw_pll_work); ++} + +- ath9k_ps_wakeup(sc); ++static void ath_cancel_work(struct ath_softc *sc) ++{ ++ __ath_cancel_work(sc); ++ cancel_work_sync(&sc->hw_reset_work); ++} + +- spin_lock_bh(&sc->sc_pcu_lock); ++static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush) ++{ ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); ++ bool ret; + +- /* +- * This is only performed if the channel settings have +- * actually changed. +- * +- * To switch channels clear any pending DMA operations; +- * wait long enough for the RX fifo to drain, reset the +- * hardware at the new frequency, and then re-enable +- * the relevant bits of the h/w. +- */ +- ath9k_hw_disable_interrupts(ah); +- stopped = ath_drain_all_txq(sc, false); ++ ieee80211_stop_queues(sc->hw); + +- if (!ath_stoprecv(sc)) +- stopped = false; ++ sc->hw_busy_count = 0; ++ del_timer_sync(&common->ani.timer); + +- if (!ath9k_hw_check_alive(ah)) +- stopped = false; ++ ath9k_hw_disable_interrupts(ah); + +- /* XXX: do not flush receive queue here. We don't want +- * to flush data frames already in queue because of +- * changing channel. */ ++ ret = ath_drain_all_txq(sc, retry_tx); + +- if (!stopped || !(sc->sc_flags & SC_OP_OFFCHANNEL)) +- fastcc = false; ++ if (!ath_stoprecv(sc)) ++ ret = false; + +- if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) +- caldata = &sc->caldata; ++ if (!flush) { ++ if (ah->caps.hw_caps & ATH9K_HW_CAP_EDMA) ++ ath_rx_tasklet(sc, 0, true); ++ ath_rx_tasklet(sc, 0, false); ++ } else { ++ ath_flushrecv(sc); ++ } + +- ath_dbg(common, ATH_DBG_CONFIG, +- "(%u MHz) -> (%u MHz), conf_is_ht40: %d fastcc: %d\n", +- sc->sc_ah->curchan->channel, +- channel->center_freq, conf_is_ht40(conf), +- fastcc); ++ return ret; ++} + +- r = ath9k_hw_reset(ah, hchan, caldata, fastcc); +- if (r) { +- ath_err(common, +- "Unable to reset channel (%u MHz), reset status %d\n", +- channel->center_freq, r); +- goto ps_restore; +- } ++static bool ath_complete_reset(struct ath_softc *sc, bool start) ++{ ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); + + if (ath_startrecv(sc) != 0) { + ath_err(common, "Unable to restart recv logic\n"); +- r = -EIO; +- goto ps_restore; ++ return false; + } + + ath9k_cmn_update_txpow(ah, sc->curtxpow, +@@ -296,21 +270,93 @@ static int ath_set_channel(struct ath_so + ath9k_hw_set_interrupts(ah, ah->imask); + ath9k_hw_enable_interrupts(ah); + +- if (!(sc->sc_flags & (SC_OP_OFFCHANNEL))) { ++ if (!(sc->sc_flags & (SC_OP_OFFCHANNEL)) && start) { + if (sc->sc_flags & SC_OP_BEACONS) + ath_set_beacon(sc); ++ + ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0); + ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, HZ/2); + if (!common->disable_ani) + ath_start_ani(common); + } + +- ps_restore: +- ieee80211_wake_queues(hw); ++ ieee80211_wake_queues(sc->hw); ++ ++ return true; ++} ++ ++static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan, ++ bool retry_tx) ++{ ++ struct ath_hw *ah = sc->sc_ah; ++ struct ath_common *common = ath9k_hw_common(ah); ++ struct ath9k_hw_cal_data *caldata = NULL; ++ bool fastcc = true; ++ bool flush = false; ++ int r; ++ ++ __ath_cancel_work(sc); ++ ++ spin_lock_bh(&sc->sc_pcu_lock); + ++ if (!(sc->sc_flags & SC_OP_OFFCHANNEL)) { ++ fastcc = false; ++ caldata = &sc->caldata; ++ } ++ ++ if (!hchan) { ++ fastcc = false; ++ flush = true; ++ hchan = ah->curchan; ++ } ++ ++ if (fastcc && !ath9k_hw_check_alive(ah)) ++ fastcc = false; ++ ++ if (!ath_prepare_reset(sc, retry_tx, flush)) ++ fastcc = false; ++ ++ ath_dbg(common, ATH_DBG_CONFIG, ++ "Reset to %u MHz, HT40: %d fastcc: %d\n", ++ hchan->channel, !!(hchan->channelFlags & (CHANNEL_HT40MINUS | ++ CHANNEL_HT40PLUS)), ++ fastcc); ++ ++ r = ath9k_hw_reset(ah, hchan, caldata, fastcc); ++ if (r) { ++ ath_err(common, ++ "Unable to reset channel, reset status %d\n", r); ++ goto out; ++ } ++ ++ if (!ath_complete_reset(sc, true)) ++ r = -EIO; ++ ++out: + spin_unlock_bh(&sc->sc_pcu_lock); ++ return 0; ++} ++ ++ ++/* ++ * Set/change channels. If the channel is really being changed, it's done ++ * by reseting the chip. To accomplish this we must first cleanup any pending ++ * DMA, then restart stuff. ++*/ ++static int ath_set_channel(struct ath_softc *sc, struct ieee80211_hw *hw, ++ struct ath9k_channel *hchan) ++{ ++ int r; ++ ++ if (sc->sc_flags & SC_OP_INVALID) ++ return -EIO; ++ ++ ath9k_ps_wakeup(sc); ++ ++ r = ath_reset_internal(sc, hchan, false); + + ath9k_ps_restore(sc); ++ + return r; + } + +@@ -823,28 +869,13 @@ static void ath_radio_enable(struct ath_ + channel->center_freq, r); + } + +- ath9k_cmn_update_txpow(ah, sc->curtxpow, +- sc->config.txpowlimit, &sc->curtxpow); +- if (ath_startrecv(sc) != 0) { +- ath_err(common, "Unable to restart recv logic\n"); +- goto out; +- } +- if (sc->sc_flags & SC_OP_BEACONS) +- ath_set_beacon(sc); /* restart beacons */ +- +- /* Re-Enable interrupts */ +- ath9k_hw_set_interrupts(ah, ah->imask); +- ath9k_hw_enable_interrupts(ah); ++ ath_complete_reset(sc, true); + + /* Enable LED */ + ath9k_hw_cfg_output(ah, ah->led_pin, + AR_GPIO_OUTPUT_MUX_AS_OUTPUT); + ath9k_hw_set_gpio(ah, ah->led_pin, 0); + +- ieee80211_wake_queues(hw); +- ieee80211_queue_delayed_work(hw, &sc->hw_pll_work, HZ/2); +- +-out: + spin_unlock_bh(&sc->sc_pcu_lock); + + ath9k_ps_restore(sc); +@@ -857,11 +888,10 @@ void ath_radio_disable(struct ath_softc + int r; + + ath9k_ps_wakeup(sc); +- cancel_delayed_work_sync(&sc->hw_pll_work); + +- spin_lock_bh(&sc->sc_pcu_lock); ++ ath_cancel_work(sc); + +- ieee80211_stop_queues(hw); ++ spin_lock_bh(&sc->sc_pcu_lock); + + /* + * Keep the LED on when the radio is disabled +@@ -872,13 +902,7 @@ void ath_radio_disable(struct ath_softc + ath9k_hw_cfg_gpio_input(ah, ah->led_pin); + } + +- /* Disable interrupts */ +- ath9k_hw_disable_interrupts(ah); +- +- ath_drain_all_txq(sc, false); /* clear pending tx frames */ +- +- ath_stoprecv(sc); /* turn off frame recv */ +- ath_flushrecv(sc); /* flush recv queue */ ++ ath_prepare_reset(sc, false, true); + + if (!ah->curchan) + ah->curchan = ath9k_cmn_get_curchannel(hw, ah); +@@ -900,48 +924,11 @@ void ath_radio_disable(struct ath_softc + + static int ath_reset(struct ath_softc *sc, bool retry_tx) + { +- struct ath_hw *ah = sc->sc_ah; +- struct ath_common *common = ath9k_hw_common(ah); +- struct ieee80211_hw *hw = sc->hw; + int r; + +- sc->hw_busy_count = 0; +- +- /* Stop ANI */ +- +- del_timer_sync(&common->ani.timer); +- + ath9k_ps_wakeup(sc); + +- ieee80211_stop_queues(hw); +- +- ath9k_hw_disable_interrupts(ah); +- ath_drain_all_txq(sc, retry_tx); +- +- ath_stoprecv(sc); +- ath_flushrecv(sc); +- +- r = ath9k_hw_reset(ah, sc->sc_ah->curchan, ah->caldata, false); +- if (r) +- ath_err(common, +- "Unable to reset hardware; reset status %d\n", r); +- +- if (ath_startrecv(sc) != 0) +- ath_err(common, "Unable to start recv logic\n"); +- +- /* +- * We may be doing a reset in response to a request +- * that changes the channel so update any state that +- * might change as a result. +- */ +- ath9k_cmn_update_txpow(ah, sc->curtxpow, +- sc->config.txpowlimit, &sc->curtxpow); +- +- if ((sc->sc_flags & SC_OP_BEACONS) || !(sc->sc_flags & (SC_OP_OFFCHANNEL))) +- ath_set_beacon(sc); /* restart beacons */ +- +- ath9k_hw_set_interrupts(ah, ah->imask); +- ath9k_hw_enable_interrupts(ah); ++ r = ath_reset_internal(sc, NULL, retry_tx); + + if (retry_tx) { + int i; +@@ -954,12 +941,6 @@ static int ath_reset(struct ath_softc *s + } + } + +- ieee80211_wake_queues(hw); +- +- /* Start ANI */ +- if (!common->disable_ani) +- ath_start_ani(common); +- + ath9k_ps_restore(sc); + + return r; +@@ -969,9 +950,7 @@ void ath_reset_work(struct work_struct * + { + struct ath_softc *sc = container_of(work, struct ath_softc, hw_check_work); + +- spin_lock_bh(&sc->sc_pcu_lock); + ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); + } + + void ath_hw_check(struct work_struct *work) +@@ -992,11 +971,8 @@ void ath_hw_check(struct work_struct *wo + ath_dbg(common, ATH_DBG_RESET, "Possible baseband hang, " + "busy=%d (try %d)\n", busy, sc->hw_busy_count + 1); + if (busy >= 99) { +- if (++sc->hw_busy_count >= 3) { +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); +- } ++ if (++sc->hw_busy_count >= 3) ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + + } else if (busy >= 0) + sc->hw_busy_count = 0; +@@ -1016,9 +992,7 @@ static void ath_hw_pll_rx_hang_check(str + /* Rx is hung for more than 500ms. Reset it */ + ath_dbg(common, ATH_DBG_RESET, + "Possible RX hang, resetting"); +- spin_lock_bh(&sc->sc_pcu_lock); +- ath_reset(sc, true); +- spin_unlock_bh(&sc->sc_pcu_lock); ++ ieee80211_queue_work(sc->hw, &sc->hw_reset_work); + count = 0; + } + } else +@@ -1089,28 +1063,6 @@ static int ath9k_start(struct ieee80211_ + goto mutex_unlock; + } + +- /* +- * This is needed only to setup initial state +- * but it's best done after a reset. +- */ +- ath9k_cmn_update_txpow(ah, sc->curtxpow, +- sc->config.txpowlimit, &sc->curtxpow); +- +- /* +- * Setup the hardware after reset: +- * The receive engine is set going. +- * Frame transmit is handled entirely +- * in the frame output path; there's nothing to do +- * here except setup the interrupt mask. +- */ +- if (ath_startrecv(sc) != 0) { +- ath_err(common, "Unable to start recv logic\n"); +- r = -EIO; +- spin_unlock_bh(&sc->sc_pcu_lock); +- goto mutex_unlock; +- } +- spin_unlock_bh(&sc->sc_pcu_lock); +- + /* Setup our intr mask. */ + ah->imask = ATH9K_INT_TX | ATH9K_INT_RXEOL | + ATH9K_INT_RXORN | ATH9K_INT_FATAL | +@@ -1133,12 +1085,14 @@ static int ath9k_start(struct ieee80211_ + + /* Disable BMISS interrupt when we're not associated */ + ah->imask &= ~(ATH9K_INT_SWBA | ATH9K_INT_BMISS); +- ath9k_hw_set_interrupts(ah, ah->imask); +- ath9k_hw_enable_interrupts(ah); + +- ieee80211_wake_queues(hw); ++ if (!ath_complete_reset(sc, false)) { ++ r = -EIO; ++ spin_unlock_bh(&sc->sc_pcu_lock); ++ goto mutex_unlock; ++ } + +- ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0); ++ spin_unlock_bh(&sc->sc_pcu_lock); + + if ((ah->btcoex_hw.scheme != ATH_BTCOEX_CFG_NONE) && + !ah->btcoex_hw.enabled) { +@@ -1231,10 +1185,7 @@ static void ath9k_stop(struct ieee80211_ + + mutex_lock(&sc->mutex); + +- cancel_delayed_work_sync(&sc->tx_complete_work); +- cancel_delayed_work_sync(&sc->hw_pll_work); +- cancel_work_sync(&sc->paprd_work); +- cancel_work_sync(&sc->hw_check_work); ++ ath_cancel_work(sc); + + if (sc->sc_flags & SC_OP_INVALID) { + ath_dbg(common, ATH_DBG_ANY, "Device not present\n"); +@@ -2351,9 +2302,11 @@ static void ath9k_flush(struct ieee80211 + ath9k_ps_wakeup(sc); + spin_lock_bh(&sc->sc_pcu_lock); + drain_txq = ath_drain_all_txq(sc, false); ++ spin_unlock_bh(&sc->sc_pcu_lock); ++ + if (!drain_txq) + ath_reset(sc, false); +- spin_unlock_bh(&sc->sc_pcu_lock); ++ + ath9k_ps_restore(sc); + ieee80211_wake_queues(hw); + |