ath6kl: Recover from fw crash
authorVasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
Wed, 29 Aug 2012 14:10:26 +0000 (19:40 +0530)
committerKalle Valo <kvalo@qca.qualcomm.com>
Wed, 24 Oct 2012 08:49:48 +0000 (11:49 +0300)
Re-initialize the target when fw crash is reported.
This would make the device functional again after
target crash. During the target re-initialization
it is made sure that target is not bugged with data/cmd
request, ar->state ATH6KL_STATE_RECOVERY is used
for this purpose.

Signed-off-by: Vasanthakumar Thiagarajan <vthiagar@qca.qualcomm.com>
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
drivers/net/wireless/ath/ath6kl/Makefile
drivers/net/wireless/ath/ath6kl/cfg80211.c
drivers/net/wireless/ath/ath6kl/core.c
drivers/net/wireless/ath/ath6kl/core.h
drivers/net/wireless/ath/ath6kl/debug.h
drivers/net/wireless/ath/ath6kl/hif.c
drivers/net/wireless/ath/ath6kl/init.c
drivers/net/wireless/ath/ath6kl/recovery.c [new file with mode: 0644]
drivers/net/wireless/ath/ath6kl/sdio.c
drivers/net/wireless/ath/ath6kl/txrx.c

index 8cae8886f17dc4eb667f107b49ef4af5c674369b..cab0ec0d5380afde712ac83bf23a2eaab514cbb8 100644 (file)
@@ -34,6 +34,7 @@ ath6kl_core-y += main.o
 ath6kl_core-y += txrx.o
 ath6kl_core-y += wmi.o
 ath6kl_core-y += core.o
+ath6kl_core-y += recovery.o
 ath6kl_core-$(CONFIG_NL80211_TESTMODE) += testmode.o
 
 obj-$(CONFIG_ATH6KL_SDIO) += ath6kl_sdio.o
index b58878648d229fadfe9df1fa4403c78a29ff90c4..8cf146b5c57f66afd0dd2cf53819a7977cd70032 100644 (file)
@@ -2503,14 +2503,23 @@ static int __ath6kl_cfg80211_suspend(struct wiphy *wiphy,
 {
        struct ath6kl *ar = wiphy_priv(wiphy);
 
+       ath6kl_recovery_suspend(ar);
+
        return ath6kl_hif_suspend(ar, wow);
 }
 
 static int __ath6kl_cfg80211_resume(struct wiphy *wiphy)
 {
        struct ath6kl *ar = wiphy_priv(wiphy);
+       int err;
+
+       err = ath6kl_hif_resume(ar);
+       if (err)
+               return err;
 
-       return ath6kl_hif_resume(ar);
+       ar->fw_recovery.enable = true;
+
+       return 0;
 }
 
 /*
@@ -3434,6 +3443,10 @@ void ath6kl_cfg80211_stop(struct ath6kl_vif *vif)
        clear_bit(CONNECTED, &vif->flags);
        clear_bit(CONNECT_PEND, &vif->flags);
 
+       /* Stop netdev queues, needed during recovery */
+       netif_stop_queue(vif->ndev);
+       netif_carrier_off(vif->ndev);
+
        /* disable scanning */
        if (ath6kl_wmi_scanparams_cmd(vif->ar->wmi, vif->fw_vif_idx, 0xFFFF,
                                      0, 0, 0, 0, 0, 0, 0, 0, 0) != 0)
@@ -3447,7 +3460,7 @@ void ath6kl_cfg80211_stop_all(struct ath6kl *ar)
        struct ath6kl_vif *vif;
 
        vif = ath6kl_vif_first(ar);
-       if (!vif) {
+       if (!vif && ar->state != ATH6KL_STATE_RECOVERY) {
                /* save the current power mode before enabling power save */
                ar->wmi->saved_pwr_mode = ar->wmi->pwr_mode;
 
index 82c4dd2a960e5077f7a9f11e1201cabf3b75be04..adcaa965486b941ba0dd9d1ec62a8dfbda57c6f8 100644 (file)
@@ -202,6 +202,8 @@ int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type)
        ath6kl_dbg(ATH6KL_DBG_TRC, "%s: name=%s dev=0x%p, ar=0x%p\n",
                   __func__, wdev->netdev->name, wdev->netdev, ar);
 
+       ath6kl_recovery_init(ar);
+
        return ret;
 
 err_rxbuf_cleanup:
@@ -291,6 +293,8 @@ void ath6kl_core_cleanup(struct ath6kl *ar)
 {
        ath6kl_hif_power_off(ar);
 
+       ath6kl_recovery_cleanup(ar);
+
        destroy_workqueue(ar->ath6kl_wq);
 
        if (ar->htc_target)
index 8b31b9a0f38fc3741961bed3c9fc994946b5aead..c7dcdadd8b830cd3fe926a4b52e9bfd0a954d001 100644 (file)
@@ -645,6 +645,12 @@ enum ath6kl_state {
        ATH6KL_STATE_DEEPSLEEP,
        ATH6KL_STATE_CUTPOWER,
        ATH6KL_STATE_WOW,
+       ATH6KL_STATE_RECOVERY,
+};
+
+/* Fw error recovery */
+enum ath6kl_fw_err {
+       ATH6KL_FW_ASSERT,
 };
 
 struct ath6kl {
@@ -790,6 +796,12 @@ struct ath6kl {
 
        bool wiphy_registered;
 
+       struct ath6kl_fw_recovery {
+               bool enable;
+               struct work_struct recovery_work;
+               unsigned long err_reason;
+       } fw_recovery;
+
 #ifdef CONFIG_ATH6KL_DEBUG
        struct {
                struct sk_buff_head fwlog_queue;
@@ -925,4 +937,10 @@ int ath6kl_core_init(struct ath6kl *ar, enum ath6kl_htc_type htc_type);
 void ath6kl_core_cleanup(struct ath6kl *ar);
 void ath6kl_core_destroy(struct ath6kl *ar);
 
+/* Fw error recovery */
+void ath6kl_init_hw_restart(struct ath6kl *ar);
+void ath6kl_recovery_err_notify(struct ath6kl *ar, enum ath6kl_fw_err reason);
+void ath6kl_recovery_init(struct ath6kl *ar);
+void ath6kl_recovery_cleanup(struct ath6kl *ar);
+void ath6kl_recovery_suspend(struct ath6kl *ar);
 #endif /* CORE_H */
index 49639d8266c28a25a186cfe148c70a5ef0c3535f..f97cd4ead543fdd239220372cc6846539343d1c6 100644 (file)
@@ -44,6 +44,7 @@ enum ATH6K_DEBUG_MASK {
        ATH6KL_DBG_SUSPEND      = BIT(20),
        ATH6KL_DBG_USB          = BIT(21),
        ATH6KL_DBG_USB_BULK     = BIT(22),
+       ATH6KL_DBG_RECOVERY     = BIT(23),
        ATH6KL_DBG_ANY          = 0xffffffff  /* enable all logs */
 };
 
index 68ed6c2665b73387cefbc1edf0985a94b7ad36a6..029914a22ea07e06d23caff20b1bbfae3cd33ae1 100644 (file)
@@ -136,6 +136,7 @@ static int ath6kl_hif_proc_dbg_intr(struct ath6kl_device *dev)
 
        ath6kl_hif_dump_fw_crash(dev->ar);
        ath6kl_read_fwlogs(dev->ar);
+       ath6kl_recovery_err_notify(dev->ar, ATH6KL_FW_ASSERT);
 
        return ret;
 }
index be27ebec905269d8d6aca17db3b6c9ab7ccf1411..301443c9f9ee75c9a4e06a0180814436db4a3b2a 100644 (file)
@@ -1695,6 +1695,25 @@ int ath6kl_init_hw_stop(struct ath6kl *ar)
        return 0;
 }
 
+void ath6kl_init_hw_restart(struct ath6kl *ar)
+{
+
+       ar->state = ATH6KL_STATE_RECOVERY;
+
+       ath6kl_cfg80211_stop_all(ar);
+
+       if (__ath6kl_init_hw_stop(ar))
+               return;
+
+       if (__ath6kl_init_hw_start(ar)) {
+               ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Failed to restart during fw error recovery\n");
+               return;
+       }
+
+       ar->state = ATH6KL_STATE_ON;
+       ar->fw_recovery.err_reason = 0;
+}
+
 /* FIXME: move this to cfg80211.c and rename to ath6kl_cfg80211_vif_stop() */
 void ath6kl_cleanup_vif(struct ath6kl_vif *vif, bool wmi_ready)
 {
diff --git a/drivers/net/wireless/ath/ath6kl/recovery.c b/drivers/net/wireless/ath/ath6kl/recovery.c
new file mode 100644 (file)
index 0000000..c225fc4
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "core.h"
+#include "cfg80211.h"
+#include "debug.h"
+
+static void ath6kl_recovery_work(struct work_struct *work)
+{
+       struct ath6kl *ar = container_of(work, struct ath6kl,
+                                        fw_recovery.recovery_work);
+
+       ath6kl_init_hw_restart(ar);
+}
+
+void ath6kl_recovery_err_notify(struct ath6kl *ar, enum ath6kl_fw_err reason)
+{
+       ath6kl_dbg(ATH6KL_DBG_RECOVERY, "Fw error detected, reason:%d\n",
+                  reason);
+
+       set_bit(reason, &ar->fw_recovery.err_reason);
+
+       if (ar->fw_recovery.enable && ar->state != ATH6KL_STATE_RECOVERY)
+               queue_work(ar->ath6kl_wq, &ar->fw_recovery.recovery_work);
+}
+
+void ath6kl_recovery_init(struct ath6kl *ar)
+{
+       struct ath6kl_fw_recovery *recovery = &ar->fw_recovery;
+
+       recovery->enable = true;
+       INIT_WORK(&recovery->recovery_work, ath6kl_recovery_work);
+}
+
+void ath6kl_recovery_cleanup(struct ath6kl *ar)
+{
+       ar->fw_recovery.enable = false;
+
+       cancel_work_sync(&ar->fw_recovery.recovery_work);
+}
+
+void ath6kl_recovery_suspend(struct ath6kl *ar)
+{
+       ath6kl_recovery_cleanup(ar);
+
+       /* Process pending fw error detection */
+       if (ar->fw_recovery.err_reason)
+               ath6kl_init_hw_restart(ar);
+}
index cc17fe02bdad0c80da504ce2f50e2b89901ccaf0..a72a4d02a4c87e503570b03b832374e0e465ea14 100644 (file)
@@ -931,6 +931,9 @@ static int ath6kl_sdio_resume(struct ath6kl *ar)
 
        case ATH6KL_STATE_RESUMING:
                break;
+
+       case ATH6KL_STATE_RECOVERY:
+               break;
        }
 
        ath6kl_cfg80211_resume(ar);
index 740a488ef5049211e9c4bebf2a852c1d1d2356cb..cbe1a9d891123119e7591d875f280e99801a186d 100644 (file)
@@ -288,7 +288,8 @@ int ath6kl_control_tx(void *devt, struct sk_buff *skb,
        int status = 0;
        struct ath6kl_cookie *cookie = NULL;
 
-       if (WARN_ON_ONCE(ar->state == ATH6KL_STATE_WOW)) {
+       if (WARN_ON_ONCE(ar->state == ATH6KL_STATE_WOW) ||
+           ar->state == ATH6KL_STATE_RECOVERY) {
                dev_kfree_skb(skb);
                return -EACCES;
        }