CHROMUMOS: ath9k: detect rx stuck by cycle counters
authorPaul Stewart <pstew@chromium.org>
Thu, 8 Mar 2012 01:21:32 +0000 (01:21 +0000)
committerOlof Johansson <olof@lixom.net>
Fri, 1 Jun 2012 07:02:28 +0000 (00:02 -0700)
And recover chip from the rx stuck.

This is a workaround from Atheros for a receive hang in their part.
It uses various status monitoring to detect that the receiver has
failed, and resets the chip.  There is activity still afoot to find
the root cause to this problem, and this change is only meant to
alleviate the immediate problem, since throughput still suffers
before the part enters this state.

Signed-off-by: Paul Stewart <pstew@chromium.org>
BUG=chrome-os-partner:4886
TEST=Manual: Bench-top re-enactment of congested environment + in-field
testing with client machines

Change-Id: I7acfe51ca56195cf8f201446580dee9ae9804bac
Reviewed-on: https://gerrit.chromium.org/gerrit/15861
Reviewed-by: Gary Morain <gmorain@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>

drivers/net/wireless/ath/ath.h
drivers/net/wireless/ath/ath9k/ani.c
drivers/net/wireless/ath/ath9k/ani.h
drivers/net/wireless/ath/ath9k/ar9003_phy.c
drivers/net/wireless/ath/ath9k/ath9k.h
drivers/net/wireless/ath/ath9k/hw.h
drivers/net/wireless/ath/ath9k/init.c
drivers/net/wireless/ath/ath9k/main.c
drivers/net/wireless/ath/ath9k/recv.c
drivers/net/wireless/ath/hw.c

index fa90d05..ea657c9 100644 (file)
@@ -150,6 +150,7 @@ struct ath_common {
        spinlock_t cc_lock;
        struct ath_cycle_counters cc_ani;
        struct ath_cycle_counters cc_survey;
+       struct ath_cycle_counters cc_rxpoll;
 
        struct ath_regulatory regulatory;
        struct ath_regulatory reg_world_copy;
index 7e0ea4e..62cffb0 100644 (file)
@@ -43,11 +43,11 @@ static const struct ani_ofdm_level_entry ofdm_level_table[] = {
        {  2,  2,  1  }, /* lvl 2 */
        {  3,  2,  1  }, /* lvl 3  (default) */
        {  4,  3,  1  }, /* lvl 4 */
-       {  5,  4,  1  }, /* lvl 5 */
-       {  6,  5,  1  }, /* lvl 6 */
-       {  7,  6,  1  }, /* lvl 7 */
-       {  7,  7,  1  }, /* lvl 8 */
-       {  7,  8,  0  }  /* lvl 9 */
+       {  4,  3,  0  }, /* lvl 5 */
+       {  5,  4,  0  }, /* lvl 6 */
+       {  6,  5,  0  }, /* lvl 7 */
+       {  7,  6,  0  }, /* lvl 8 */
+       {  7,  7,  0  }, /* lvl 9 */
 };
 #define ATH9K_ANI_OFDM_NUM_LEVEL \
        ARRAY_SIZE(ofdm_level_table)
@@ -290,20 +290,12 @@ static void ath9k_hw_set_ofdm_nil(struct ath_hw *ah, u8 immunityLevel)
                                     ATH9K_ANI_FIRSTEP_LEVEL,
                                     entry_ofdm->fir_step_level);
 
-       if ((ah->opmode != NL80211_IFTYPE_STATION &&
-            ah->opmode != NL80211_IFTYPE_ADHOC) ||
-           aniState->noiseFloor <= aniState->rssiThrHigh) {
-               if (aniState->ofdmWeakSigDetectOff)
-                       /* force on ofdm weak sig detect */
-                       ath9k_hw_ani_control(ah,
-                               ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
-                                            true);
-               else if (aniState->ofdmWeakSigDetectOff ==
-                        entry_ofdm->ofdm_weak_signal_on)
-                       ath9k_hw_ani_control(ah,
+       if ((aniState->noiseFloor >= aniState->rssiThrHigh) &&
+           (!aniState->ofdmWeakSigDetectOff !=
+                       entry_ofdm->ofdm_weak_signal_on))
+               ath9k_hw_ani_control(ah,
                                ATH9K_ANI_OFDM_WEAK_SIGNAL_DETECTION,
                                entry_ofdm->ofdm_weak_signal_on);
-       }
 }
 
 static void ath9k_hw_ani_ofdm_err_trigger(struct ath_hw *ah)
@@ -651,6 +643,7 @@ static bool ath9k_hw_ani_read_counters(struct ath_hw *ah)
        }
 
        aniState->listenTime += listenTime;
+       aniState->totallistenTime += listenTime;
 
        ath9k_hw_update_mibstats(ah, &ah->ah_mibStats);
 
@@ -711,18 +704,22 @@ void ath9k_hw_ani_monitor(struct ath_hw *ah, struct ath9k_channel *chan)
                         aniState->listenTime;
 
        ath_dbg(common, ANI,
-               "listenTime=%d OFDM:%d errs=%d/s CCK:%d errs=%d/s ofdm_turn=%d\n",
+               "listenTime=%d totallistenTime= %d OFDM:%d errs=%d/s "
+               "CCK:%d errs=%d/s ofdm_turn=%d\n",
                aniState->listenTime,
+               aniState->totallistenTime,
                aniState->ofdmNoiseImmunityLevel,
                ofdmPhyErrRate, aniState->cckNoiseImmunityLevel,
                cckPhyErrRate, aniState->ofdmsTurn);
 
-       if (aniState->listenTime > 5 * ah->aniperiod) {
+       if ((aniState->totallistenTime > 5 * ah->aniperiod) &&
+           (aniState->totallistenTime != aniState->listenTime)) {
                if (ofdmPhyErrRate <= ah->config.ofdm_trig_low &&
                    cckPhyErrRate <= ah->config.cck_trig_low) {
                        ath9k_hw_ani_lower_immunity(ah);
                        aniState->ofdmsTurn = !aniState->ofdmsTurn;
                }
+               aniState->totallistenTime = 0;
                ath9k_ani_restart(ah);
        } else if (aniState->listenTime > ah->aniperiod) {
                /* check to see if need to raise immunity */
index 83029d6..f998973 100644 (file)
@@ -124,6 +124,7 @@ struct ar5416AniState {
        u8 cckWeakSigThreshold;
        bool update_ani;
        u32 listenTime;
+       u32 totallistenTime;
        int32_t rssiThrLow;
        int32_t rssiThrHigh;
        u32 noiseFloor;
index 60032b2..32b176b 100644 (file)
@@ -823,56 +823,6 @@ static bool ar9003_hw_ani_control(struct ath_hw *ah,
                 * on == 0 means more noise imm
                 */
                u32 on = param ? 1 : 0;
-               /*
-                * make register setting for default
-                * (weak sig detect ON) come from INI file
-                */
-               int m1ThreshLow = on ?
-                       aniState->iniDef.m1ThreshLow : m1ThreshLow_off;
-               int m2ThreshLow = on ?
-                       aniState->iniDef.m2ThreshLow : m2ThreshLow_off;
-               int m1Thresh = on ?
-                       aniState->iniDef.m1Thresh : m1Thresh_off;
-               int m2Thresh = on ?
-                       aniState->iniDef.m2Thresh : m2Thresh_off;
-               int m2CountThr = on ?
-                       aniState->iniDef.m2CountThr : m2CountThr_off;
-               int m2CountThrLow = on ?
-                       aniState->iniDef.m2CountThrLow : m2CountThrLow_off;
-               int m1ThreshLowExt = on ?
-                       aniState->iniDef.m1ThreshLowExt : m1ThreshLowExt_off;
-               int m2ThreshLowExt = on ?
-                       aniState->iniDef.m2ThreshLowExt : m2ThreshLowExt_off;
-               int m1ThreshExt = on ?
-                       aniState->iniDef.m1ThreshExt : m1ThreshExt_off;
-               int m2ThreshExt = on ?
-                       aniState->iniDef.m2ThreshExt : m2ThreshExt_off;
-
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
-                             AR_PHY_SFCORR_LOW_M1_THRESH_LOW,
-                             m1ThreshLow);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
-                             AR_PHY_SFCORR_LOW_M2_THRESH_LOW,
-                             m2ThreshLow);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR,
-                             AR_PHY_SFCORR_M1_THRESH, m1Thresh);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR,
-                             AR_PHY_SFCORR_M2_THRESH, m2Thresh);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR,
-                             AR_PHY_SFCORR_M2COUNT_THR, m2CountThr);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_LOW,
-                             AR_PHY_SFCORR_LOW_M2COUNT_THR_LOW,
-                             m2CountThrLow);
-
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
-                             AR_PHY_SFCORR_EXT_M1_THRESH_LOW, m1ThreshLowExt);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
-                             AR_PHY_SFCORR_EXT_M2_THRESH_LOW, m2ThreshLowExt);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
-                             AR_PHY_SFCORR_EXT_M1_THRESH, m1ThreshExt);
-               REG_RMW_FIELD(ah, AR_PHY_SFCORR_EXT,
-                             AR_PHY_SFCORR_EXT_M2_THRESH, m2ThreshExt);
-
                if (on)
                        REG_SET_BIT(ah, AR_PHY_SFCORR_LOW,
                                    AR_PHY_SFCORR_LOW_USE_SELF_CORR_LOW);
index 8c84049..08fe6a9 100644 (file)
@@ -430,6 +430,7 @@ void ath9k_set_beaconing_status(struct ath_softc *sc, bool status);
 void ath_reset_work(struct work_struct *work);
 void ath_hw_check(struct work_struct *work);
 void ath_hw_pll_work(struct work_struct *work);
+void ath_rx_poll_work(unsigned long data);
 void ath_paprd_calibrate(struct work_struct *work);
 void ath_ani_calibrate(unsigned long data);
 void ath_start_ani(struct ath_common *common);
@@ -670,6 +671,7 @@ struct ath_softc {
        struct ath_beacon_config cur_beacon_conf;
        struct delayed_work tx_complete_work;
        struct delayed_work hw_pll_work;
+       struct timer_list rx_poll_timer;
 
 #ifdef CONFIG_ATH9K_BTCOEX_SUPPORT
        struct ath_btcoex btcoex;
@@ -680,6 +682,7 @@ struct ath_softc {
 
        struct ath_ant_comb ant_comb;
        u8 ant_tx, ant_rx;
+       atomic_t stop_rx_poll;
 };
 
 void ath9k_tasklet(unsigned long data);
index e88f182..ab24137 100644 (file)
@@ -1003,6 +1003,7 @@ void ar9003_hw_bb_watchdog_config(struct ath_hw *ah);
 void ar9003_hw_bb_watchdog_read(struct ath_hw *ah);
 void ar9003_hw_bb_watchdog_dbg_info(struct ath_hw *ah);
 void ar9003_hw_disable_phy_restart(struct ath_hw *ah);
+void ar9003_hw_dump_ani_reg(struct ath_hw *ah);
 void ar9003_paprd_enable(struct ath_hw *ah, bool val);
 void ar9003_paprd_populate_single_table(struct ath_hw *ah,
                                        struct ath9k_hw_cal_data *caldata,
@@ -1026,6 +1027,8 @@ void ar9002_hw_attach_ops(struct ath_hw *ah);
 void ar9003_hw_attach_ops(struct ath_hw *ah);
 
 void ar9002_hw_load_ani_reg(struct ath_hw *ah, struct ath9k_channel *chan);
+void ar9003_hw_dump_txdesc(struct ath_hw *ah);
+bool ath9k_hw_detect_mac_hang(struct ath_hw *ah);
 /*
  * ANI work can be shared between all families but a next
  * generation implementation of ANI will be used only for AR9003 only
index cb00645..c9da964 100644 (file)
@@ -760,6 +760,7 @@ int ath9k_init_device(u16 devid, struct ath_softc *sc,
        INIT_WORK(&sc->hw_check_work, ath_hw_check);
        INIT_WORK(&sc->paprd_work, ath_paprd_calibrate);
        INIT_DELAYED_WORK(&sc->hw_pll_work, ath_hw_pll_work);
+       setup_timer(&sc->rx_poll_timer, ath_rx_poll_work, (unsigned long)sc);
 
        /* Register with mac80211 */
        error = ieee80211_register_hw(hw);
index 32c2247..aaa1be8 100644 (file)
@@ -219,8 +219,19 @@ static int ath_update_survey_stats(struct ath_softc *sc)
 
 static void __ath_cancel_work(struct ath_softc *sc)
 {
+       struct ath_hw *ah = sc->sc_ah;
+       struct ath_common *common = ath9k_hw_common(ah);
+
+       if (sc->sc_flags & SC_OP_INVALID)
+               return -EIO;
+
+       sc->hw_busy_count = 0;
+
+       del_timer_sync(&common->ani.timer);
+       del_timer_sync(&sc->rx_poll_timer);
        cancel_work_sync(&sc->paprd_work);
        cancel_work_sync(&sc->hw_check_work);
+       cancel_work_sync(&sc->hw_reset_work);
        cancel_delayed_work_sync(&sc->tx_complete_work);
        cancel_delayed_work_sync(&sc->hw_pll_work);
 }
@@ -228,7 +239,6 @@ static void __ath_cancel_work(struct ath_softc *sc)
 static void ath_cancel_work(struct ath_softc *sc)
 {
        __ath_cancel_work(sc);
-       cancel_work_sync(&sc->hw_reset_work);
 }
 
 static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush)
@@ -280,6 +290,9 @@ static bool ath_complete_reset(struct ath_softc *sc, bool start)
                if (sc->sc_flags & SC_OP_BEACONS)
                        ath_set_beacon(sc);
 
+               if (sc->sc_flags & SC_OP_PRIM_STA_VIF)
+                       mod_timer(&sc->rx_poll_timer,
+                                 jiffies + msecs_to_jiffies(10));
                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)
@@ -709,6 +722,7 @@ void ath_rx_poll_work(unsigned long data)
        ath_dbg(common, RX_STUCK, "IMR %08x IER %08x intr_cnt %d\n",
                REG_READ(ah, AR_IMR), REG_READ(ah, AR_IER),
                atomic_read(&ah->intr_ref_cnt));
+
        ar9003_hw_dump_txdesc(ah);
 
        REG_SET_BIT(ah, AR_DIAG_SW, 0x8080000);
@@ -1143,9 +1157,47 @@ static int ath_reset(struct ath_softc *sc, bool retry_tx)
 {
        int r;
 
+       sc->hw_busy_count = 0;
+
+       ath9k_debug_samp_bb_mac(sc);
+       /* Stop ANI */
+       del_timer_sync(&common->ani.timer);
+       del_timer_sync(&sc->rx_poll_timer);
+
        ath9k_ps_wakeup(sc);
 
-       r = ath_reset_internal(sc, NULL, retry_tx);
+       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 */
+
+       if (sc->sc_flags & SC_OP_PRIM_STA_VIF)
+               mod_timer(&sc->rx_poll_timer, jiffies + msecs_to_jiffies(300));
+
+       ath9k_hw_set_interrupts(ah, ah->imask);
+       ath9k_hw_enable_interrupts(ah);
 
        if (retry_tx) {
                int i;
@@ -2178,6 +2230,9 @@ static void ath9k_bss_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
                if (!common->disable_ani) {
                        sc->sc_flags |= SC_OP_ANI_RUN;
                        ath_start_ani(common);
+                       atomic_set(&sc->stop_rx_poll, 0);
+                       mod_timer(&sc->rx_poll_timer,
+                                 jiffies + msecs_to_jiffies(300));
                }
 
        }
@@ -2214,6 +2269,7 @@ static void ath9k_config_bss(struct ath_softc *sc, struct ieee80211_vif *vif)
                /* Stop ANI */
                sc->sc_flags &= ~SC_OP_ANI_RUN;
                del_timer_sync(&common->ani.timer);
+               del_timer_sync(&sc->rx_poll_timer);
                memset(&sc->caldata, 0, sizeof(sc->caldata));
        }
 }
index 1c4583c..e5d2fb0 100644 (file)
@@ -1841,6 +1841,12 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp)
 
                memset(rxs, 0, sizeof(struct ieee80211_rx_status));
 
+               if (rs.is_mybeacon) {
+                       atomic_set(&sc->stop_rx_poll, 1);
+                       mod_timer(&sc->rx_poll_timer,
+                                 jiffies + msecs_to_jiffies(300));
+               }
+
                rxs->mactime = (tsf & ~0xffffffffULL) | rs.rs_tstamp;
                if (rs.rs_tstamp > tsf_lower &&
                    unlikely(rs.rs_tstamp - tsf_lower > 0x10000000))
index 19befb3..f1821ea 100644 (file)
@@ -166,6 +166,11 @@ void ath_hw_cycle_counters_update(struct ath_common *common)
        common->cc_survey.rx_busy += busy;
        common->cc_survey.rx_frame += rx;
        common->cc_survey.tx_frame += tx;
+
+       common->cc_rxpoll.cycles += cycles;
+       common->cc_rxpoll.rx_busy += busy;
+       common->cc_rxpoll.rx_frame += rx;
+       common->cc_rxpoll.tx_frame += tx;
 }
 EXPORT_SYMBOL(ath_hw_cycle_counters_update);