From: Simon Wunderlich <simon.wunderl...@open-mesh.com>

QCA 802.11n chips (especially AR9330/AR9340) sometimes end up in a state in
which a read of AR_CFG always returns 0xdeadbeef. This should not happen
when when the power_mode of the device is ATH9K_PM_AWAKE.

This problem is not yet detected by any other workaround in ath9k. No way
is known to reproduce the problem easily.

Signed-off-by: Simon Wunderlich <simon.wunderl...@open-mesh.com>
[sven.eckelm...@open-mesh.com: port to recent ath9k, add commit message]
Signed-off-by: Sven Eckelmann <sven.eckelm...@open-mesh.com>
---
This was discussed 4 years ago on the OpenWrt mailing list. The most
relevant post is
https://lists.openwrt.org/pipermail/openwrt-devel/2012-September/016708.html
---
 drivers/net/wireless/ath/ath9k/ath9k.h |  3 +++
 drivers/net/wireless/ath/ath9k/debug.c |  1 +
 drivers/net/wireless/ath/ath9k/debug.h |  1 +
 drivers/net/wireless/ath/ath9k/init.c  |  1 +
 drivers/net/wireless/ath/ath9k/link.c  | 33 +++++++++++++++++++++++++++++++++
 drivers/net/wireless/ath/ath9k/main.c  |  4 ++++
 6 files changed, 43 insertions(+)

diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h 
b/drivers/net/wireless/ath/ath9k/ath9k.h
index 26fc8ec..9c6fee7 100644
--- a/drivers/net/wireless/ath/ath9k/ath9k.h
+++ b/drivers/net/wireless/ath/ath9k/ath9k.h
@@ -710,11 +710,13 @@ void ath9k_csa_update(struct ath_softc *sc);
 #define ATH_ANI_MAX_SKIP_COUNT    10
 #define ATH_PAPRD_TIMEOUT         100 /* msecs */
 #define ATH_PLL_WORK_INTERVAL     100
+#define ATH_HANG_WORK_INTERVAL    30000
 
 void ath_tx_complete_poll_work(struct work_struct *work);
 void ath_reset_work(struct work_struct *work);
 bool ath_hw_check(struct ath_softc *sc);
 void ath_hw_pll_work(struct work_struct *work);
+void ath_hw_hang_work(struct work_struct *work);
 void ath_paprd_calibrate(struct work_struct *work);
 void ath_ani_calibrate(unsigned long data);
 void ath_start_ani(struct ath_softc *sc);
@@ -1014,6 +1016,7 @@ struct ath_softc {
 #endif
        struct delayed_work tx_complete_work;
        struct delayed_work hw_pll_work;
+       struct delayed_work hw_hang_work;
        struct timer_list sleep_timer;
 
 #ifdef CONFIG_ATH9K_BTCOEX_SUPPORT
diff --git a/drivers/net/wireless/ath/ath9k/debug.c 
b/drivers/net/wireless/ath/ath9k/debug.c
index c56e40f..608b370 100644
--- a/drivers/net/wireless/ath/ath9k/debug.c
+++ b/drivers/net/wireless/ath/ath9k/debug.c
@@ -767,6 +767,7 @@ static int read_file_reset(struct seq_file *file, void 
*data)
                [RESET_TYPE_CALIBRATION] = "Calibration error",
                [RESET_TX_DMA_ERROR] = "Tx DMA stop error",
                [RESET_RX_DMA_ERROR] = "Rx DMA stop error",
+               [RESET_TYPE_DEADBEEF] = "deadbeef hang",
        };
        int i;
 
diff --git a/drivers/net/wireless/ath/ath9k/debug.h 
b/drivers/net/wireless/ath/ath9k/debug.h
index cd68c5f..0d77abbf6 100644
--- a/drivers/net/wireless/ath/ath9k/debug.h
+++ b/drivers/net/wireless/ath/ath9k/debug.h
@@ -52,6 +52,7 @@ enum ath_reset_type {
        RESET_TYPE_CALIBRATION,
        RESET_TX_DMA_ERROR,
        RESET_RX_DMA_ERROR,
+       RESET_TYPE_DEADBEEF,
        __RESET_TYPE_MAX
 };
 
diff --git a/drivers/net/wireless/ath/ath9k/init.c 
b/drivers/net/wireless/ath/ath9k/init.c
index 368d9b3..9bc7d1c 100644
--- a/drivers/net/wireless/ath/ath9k/init.c
+++ b/drivers/net/wireless/ath/ath9k/init.c
@@ -638,6 +638,7 @@ static int ath9k_init_softc(u16 devid, struct ath_softc *sc,
        INIT_WORK(&sc->hw_reset_work, ath_reset_work);
        INIT_WORK(&sc->paprd_work, ath_paprd_calibrate);
        INIT_DELAYED_WORK(&sc->hw_pll_work, ath_hw_pll_work);
+       INIT_DELAYED_WORK(&sc->hw_hang_work, ath_hw_hang_work);
 
        ath9k_init_channel_context(sc);
 
diff --git a/drivers/net/wireless/ath/ath9k/link.c 
b/drivers/net/wireless/ath/ath9k/link.c
index 5ad0fee..ff11d85 100644
--- a/drivers/net/wireless/ath/ath9k/link.c
+++ b/drivers/net/wireless/ath/ath9k/link.c
@@ -138,6 +138,39 @@ void ath_hw_pll_work(struct work_struct *work)
                                     msecs_to_jiffies(ATH_PLL_WORK_INTERVAL));
 }
 
+static bool ath_hw_hang_deadbeef(struct ath_softc *sc)
+{
+       struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+       u32 reg;
+
+       /* check for stucked MAC */
+       ath9k_ps_wakeup(sc);
+       reg = REG_READ(sc->sc_ah, AR_CFG);
+       ath9k_ps_restore(sc);
+
+       if (reg != 0xdeadbeef)
+               return false;
+
+       ath_dbg(common, RESET,
+               "0xdeadbeef hang is detected. Schedule chip reset\n");
+       ath9k_queue_reset(sc, RESET_TYPE_DEADBEEF);
+
+       return true;
+}
+
+void ath_hw_hang_work(struct work_struct *work)
+{
+       struct ath_softc *sc = container_of(work, struct ath_softc,
+                                           hw_hang_work.work);
+
+       if (ath_hw_hang_deadbeef(sc))
+               goto requeue_worker;
+
+requeue_worker:
+       ieee80211_queue_delayed_work(sc->hw, &sc->hw_hang_work,
+                                    msecs_to_jiffies(ATH_HANG_WORK_INTERVAL));
+}
+
 /*
  * PA Pre-distortion.
  */
diff --git a/drivers/net/wireless/ath/ath9k/main.c 
b/drivers/net/wireless/ath/ath9k/main.c
index e9f32b5..4d3e216 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -183,6 +183,7 @@ static void __ath_cancel_work(struct ath_softc *sc)
        cancel_work_sync(&sc->paprd_work);
        cancel_delayed_work_sync(&sc->tx_complete_work);
        cancel_delayed_work_sync(&sc->hw_pll_work);
+       cancel_delayed_work_sync(&sc->hw_hang_work);
 
 #ifdef CONFIG_ATH9K_BTCOEX_SUPPORT
        if (ath9k_hw_mci_is_enabled(sc->sc_ah))
@@ -204,6 +205,9 @@ void ath_restart_work(struct ath_softc *sc)
                ieee80211_queue_delayed_work(sc->hw, &sc->hw_pll_work,
                                     msecs_to_jiffies(ATH_PLL_WORK_INTERVAL));
 
+       ieee80211_queue_delayed_work(sc->hw, &sc->hw_hang_work,
+                                    msecs_to_jiffies(ATH_HANG_WORK_INTERVAL));
+
        ath_start_ani(sc);
 }
 
-- 
2.10.2

Reply via email to