mwifiex: add PCIe function level reset support
[linux-2.6-block.git] / drivers / net / wireless / marvell / mwifiex / main.c
index db4925db39aa4f07d716040306d6041a037ae0a3..9b2e98cfe090db9d22d990dc0696e100f5f0979f 100644 (file)
@@ -23,6 +23,7 @@
 #include "11n.h"
 
 #define VERSION        "1.0"
+#define MFG_FIRMWARE   "mwifiex_mfg.bin"
 
 static unsigned int debug_mask = MWIFIEX_DEFAULT_DEBUG_MASK;
 module_param(debug_mask, uint, 0);
@@ -37,6 +38,10 @@ module_param(driver_mode, ushort, 0);
 MODULE_PARM_DESC(driver_mode,
                 "station=0x1(default), ap-sta=0x3, station-p2p=0x5, ap-sta-p2p=0x7");
 
+bool mfg_mode;
+module_param(mfg_mode, bool, 0);
+MODULE_PARM_DESC(mfg_mode, "manufacturing mode enable:1, disable:0");
+
 /*
  * This function registers the device and performs all the necessary
  * initializations.
@@ -139,6 +144,8 @@ static int mwifiex_unregister(struct mwifiex_adapter *adapter)
                adapter->nd_info = NULL;
        }
 
+       kfree(adapter->regd);
+
        vfree(adapter->chan_stats);
        kfree(adapter);
        return 0;
@@ -486,9 +493,11 @@ static void mwifiex_free_adapter(struct mwifiex_adapter *adapter)
  */
 static void mwifiex_terminate_workqueue(struct mwifiex_adapter *adapter)
 {
-       flush_workqueue(adapter->workqueue);
-       destroy_workqueue(adapter->workqueue);
-       adapter->workqueue = NULL;
+       if (adapter->workqueue) {
+               flush_workqueue(adapter->workqueue);
+               destroy_workqueue(adapter->workqueue);
+               adapter->workqueue = NULL;
+       }
 
        if (adapter->rx_workqueue) {
                flush_workqueue(adapter->rx_workqueue);
@@ -559,16 +568,21 @@ static void mwifiex_fw_dpc(const struct firmware *firmware, void *context)
                goto done;
        }
        /* Wait for mwifiex_init to complete */
-       wait_event_interruptible(adapter->init_wait_q,
-                                adapter->init_wait_q_woken);
-       if (adapter->hw_status != MWIFIEX_HW_STATUS_READY)
-               goto err_init_fw;
+       if (!adapter->mfg_mode) {
+               wait_event_interruptible(adapter->init_wait_q,
+                                        adapter->init_wait_q_woken);
+               if (adapter->hw_status != MWIFIEX_HW_STATUS_READY)
+                       goto err_init_fw;
+       }
 
        priv = adapter->priv[MWIFIEX_BSS_ROLE_STA];
-       if (mwifiex_register_cfg80211(adapter)) {
-               mwifiex_dbg(adapter, ERROR,
-                           "cannot register with cfg80211\n");
-               goto err_init_fw;
+
+       if (!adapter->wiphy) {
+               if (mwifiex_register_cfg80211(adapter)) {
+                       mwifiex_dbg(adapter, ERROR,
+                                   "cannot register with cfg80211\n");
+                       goto err_init_fw;
+               }
        }
 
        if (mwifiex_init_channel_scan_gap(adapter)) {
@@ -662,16 +676,41 @@ done:
 /*
  * This function initializes the hardware and gets firmware.
  */
-static int mwifiex_init_hw_fw(struct mwifiex_adapter *adapter)
+static int mwifiex_init_hw_fw(struct mwifiex_adapter *adapter,
+                             bool req_fw_nowait)
 {
        int ret;
 
-       ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name,
-                                     adapter->dev, GFP_KERNEL, adapter,
-                                     mwifiex_fw_dpc);
-       if (ret < 0)
-               mwifiex_dbg(adapter, ERROR,
-                           "request_firmware_nowait error %d\n", ret);
+       /* Override default firmware with manufacturing one if
+        * manufacturing mode is enabled
+        */
+       if (mfg_mode) {
+               if (strlcpy(adapter->fw_name, MFG_FIRMWARE,
+                           sizeof(adapter->fw_name)) >=
+                           sizeof(adapter->fw_name)) {
+                       pr_err("%s: fw_name too long!\n", __func__);
+                       return -1;
+               }
+       }
+
+       if (req_fw_nowait) {
+               ret = request_firmware_nowait(THIS_MODULE, 1, adapter->fw_name,
+                                             adapter->dev, GFP_KERNEL, adapter,
+                                             mwifiex_fw_dpc);
+               if (ret < 0)
+                       mwifiex_dbg(adapter, ERROR,
+                                   "request_firmware_nowait error %d\n", ret);
+       } else {
+               ret = request_firmware(&adapter->firmware,
+                                      adapter->fw_name,
+                                      adapter->dev);
+               if (ret < 0)
+                       mwifiex_dbg(adapter, ERROR,
+                                   "request_firmware error %d\n", ret);
+               else
+                       mwifiex_fw_dpc(adapter->firmware, (void *)adapter);
+       }
+
        return ret;
 }
 
@@ -1320,6 +1359,199 @@ static void mwifiex_main_work_queue(struct work_struct *work)
        mwifiex_main_process(adapter);
 }
 
+/*
+ * This function gets called during PCIe function level reset. Required
+ * code is extracted from mwifiex_remove_card()
+ */
+static int
+mwifiex_shutdown_sw(struct mwifiex_adapter *adapter, struct semaphore *sem)
+{
+       struct mwifiex_private *priv;
+       int i;
+
+       if (down_interruptible(sem))
+               goto exit_sem_err;
+
+       if (!adapter)
+               goto exit_remove;
+
+       priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY);
+       mwifiex_deauthenticate(priv, NULL);
+
+       /* We can no longer handle interrupts once we start doing the teardown
+        * below.
+        */
+       if (adapter->if_ops.disable_int)
+               adapter->if_ops.disable_int(adapter);
+
+       adapter->surprise_removed = true;
+       mwifiex_terminate_workqueue(adapter);
+
+       /* Stop data */
+       for (i = 0; i < adapter->priv_num; i++) {
+               priv = adapter->priv[i];
+               if (priv && priv->netdev) {
+                       mwifiex_stop_net_dev_queue(priv->netdev, adapter);
+                       if (netif_carrier_ok(priv->netdev))
+                               netif_carrier_off(priv->netdev);
+                       netif_device_detach(priv->netdev);
+               }
+       }
+
+       mwifiex_dbg(adapter, CMD, "cmd: calling mwifiex_shutdown_drv...\n");
+       adapter->init_wait_q_woken = false;
+
+       if (mwifiex_shutdown_drv(adapter) == -EINPROGRESS)
+               wait_event_interruptible(adapter->init_wait_q,
+                                        adapter->init_wait_q_woken);
+       if (adapter->if_ops.down_dev)
+               adapter->if_ops.down_dev(adapter);
+
+       mwifiex_dbg(adapter, CMD, "cmd: mwifiex_shutdown_drv done\n");
+       if (atomic_read(&adapter->rx_pending) ||
+           atomic_read(&adapter->tx_pending) ||
+           atomic_read(&adapter->cmd_pending)) {
+               mwifiex_dbg(adapter, ERROR,
+                           "rx_pending=%d, tx_pending=%d,\t"
+                           "cmd_pending=%d\n",
+                           atomic_read(&adapter->rx_pending),
+                           atomic_read(&adapter->tx_pending),
+                           atomic_read(&adapter->cmd_pending));
+       }
+
+       for (i = 0; i < adapter->priv_num; i++) {
+               priv = adapter->priv[i];
+               if (!priv)
+                       continue;
+               rtnl_lock();
+               if (priv->netdev &&
+                   priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED)
+                       mwifiex_del_virtual_intf(adapter->wiphy, &priv->wdev);
+               rtnl_unlock();
+       }
+
+exit_remove:
+       up(sem);
+exit_sem_err:
+       mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__);
+       return 0;
+}
+
+/* This function gets called during PCIe function level reset. Required
+ * code is extracted from mwifiex_add_card()
+ */
+static int
+mwifiex_reinit_sw(struct mwifiex_adapter *adapter, struct semaphore *sem,
+                 struct mwifiex_if_ops *if_ops, u8 iface_type)
+{
+       char fw_name[32];
+       struct pcie_service_card *card = adapter->card;
+
+       if (down_interruptible(sem))
+               goto exit_sem_err;
+
+       mwifiex_init_lock_list(adapter);
+       if (adapter->if_ops.up_dev)
+               adapter->if_ops.up_dev(adapter);
+
+       adapter->iface_type = iface_type;
+       adapter->card_sem = sem;
+
+       adapter->hw_status = MWIFIEX_HW_STATUS_INITIALIZING;
+       adapter->surprise_removed = false;
+       init_waitqueue_head(&adapter->init_wait_q);
+       adapter->is_suspended = false;
+       adapter->hs_activated = false;
+       init_waitqueue_head(&adapter->hs_activate_wait_q);
+       init_waitqueue_head(&adapter->cmd_wait_q.wait);
+       adapter->cmd_wait_q.status = 0;
+       adapter->scan_wait_q_woken = false;
+
+       if ((num_possible_cpus() > 1) || adapter->iface_type == MWIFIEX_USB)
+               adapter->rx_work_enabled = true;
+
+       adapter->workqueue =
+               alloc_workqueue("MWIFIEX_WORK_QUEUE",
+                               WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 1);
+       if (!adapter->workqueue)
+               goto err_kmalloc;
+
+       INIT_WORK(&adapter->main_work, mwifiex_main_work_queue);
+
+       if (adapter->rx_work_enabled) {
+               adapter->rx_workqueue = alloc_workqueue("MWIFIEX_RX_WORK_QUEUE",
+                                                       WQ_HIGHPRI |
+                                                       WQ_MEM_RECLAIM |
+                                                       WQ_UNBOUND, 1);
+               if (!adapter->rx_workqueue)
+                       goto err_kmalloc;
+               INIT_WORK(&adapter->rx_work, mwifiex_rx_work_queue);
+       }
+
+       /* Register the device. Fill up the private data structure with
+        * relevant information from the card. Some code extracted from
+        * mwifiex_register_dev()
+        */
+       mwifiex_dbg(adapter, INFO, "%s, mwifiex_init_hw_fw()...\n", __func__);
+       strcpy(fw_name, adapter->fw_name);
+       strcpy(adapter->fw_name, PCIE8997_DEFAULT_WIFIFW_NAME);
+
+       adapter->tx_buf_size = card->pcie.tx_buf_size;
+       adapter->ext_scan = card->pcie.can_ext_scan;
+       if (mwifiex_init_hw_fw(adapter, false)) {
+               strcpy(adapter->fw_name, fw_name);
+               mwifiex_dbg(adapter, ERROR,
+                           "%s: firmware init failed\n", __func__);
+               goto err_init_fw;
+       }
+       strcpy(adapter->fw_name, fw_name);
+       mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__);
+       up(sem);
+       return 0;
+
+err_init_fw:
+       mwifiex_dbg(adapter, ERROR, "info: %s: unregister device\n", __func__);
+       if (adapter->if_ops.unregister_dev)
+               adapter->if_ops.unregister_dev(adapter);
+       if (adapter->hw_status == MWIFIEX_HW_STATUS_READY) {
+               mwifiex_dbg(adapter, ERROR,
+                           "info: %s: shutdown mwifiex\n", __func__);
+               adapter->init_wait_q_woken = false;
+
+               if (mwifiex_shutdown_drv(adapter) == -EINPROGRESS)
+                       wait_event_interruptible(adapter->init_wait_q,
+                                                adapter->init_wait_q_woken);
+       }
+
+err_kmalloc:
+       mwifiex_terminate_workqueue(adapter);
+       adapter->surprise_removed = true;
+       up(sem);
+exit_sem_err:
+       mwifiex_dbg(adapter, INFO, "%s, error\n", __func__);
+
+       return -1;
+}
+
+/* This function processes pre and post PCIe function level resets.
+ * It performs software cleanup without touching PCIe specific code.
+ * Also, during initialization PCIe stuff is skipped.
+ */
+void mwifiex_do_flr(struct mwifiex_adapter *adapter, bool prepare)
+{
+       struct mwifiex_if_ops if_ops;
+
+       if (!prepare) {
+               mwifiex_reinit_sw(adapter, adapter->card_sem, &if_ops,
+                                 adapter->iface_type);
+       } else {
+               memcpy(&if_ops, &adapter->if_ops,
+                      sizeof(struct mwifiex_if_ops));
+               mwifiex_shutdown_sw(adapter, adapter->card_sem);
+       }
+}
+EXPORT_SYMBOL_GPL(mwifiex_do_flr);
+
 /*
  * This function adds the card.
  *
@@ -1391,7 +1623,7 @@ mwifiex_add_card(void *card, struct semaphore *sem,
                goto err_registerdev;
        }
 
-       if (mwifiex_init_hw_fw(adapter)) {
+       if (mwifiex_init_hw_fw(adapter, true)) {
                pr_err("%s: firmware init failed\n", __func__);
                goto err_init_fw;
        }