|
@@ -224,6 +224,12 @@ struct mwl8k_priv {
|
|
|
* the firmware image is swapped.
|
|
|
*/
|
|
|
struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_QUEUES];
|
|
|
+
|
|
|
+ /* async firmware loading state */
|
|
|
+ unsigned fw_state;
|
|
|
+ char *fw_pref;
|
|
|
+ char *fw_alt;
|
|
|
+ struct completion firmware_loading_complete;
|
|
|
};
|
|
|
|
|
|
/* Per interface specific private data */
|
|
@@ -403,34 +409,66 @@ static void mwl8k_release_firmware(struct mwl8k_priv *priv)
|
|
|
mwl8k_release_fw(&priv->fw_helper);
|
|
|
}
|
|
|
|
|
|
+/* states for asynchronous f/w loading */
|
|
|
+static void mwl8k_fw_state_machine(const struct firmware *fw, void *context);
|
|
|
+enum {
|
|
|
+ FW_STATE_INIT = 0,
|
|
|
+ FW_STATE_LOADING_PREF,
|
|
|
+ FW_STATE_LOADING_ALT,
|
|
|
+ FW_STATE_ERROR,
|
|
|
+};
|
|
|
+
|
|
|
/* Request fw image */
|
|
|
static int mwl8k_request_fw(struct mwl8k_priv *priv,
|
|
|
- const char *fname, struct firmware **fw)
|
|
|
+ const char *fname, struct firmware **fw,
|
|
|
+ bool nowait)
|
|
|
{
|
|
|
/* release current image */
|
|
|
if (*fw != NULL)
|
|
|
mwl8k_release_fw(fw);
|
|
|
|
|
|
- return request_firmware((const struct firmware **)fw,
|
|
|
- fname, &priv->pdev->dev);
|
|
|
+ if (nowait)
|
|
|
+ return request_firmware_nowait(THIS_MODULE, 1, fname,
|
|
|
+ &priv->pdev->dev, GFP_KERNEL,
|
|
|
+ priv, mwl8k_fw_state_machine);
|
|
|
+ else
|
|
|
+ return request_firmware((const struct firmware **)fw,
|
|
|
+ fname, &priv->pdev->dev);
|
|
|
}
|
|
|
|
|
|
-static int mwl8k_request_firmware(struct mwl8k_priv *priv, char *fw_image)
|
|
|
+static int mwl8k_request_firmware(struct mwl8k_priv *priv, char *fw_image,
|
|
|
+ bool nowait)
|
|
|
{
|
|
|
struct mwl8k_device_info *di = priv->device_info;
|
|
|
int rc;
|
|
|
|
|
|
if (di->helper_image != NULL) {
|
|
|
- rc = mwl8k_request_fw(priv, di->helper_image, &priv->fw_helper);
|
|
|
- if (rc) {
|
|
|
- printk(KERN_ERR "%s: Error requesting helper "
|
|
|
- "firmware file %s\n", pci_name(priv->pdev),
|
|
|
- di->helper_image);
|
|
|
+ if (nowait)
|
|
|
+ rc = mwl8k_request_fw(priv, di->helper_image,
|
|
|
+ &priv->fw_helper, true);
|
|
|
+ else
|
|
|
+ rc = mwl8k_request_fw(priv, di->helper_image,
|
|
|
+ &priv->fw_helper, false);
|
|
|
+ if (rc)
|
|
|
+ printk(KERN_ERR "%s: Error requesting helper fw %s\n",
|
|
|
+ pci_name(priv->pdev), di->helper_image);
|
|
|
+
|
|
|
+ if (rc || nowait)
|
|
|
return rc;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- rc = mwl8k_request_fw(priv, fw_image, &priv->fw_ucode);
|
|
|
+ if (nowait) {
|
|
|
+ /*
|
|
|
+ * if we get here, no helper image is needed. Skip the
|
|
|
+ * FW_STATE_INIT state.
|
|
|
+ */
|
|
|
+ priv->fw_state = FW_STATE_LOADING_PREF;
|
|
|
+ rc = mwl8k_request_fw(priv, fw_image,
|
|
|
+ &priv->fw_ucode,
|
|
|
+ true);
|
|
|
+ } else
|
|
|
+ rc = mwl8k_request_fw(priv, fw_image,
|
|
|
+ &priv->fw_ucode, false);
|
|
|
if (rc) {
|
|
|
printk(KERN_ERR "%s: Error requesting firmware file %s\n",
|
|
|
pci_name(priv->pdev), fw_image);
|
|
@@ -3998,7 +4036,99 @@ static DEFINE_PCI_DEVICE_TABLE(mwl8k_pci_id_table) = {
|
|
|
};
|
|
|
MODULE_DEVICE_TABLE(pci, mwl8k_pci_id_table);
|
|
|
|
|
|
-static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image)
|
|
|
+static int mwl8k_request_alt_fw(struct mwl8k_priv *priv)
|
|
|
+{
|
|
|
+ int rc;
|
|
|
+ printk(KERN_ERR "%s: Error requesting preferred fw %s.\n"
|
|
|
+ "Trying alternative firmware %s\n", pci_name(priv->pdev),
|
|
|
+ priv->fw_pref, priv->fw_alt);
|
|
|
+ rc = mwl8k_request_fw(priv, priv->fw_alt, &priv->fw_ucode, true);
|
|
|
+ if (rc) {
|
|
|
+ printk(KERN_ERR "%s: Error requesting alt fw %s\n",
|
|
|
+ pci_name(priv->pdev), priv->fw_alt);
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int mwl8k_firmware_load_success(struct mwl8k_priv *priv);
|
|
|
+static void mwl8k_fw_state_machine(const struct firmware *fw, void *context)
|
|
|
+{
|
|
|
+ struct mwl8k_priv *priv = context;
|
|
|
+ struct mwl8k_device_info *di = priv->device_info;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ switch (priv->fw_state) {
|
|
|
+ case FW_STATE_INIT:
|
|
|
+ if (!fw) {
|
|
|
+ printk(KERN_ERR "%s: Error requesting helper fw %s\n",
|
|
|
+ pci_name(priv->pdev), di->helper_image);
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ priv->fw_helper = fw;
|
|
|
+ rc = mwl8k_request_fw(priv, priv->fw_pref, &priv->fw_ucode,
|
|
|
+ true);
|
|
|
+ if (rc && priv->fw_alt) {
|
|
|
+ rc = mwl8k_request_alt_fw(priv);
|
|
|
+ if (rc)
|
|
|
+ goto fail;
|
|
|
+ priv->fw_state = FW_STATE_LOADING_ALT;
|
|
|
+ } else if (rc)
|
|
|
+ goto fail;
|
|
|
+ else
|
|
|
+ priv->fw_state = FW_STATE_LOADING_PREF;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case FW_STATE_LOADING_PREF:
|
|
|
+ if (!fw) {
|
|
|
+ if (priv->fw_alt) {
|
|
|
+ rc = mwl8k_request_alt_fw(priv);
|
|
|
+ if (rc)
|
|
|
+ goto fail;
|
|
|
+ priv->fw_state = FW_STATE_LOADING_ALT;
|
|
|
+ } else
|
|
|
+ goto fail;
|
|
|
+ } else {
|
|
|
+ priv->fw_ucode = fw;
|
|
|
+ rc = mwl8k_firmware_load_success(priv);
|
|
|
+ if (rc)
|
|
|
+ goto fail;
|
|
|
+ else
|
|
|
+ complete(&priv->firmware_loading_complete);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case FW_STATE_LOADING_ALT:
|
|
|
+ if (!fw) {
|
|
|
+ printk(KERN_ERR "%s: Error requesting alt fw %s\n",
|
|
|
+ pci_name(priv->pdev), di->helper_image);
|
|
|
+ goto fail;
|
|
|
+ }
|
|
|
+ priv->fw_ucode = fw;
|
|
|
+ rc = mwl8k_firmware_load_success(priv);
|
|
|
+ if (rc)
|
|
|
+ goto fail;
|
|
|
+ else
|
|
|
+ complete(&priv->firmware_loading_complete);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ printk(KERN_ERR "%s: Unexpected firmware loading state: %d\n",
|
|
|
+ MWL8K_NAME, priv->fw_state);
|
|
|
+ BUG_ON(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+fail:
|
|
|
+ priv->fw_state = FW_STATE_ERROR;
|
|
|
+ complete(&priv->firmware_loading_complete);
|
|
|
+ device_release_driver(&priv->pdev->dev);
|
|
|
+ mwl8k_release_firmware(priv);
|
|
|
+}
|
|
|
+
|
|
|
+static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image,
|
|
|
+ bool nowait)
|
|
|
{
|
|
|
struct mwl8k_priv *priv = hw->priv;
|
|
|
int rc;
|
|
@@ -4007,12 +4137,15 @@ static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image)
|
|
|
mwl8k_hw_reset(priv);
|
|
|
|
|
|
/* Ask userland hotplug daemon for the device firmware */
|
|
|
- rc = mwl8k_request_firmware(priv, fw_image);
|
|
|
+ rc = mwl8k_request_firmware(priv, fw_image, nowait);
|
|
|
if (rc) {
|
|
|
wiphy_err(hw->wiphy, "Firmware files not found\n");
|
|
|
return rc;
|
|
|
}
|
|
|
|
|
|
+ if (nowait)
|
|
|
+ return rc;
|
|
|
+
|
|
|
/* Load firmware into hardware */
|
|
|
rc = mwl8k_load_firmware(hw);
|
|
|
if (rc)
|
|
@@ -4147,7 +4280,7 @@ static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image)
|
|
|
for (i = 0; i < MWL8K_TX_QUEUES; i++)
|
|
|
mwl8k_txq_deinit(hw, i);
|
|
|
|
|
|
- rc = mwl8k_init_firmware(hw, fw_image);
|
|
|
+ rc = mwl8k_init_firmware(hw, fw_image, false);
|
|
|
if (rc)
|
|
|
goto fail;
|
|
|
|
|
@@ -4181,6 +4314,13 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv)
|
|
|
struct ieee80211_hw *hw = priv->hw;
|
|
|
int i, rc;
|
|
|
|
|
|
+ rc = mwl8k_load_firmware(hw);
|
|
|
+ mwl8k_release_firmware(priv);
|
|
|
+ if (rc) {
|
|
|
+ wiphy_err(hw->wiphy, "Cannot start firmware\n");
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Extra headroom is the size of the required DMA header
|
|
|
* minus the size of the smallest 802.11 frame (CTS frame).
|
|
@@ -4325,28 +4465,29 @@ static int __devinit mwl8k_probe(struct pci_dev *pdev,
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Choose the initial fw image depending on user input and availability
|
|
|
- * of images.
|
|
|
+ * Choose the initial fw image depending on user input. If a second
|
|
|
+ * image is available, make it the alternative image that will be
|
|
|
+ * loaded if the first one fails.
|
|
|
*/
|
|
|
+ init_completion(&priv->firmware_loading_complete);
|
|
|
di = priv->device_info;
|
|
|
- if (ap_mode_default && di->fw_image_ap)
|
|
|
- rc = mwl8k_init_firmware(hw, di->fw_image_ap);
|
|
|
- else if (!ap_mode_default && di->fw_image_sta)
|
|
|
- rc = mwl8k_init_firmware(hw, di->fw_image_sta);
|
|
|
- else if (ap_mode_default && !di->fw_image_ap && di->fw_image_sta) {
|
|
|
+ if (ap_mode_default && di->fw_image_ap) {
|
|
|
+ priv->fw_pref = di->fw_image_ap;
|
|
|
+ priv->fw_alt = di->fw_image_sta;
|
|
|
+ } else if (!ap_mode_default && di->fw_image_sta) {
|
|
|
+ priv->fw_pref = di->fw_image_sta;
|
|
|
+ priv->fw_alt = di->fw_image_ap;
|
|
|
+ } else if (ap_mode_default && !di->fw_image_ap && di->fw_image_sta) {
|
|
|
printk(KERN_WARNING "AP fw is unavailable. Using STA fw.");
|
|
|
- rc = mwl8k_init_firmware(hw, di->fw_image_sta);
|
|
|
+ priv->fw_pref = di->fw_image_sta;
|
|
|
} else if (!ap_mode_default && !di->fw_image_sta && di->fw_image_ap) {
|
|
|
printk(KERN_WARNING "STA fw is unavailable. Using AP fw.");
|
|
|
- rc = mwl8k_init_firmware(hw, di->fw_image_ap);
|
|
|
- } else
|
|
|
- rc = mwl8k_init_firmware(hw, di->fw_image_sta);
|
|
|
+ priv->fw_pref = di->fw_image_ap;
|
|
|
+ }
|
|
|
+ rc = mwl8k_init_firmware(hw, priv->fw_pref, true);
|
|
|
if (rc)
|
|
|
goto err_stop_firmware;
|
|
|
-
|
|
|
- rc = mwl8k_firmware_load_success(priv);
|
|
|
- if (!rc)
|
|
|
- return rc;
|
|
|
+ return rc;
|
|
|
|
|
|
err_stop_firmware:
|
|
|
mwl8k_hw_reset(priv);
|
|
@@ -4385,6 +4526,13 @@ static void __devexit mwl8k_remove(struct pci_dev *pdev)
|
|
|
return;
|
|
|
priv = hw->priv;
|
|
|
|
|
|
+ wait_for_completion(&priv->firmware_loading_complete);
|
|
|
+
|
|
|
+ if (priv->fw_state == FW_STATE_ERROR) {
|
|
|
+ mwl8k_hw_reset(priv);
|
|
|
+ goto unmap;
|
|
|
+ }
|
|
|
+
|
|
|
ieee80211_stop_queues(hw);
|
|
|
|
|
|
ieee80211_unregister_hw(hw);
|
|
@@ -4407,6 +4555,7 @@ static void __devexit mwl8k_remove(struct pci_dev *pdev)
|
|
|
|
|
|
pci_free_consistent(priv->pdev, 4, priv->cookie, priv->cookie_dma);
|
|
|
|
|
|
+unmap:
|
|
|
pci_iounmap(pdev, priv->regs);
|
|
|
pci_iounmap(pdev, priv->sram);
|
|
|
pci_set_drvdata(pdev, NULL);
|