net: wireless: bcmdhd: Fix FW hang recovery
Dmitry Shmidt [Tue, 5 Jun 2012 22:36:59 +0000 (15:36 -0700)]
Change-Id: Ie457a34e9454936a59e7c0962902e12f312fe1a2
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>

drivers/net/wireless/bcmdhd/dhd_linux.c

index f9a0bba..5a1c283 100644 (file)
@@ -136,6 +136,9 @@ DECLARE_WAIT_QUEUE_HEAD(dhd_dpc_wait);
 #if defined(OOB_INTR_ONLY)
 extern void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable);
 #endif /* defined(OOB_INTR_ONLY) */
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
+static void dhd_hang_process(struct work_struct *work);
+#endif
 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
 MODULE_LICENSE("GPL v2");
 #endif /* LinuxVer */
@@ -258,6 +261,9 @@ typedef struct dhd_info {
 #endif /* DHDTHREAD */
        bool dhd_tasklet_create;
        tsk_ctl_t       thr_sysioc_ctl;
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
+       struct work_struct work_hang;
+#endif
 
        /* Wakelocks */
 #if defined(CONFIG_HAS_WAKELOCK) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
@@ -2059,6 +2065,7 @@ dhd_ethtool(dhd_info_t *dhd, void *uaddr)
 
 static bool dhd_check_hang(struct net_device *net, dhd_pub_t *dhdp, int error)
 {
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
        if (!dhdp)
                return FALSE;
        if ((error == -ETIMEDOUT) || ((dhdp->busstate == DHD_BUS_DOWN) &&
@@ -2068,6 +2075,7 @@ static bool dhd_check_hang(struct net_device *net, dhd_pub_t *dhdp, int error)
                net_os_send_hang_message(net);
                return TRUE;
        }
+#endif
        return FALSE;
 }
 
@@ -2334,7 +2342,7 @@ dhd_cleanup_virt_ifaces(dhd_info_t *dhd)
 static int
 dhd_stop(struct net_device *net)
 {
-       int ifidx;
+       int ifidx = 0;
        dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net);
        DHD_OS_WAKE_LOCK(&dhd->pub);
        DHD_TRACE(("%s: Enter %p\n", __FUNCTION__, net));
@@ -2369,15 +2377,15 @@ dhd_stop(struct net_device *net)
        /* Stop the protocol module */
        dhd_prot_stop(&dhd->pub);
 
+       OLD_MOD_DEC_USE_COUNT;
+exit:
 #if defined(WL_CFG80211)
        if (ifidx == 0 && !dhd_download_fw_on_driverload)
                wl_android_wifi_off(net);
 #endif
-       dhd->pub.hang_was_sent = 0;
        dhd->pub.rxcnt_timeout = 0;
        dhd->pub.txcnt_timeout = 0;
-       OLD_MOD_DEC_USE_COUNT;
-exit:
+
        DHD_OS_WAKE_UNLOCK(&dhd->pub);
        return 0;
 }
@@ -2401,6 +2409,7 @@ dhd_open(struct net_device *net)
                firmware_path[0] = '\0';
        }
 
+       dhd->pub.hang_was_sent = 0;
 #if !defined(WL_CFG80211)
        /*
         * Force start if ifconfig_up gets called before START command
@@ -2803,7 +2812,9 @@ dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen, void *dev)
                dhd->thr_sysioc_ctl.thr_pid = -1;
        }
        dhd_state |= DHD_ATTACH_STATE_THREADS_CREATED;
-
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
+       INIT_WORK(&dhd->work_hang, dhd_hang_process);
+#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))  */
        /*
         * Save the dhd_info into the priv
         */
@@ -3657,6 +3668,9 @@ void dhd_detach(dhd_pub_t *dhdp)
        }
 #endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
 
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
+       cancel_work_sync(&dhd->work_hang);
+#endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))  */
 
 #if defined(CONFIG_BCMDHD_WEXT)
        if (dhd->dhd_state & DHD_ATTACH_STATE_WL_ATTACH) {
@@ -3987,14 +4001,11 @@ dhd_os_ioctl_resp_wait(dhd_pub_t *pub, uint *condition, bool *pending)
         * Can be changed by another processor.
         */
        smp_mb();
-       while (!(*condition) && (!signal_pending(current) && timeout)) {
+       while (!(*condition) && timeout) {
                timeout = schedule_timeout(timeout);
                smp_mb();
        }
 
-       if (signal_pending(current))
-               *pending = TRUE;
-
        set_current_state(TASK_RUNNING);
        remove_wait_queue(&dhd->ioctl_resp_wait, &wait);
 
@@ -4525,6 +4536,28 @@ dhd_dev_get_pno_status(struct net_device *dev)
 
 #endif /* PNO_SUPPORT */
 
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
+static void dhd_hang_process(struct work_struct *work)
+{
+       dhd_info_t *dhd;
+       struct net_device *dev;
+
+       dhd = (dhd_info_t *)container_of(work, dhd_info_t, work_hang);
+       dev = dhd->iflist[0]->net;
+
+       if (dev) {
+               rtnl_lock();
+               dev_close(dev);
+               rtnl_unlock();
+#if defined(WL_WIRELESS_EXT)
+               wl_iw_send_priv_event(dev, "HANG");
+#endif
+#if defined(WL_CFG80211)
+               wl_cfg80211_hang(dev, WLAN_REASON_UNSPECIFIED);
+#endif
+       }
+}
+
 int net_os_send_hang_message(struct net_device *dev)
 {
        dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
@@ -4533,18 +4566,12 @@ int net_os_send_hang_message(struct net_device *dev)
        if (dhd) {
                if (!dhd->pub.hang_was_sent) {
                        dhd->pub.hang_was_sent = 1;
-#if defined(CONFIG_BCMDHD_WEXT)
-                       ret = wl_iw_send_priv_event(dev, "HANG");
-#endif
-#if defined(WL_CFG80211)
-                       ret = wl_cfg80211_hang(dev, WLAN_REASON_UNSPECIFIED);
-                       dev_close(dev);
-                       dev_open(dev);
-#endif
+                       schedule_work(&dhd->work_hang);
                }
        }
        return ret;
 }
+#endif
 
 void dhd_bus_country_set(struct net_device *dev, wl_country_t *cspec)
 {