ath9k: recover ar9380 chips from rare stuck state
authorRajkumar Manoharan <rmanohar@qca.qualcomm.com>
Thu, 15 Mar 2012 00:04:27 +0000 (05:34 +0530)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 9 Apr 2012 20:05:53 +0000 (16:05 -0400)
In the experiment with Azimuth ADEPT-n testbed where the APs transmit
power was reduced to 25% and the signal strength was futher attenuated
by 20dB and induced a path loss of ~7dB, the station was reporting
beacon losses and the following issue were observed.

* rx clear is stuck at low for more than 300ms
* dcu chain and complete state is stuck at one of the hang signature

This patch triggers the hang detection logic that recovers the chip
from any of the above conditions. As the issue was originally reported
in ChromeOs with AR9382 chips, this detection logic is enabled only for
AR9380/2 chips.

Cc: Paul Stewart <pstew@google.com>
Reported-by: Gary Morain <gmorain@google.com>
Signed-off-by: Rajkumar Manoharan <rmanohar@qca.qualcomm.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/ath/ath9k/ath9k.h
drivers/net/wireless/ath/ath9k/debug.h
drivers/net/wireless/ath/ath9k/hw.c
drivers/net/wireless/ath/ath9k/init.c
drivers/net/wireless/ath/ath9k/main.c
drivers/net/wireless/ath/ath9k/recv.c

index 8c84049682ab081b0834751502a8df8a8198f53b..0792d87558eff731f4538215deaad423f7d18659 100644 (file)
@@ -430,6 +430,8 @@ 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(unsigned long data);
+void ath_start_rx_poll(struct ath_softc *sc, u8 nbeacon);
 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 +672,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;
index 64fcfad467bf44a38e325d2aed6a4bbee4adfc37..2d47f747512e801035255eb3b222e9b54eeef5e7 100644 (file)
@@ -174,6 +174,7 @@ enum ath_reset_type {
        RESET_TYPE_TX_ERROR,
        RESET_TYPE_TX_HANG,
        RESET_TYPE_PLL_HANG,
+       RESET_TYPE_MAC_HANG,
        __RESET_TYPE_MAX
 };
 
index 6c69e4e8b1cb7ca85b6710aecb8fbe3cba1e10c9..d1345a8a2b15e4b290672490a16b9c8fb479520a 100644 (file)
@@ -1491,11 +1491,84 @@ static void ath9k_hw_apply_gpio_override(struct ath_hw *ah)
        }
 }
 
+static bool ath9k_hw_check_dcs(u32 dma_dbg, u32 num_dcu_states,
+                              int *hang_state, int *hang_pos)
+{
+       static u32 dcu_chain_state[] = {5, 6, 9}; /* DCU chain stuck states */
+       u32 chain_state, dcs_pos, i;
+
+       for (dcs_pos = 0; dcs_pos < num_dcu_states; dcs_pos++) {
+               chain_state = (dma_dbg >> (5 * dcs_pos)) & 0x1f;
+               for (i = 0; i < 3; i++) {
+                       if (chain_state == dcu_chain_state[i]) {
+                               *hang_state = chain_state;
+                               *hang_pos = dcs_pos;
+                               return true;
+                       }
+               }
+       }
+       return false;
+}
+
+#define DCU_COMPLETE_STATE        1
+#define DCU_COMPLETE_STATE_MASK 0x3
+#define NUM_STATUS_READS         50
+static bool ath9k_hw_detect_mac_hang(struct ath_hw *ah)
+{
+       u32 chain_state, comp_state, dcs_reg = AR_DMADBG_4;
+       u32 i, hang_pos, hang_state, num_state = 6;
+
+       comp_state = REG_READ(ah, AR_DMADBG_6);
+
+       if ((comp_state & DCU_COMPLETE_STATE_MASK) != DCU_COMPLETE_STATE) {
+               ath_dbg(ath9k_hw_common(ah), RESET,
+                       "MAC Hang signature not found at DCU complete\n");
+               return false;
+       }
+
+       chain_state = REG_READ(ah, dcs_reg);
+       if (ath9k_hw_check_dcs(chain_state, num_state, &hang_state, &hang_pos))
+               goto hang_check_iter;
+
+       dcs_reg = AR_DMADBG_5;
+       num_state = 4;
+       chain_state = REG_READ(ah, dcs_reg);
+       if (ath9k_hw_check_dcs(chain_state, num_state, &hang_state, &hang_pos))
+               goto hang_check_iter;
+
+       ath_dbg(ath9k_hw_common(ah), RESET,
+               "MAC Hang signature 1 not found\n");
+       return false;
+
+hang_check_iter:
+       ath_dbg(ath9k_hw_common(ah), RESET,
+               "DCU registers: chain %08x complete %08x Hang: state %d pos %d\n",
+               chain_state, comp_state, hang_state, hang_pos);
+
+       for (i = 0; i < NUM_STATUS_READS; i++) {
+               chain_state = REG_READ(ah, dcs_reg);
+               chain_state = (chain_state >> (5 * hang_pos)) & 0x1f;
+               comp_state = REG_READ(ah, AR_DMADBG_6);
+
+               if (((comp_state & DCU_COMPLETE_STATE_MASK) !=
+                                       DCU_COMPLETE_STATE) ||
+                   (chain_state != hang_state))
+                       return false;
+       }
+
+       ath_dbg(ath9k_hw_common(ah), RESET, "MAC Hang signature 1 found\n");
+
+       return true;
+}
+
 bool ath9k_hw_check_alive(struct ath_hw *ah)
 {
        int count = 50;
        u32 reg;
 
+       if (AR_SREV_9300(ah))
+               return !ath9k_hw_detect_mac_hang(ah);
+
        if (AR_SREV_9285_12_OR_LATER(ah))
                return true;
 
index cb006458fc4b62400a7f68e1778982c011eb6c79..b8f3423fdbf939f8f9a11386d09d322963350a50 100644 (file)
@@ -779,6 +779,7 @@ int ath9k_init_device(u16 devid, struct ath_softc *sc,
                        goto error_world;
        }
 
+       setup_timer(&sc->rx_poll_timer, ath_rx_poll, (unsigned long)sc);
        sc->last_rssi = ATH_RSSI_DUMMY_MARKER;
 
        ath_init_leds(sc);
index 38794850f005a52ab7987c9a95f52d87adb9470d..eeea81b16d9cb980e5458f714e618272ea413a52 100644 (file)
@@ -243,6 +243,7 @@ static bool ath_prepare_reset(struct ath_softc *sc, bool retry_tx, bool flush)
 
        sc->hw_busy_count = 0;
        del_timer_sync(&common->ani.timer);
+       del_timer_sync(&sc->rx_poll_timer);
 
        ath9k_debug_samp_bb_mac(sc);
        ath9k_hw_disable_interrupts(ah);
@@ -284,6 +285,7 @@ static bool ath_complete_reset(struct ath_softc *sc, bool start)
 
                ieee80211_queue_delayed_work(sc->hw, &sc->tx_complete_work, 0);
                ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work, HZ/2);
+               ath_start_rx_poll(sc, 3);
                if (!common->disable_ani)
                        ath_start_ani(common);
        }
@@ -914,10 +916,19 @@ void ath_hw_check(struct work_struct *work)
        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
        unsigned long flags;
        int busy;
+       u8 is_alive, nbeacon = 1;
 
        ath9k_ps_wakeup(sc);
-       if (ath9k_hw_check_alive(sc->sc_ah))
+       is_alive = ath9k_hw_check_alive(sc->sc_ah);
+
+       if (is_alive && !AR_SREV_9300(sc->sc_ah))
                goto out;
+       else if (!is_alive && AR_SREV_9300(sc->sc_ah)) {
+               ath_dbg(common, RESET,
+                       "DCU stuck is detected. Schedule chip reset\n");
+               RESET_STAT_INC(sc, RESET_TYPE_MAC_HANG);
+               goto sched_reset;
+       }
 
        spin_lock_irqsave(&common->cc_lock, flags);
        busy = ath_update_survey_stats(sc);
@@ -928,12 +939,18 @@ void ath_hw_check(struct work_struct *work)
        if (busy >= 99) {
                if (++sc->hw_busy_count >= 3) {
                        RESET_STAT_INC(sc, RESET_TYPE_BB_HANG);
-                       ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
+                       goto sched_reset;
                }
-
-       } else if (busy >= 0)
+       } else if (busy >= 0) {
                sc->hw_busy_count = 0;
+               nbeacon = 3;
+       }
 
+       ath_start_rx_poll(sc, nbeacon);
+       goto out;
+
+sched_reset:
+       ieee80211_queue_work(sc->hw, &sc->hw_reset_work);
 out:
        ath9k_ps_restore(sc);
 }
@@ -1153,6 +1170,7 @@ static void ath9k_stop(struct ieee80211_hw *hw)
        mutex_lock(&sc->mutex);
 
        ath_cancel_work(sc);
+       del_timer_sync(&sc->rx_poll_timer);
 
        if (sc->sc_flags & SC_OP_INVALID) {
                ath_dbg(common, ANY, "Device not present\n");
@@ -1385,6 +1403,24 @@ static void ath9k_do_vif_add_setup(struct ieee80211_hw *hw,
        }
 }
 
+void ath_start_rx_poll(struct ath_softc *sc, u8 nbeacon)
+{
+       if (!AR_SREV_9300(sc->sc_ah))
+               return;
+
+       if (!(sc->sc_flags & SC_OP_PRIM_STA_VIF))
+               return;
+
+       mod_timer(&sc->rx_poll_timer, jiffies + msecs_to_jiffies
+                       (nbeacon * sc->cur_beacon_conf.beacon_interval));
+}
+
+void ath_rx_poll(unsigned long data)
+{
+       struct ath_softc *sc = (struct ath_softc *)data;
+
+       ieee80211_queue_work(sc->hw, &sc->hw_check_work);
+}
 
 static int ath9k_add_interface(struct ieee80211_hw *hw,
                               struct ieee80211_vif *vif)
@@ -1906,6 +1942,8 @@ static void ath9k_bss_iter(void *data, u8 *mac, struct ieee80211_vif *vif)
                sc->last_rssi = ATH_RSSI_DUMMY_MARKER;
                sc->sc_ah->stats.avgbrssi = ATH_RSSI_DUMMY_MARKER;
 
+               ath_start_rx_poll(sc, 3);
+
                if (!common->disable_ani) {
                        sc->sc_flags |= SC_OP_ANI_RUN;
                        ath_start_ani(common);
@@ -1945,6 +1983,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));
        }
 }
@@ -1988,6 +2027,7 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
                } else {
                        sc->sc_flags &= ~SC_OP_ANI_RUN;
                        del_timer_sync(&common->ani.timer);
+                       del_timer_sync(&sc->rx_poll_timer);
                }
        }
 
index 1c4583c7ff7cffeba6ee39e1b04eefe43f71130f..858801735282176442c019a3187b3ac52530feef 100644 (file)
@@ -1855,6 +1855,10 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush, bool hp)
                if (retval)
                        goto requeue_drop_frag;
 
+               if (rs.is_mybeacon) {
+                       sc->hw_busy_count = 0;
+                       ath_start_rx_poll(sc, 3);
+               }
                /* Ensure we always have an skb to requeue once we are done
                 * processing the current buffer's skb */
                requeue_skb = ath_rxbuf_alloc(common, common->rx_bufsize, GFP_ATOMIC);