diff options
author | Yogesh Ashok Powar <yogeshp@marvell.com> | 2011-12-30 06:05:27 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2012-01-04 14:31:47 -0500 |
commit | 6b6accc3832e5a124eeb144c6b3b1ff65b503d2b (patch) | |
tree | ff884be4e816a511b601696ce029977823d0e412 /drivers/net/wireless | |
parent | 7f28197560116f08c4c27342974f9e64cab2cbb1 (diff) |
mwl8k: Recover from firmware crash
In case of firmware crash, reload the firmware and reconfigure it
by triggering ieee80211_hw_restart; mac80211 utility function.
V2 Addressed following comments from Lennert:
- Stop the queues during reload
- Removed atomic_t declaration for hw_restart
- Extend the firmware reload support for sta firmware as well
- Other misc changes
Signed-off-by: Nishant Sarmukadam <nishants@marvell.com>
Signed-off-by: Yogesh Ashok Powar <yogeshp@marvell.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless')
-rw-r--r-- | drivers/net/wireless/mwl8k.c | 136 |
1 files changed, 129 insertions, 7 deletions
diff --git a/drivers/net/wireless/mwl8k.c b/drivers/net/wireless/mwl8k.c index 901cd79a061e..cf6927189682 100644 --- a/drivers/net/wireless/mwl8k.c +++ b/drivers/net/wireless/mwl8k.c | |||
@@ -198,6 +198,7 @@ struct mwl8k_priv { | |||
198 | /* firmware access */ | 198 | /* firmware access */ |
199 | struct mutex fw_mutex; | 199 | struct mutex fw_mutex; |
200 | struct task_struct *fw_mutex_owner; | 200 | struct task_struct *fw_mutex_owner; |
201 | struct task_struct *hw_restart_owner; | ||
201 | int fw_mutex_depth; | 202 | int fw_mutex_depth; |
202 | struct completion *hostcmd_wait; | 203 | struct completion *hostcmd_wait; |
203 | 204 | ||
@@ -262,6 +263,10 @@ struct mwl8k_priv { | |||
262 | */ | 263 | */ |
263 | struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_WMM_QUEUES]; | 264 | struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_WMM_QUEUES]; |
264 | 265 | ||
266 | /* To perform the task of reloading the firmware */ | ||
267 | struct work_struct fw_reload; | ||
268 | bool hw_restart_in_progress; | ||
269 | |||
265 | /* async firmware loading state */ | 270 | /* async firmware loading state */ |
266 | unsigned fw_state; | 271 | unsigned fw_state; |
267 | char *fw_pref; | 272 | char *fw_pref; |
@@ -1498,6 +1503,18 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw) | |||
1498 | 1503 | ||
1499 | might_sleep(); | 1504 | might_sleep(); |
1500 | 1505 | ||
1506 | /* Since fw restart is in progress, allow only the firmware | ||
1507 | * commands from the restart code and block the other | ||
1508 | * commands since they are going to fail in any case since | ||
1509 | * the firmware has crashed | ||
1510 | */ | ||
1511 | if (priv->hw_restart_in_progress) { | ||
1512 | if (priv->hw_restart_owner == current) | ||
1513 | return 0; | ||
1514 | else | ||
1515 | return -EBUSY; | ||
1516 | } | ||
1517 | |||
1501 | /* | 1518 | /* |
1502 | * The TX queues are stopped at this point, so this test | 1519 | * The TX queues are stopped at this point, so this test |
1503 | * doesn't need to take ->tx_lock. | 1520 | * doesn't need to take ->tx_lock. |
@@ -1541,6 +1558,8 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw) | |||
1541 | wiphy_err(hw->wiphy, "tx rings stuck for %d ms\n", | 1558 | wiphy_err(hw->wiphy, "tx rings stuck for %d ms\n", |
1542 | MWL8K_TX_WAIT_TIMEOUT_MS); | 1559 | MWL8K_TX_WAIT_TIMEOUT_MS); |
1543 | mwl8k_dump_tx_rings(hw); | 1560 | mwl8k_dump_tx_rings(hw); |
1561 | priv->hw_restart_in_progress = true; | ||
1562 | ieee80211_queue_work(hw, &priv->fw_reload); | ||
1544 | 1563 | ||
1545 | rc = -ETIMEDOUT; | 1564 | rc = -ETIMEDOUT; |
1546 | } | 1565 | } |
@@ -2058,7 +2077,9 @@ static int mwl8k_fw_lock(struct ieee80211_hw *hw) | |||
2058 | 2077 | ||
2059 | rc = mwl8k_tx_wait_empty(hw); | 2078 | rc = mwl8k_tx_wait_empty(hw); |
2060 | if (rc) { | 2079 | if (rc) { |
2061 | ieee80211_wake_queues(hw); | 2080 | if (!priv->hw_restart_in_progress) |
2081 | ieee80211_wake_queues(hw); | ||
2082 | |||
2062 | mutex_unlock(&priv->fw_mutex); | 2083 | mutex_unlock(&priv->fw_mutex); |
2063 | 2084 | ||
2064 | return rc; | 2085 | return rc; |
@@ -2077,7 +2098,9 @@ static void mwl8k_fw_unlock(struct ieee80211_hw *hw) | |||
2077 | struct mwl8k_priv *priv = hw->priv; | 2098 | struct mwl8k_priv *priv = hw->priv; |
2078 | 2099 | ||
2079 | if (!--priv->fw_mutex_depth) { | 2100 | if (!--priv->fw_mutex_depth) { |
2080 | ieee80211_wake_queues(hw); | 2101 | if (!priv->hw_restart_in_progress) |
2102 | ieee80211_wake_queues(hw); | ||
2103 | |||
2081 | priv->fw_mutex_owner = NULL; | 2104 | priv->fw_mutex_owner = NULL; |
2082 | mutex_unlock(&priv->fw_mutex); | 2105 | mutex_unlock(&priv->fw_mutex); |
2083 | } | 2106 | } |
@@ -4398,7 +4421,8 @@ static void mwl8k_stop(struct ieee80211_hw *hw) | |||
4398 | struct mwl8k_priv *priv = hw->priv; | 4421 | struct mwl8k_priv *priv = hw->priv; |
4399 | int i; | 4422 | int i; |
4400 | 4423 | ||
4401 | mwl8k_cmd_radio_disable(hw); | 4424 | if (!priv->hw_restart_in_progress) |
4425 | mwl8k_cmd_radio_disable(hw); | ||
4402 | 4426 | ||
4403 | ieee80211_stop_queues(hw); | 4427 | ieee80211_stop_queues(hw); |
4404 | 4428 | ||
@@ -4499,6 +4523,16 @@ static int mwl8k_add_interface(struct ieee80211_hw *hw, | |||
4499 | return 0; | 4523 | return 0; |
4500 | } | 4524 | } |
4501 | 4525 | ||
4526 | static void mwl8k_remove_vif(struct mwl8k_priv *priv, struct mwl8k_vif *vif) | ||
4527 | { | ||
4528 | /* Has ieee80211_restart_hw re-added the removed interfaces? */ | ||
4529 | if (!priv->macids_used) | ||
4530 | return; | ||
4531 | |||
4532 | priv->macids_used &= ~(1 << vif->macid); | ||
4533 | list_del(&vif->list); | ||
4534 | } | ||
4535 | |||
4502 | static void mwl8k_remove_interface(struct ieee80211_hw *hw, | 4536 | static void mwl8k_remove_interface(struct ieee80211_hw *hw, |
4503 | struct ieee80211_vif *vif) | 4537 | struct ieee80211_vif *vif) |
4504 | { | 4538 | { |
@@ -4510,8 +4544,54 @@ static void mwl8k_remove_interface(struct ieee80211_hw *hw, | |||
4510 | 4544 | ||
4511 | mwl8k_cmd_set_mac_addr(hw, vif, "\x00\x00\x00\x00\x00\x00"); | 4545 | mwl8k_cmd_set_mac_addr(hw, vif, "\x00\x00\x00\x00\x00\x00"); |
4512 | 4546 | ||
4513 | priv->macids_used &= ~(1 << mwl8k_vif->macid); | 4547 | mwl8k_remove_vif(priv, mwl8k_vif); |
4514 | list_del(&mwl8k_vif->list); | 4548 | } |
4549 | |||
4550 | static void mwl8k_hw_restart_work(struct work_struct *work) | ||
4551 | { | ||
4552 | struct mwl8k_priv *priv = | ||
4553 | container_of(work, struct mwl8k_priv, fw_reload); | ||
4554 | struct ieee80211_hw *hw = priv->hw; | ||
4555 | struct mwl8k_device_info *di; | ||
4556 | int rc; | ||
4557 | |||
4558 | /* If some command is waiting for a response, clear it */ | ||
4559 | if (priv->hostcmd_wait != NULL) { | ||
4560 | complete(priv->hostcmd_wait); | ||
4561 | priv->hostcmd_wait = NULL; | ||
4562 | } | ||
4563 | |||
4564 | priv->hw_restart_owner = current; | ||
4565 | di = priv->device_info; | ||
4566 | mwl8k_fw_lock(hw); | ||
4567 | |||
4568 | if (priv->ap_fw) | ||
4569 | rc = mwl8k_reload_firmware(hw, di->fw_image_ap); | ||
4570 | else | ||
4571 | rc = mwl8k_reload_firmware(hw, di->fw_image_sta); | ||
4572 | |||
4573 | if (rc) | ||
4574 | goto fail; | ||
4575 | |||
4576 | priv->hw_restart_owner = NULL; | ||
4577 | priv->hw_restart_in_progress = false; | ||
4578 | |||
4579 | /* | ||
4580 | * This unlock will wake up the queues and | ||
4581 | * also opens the command path for other | ||
4582 | * commands | ||
4583 | */ | ||
4584 | mwl8k_fw_unlock(hw); | ||
4585 | |||
4586 | ieee80211_restart_hw(hw); | ||
4587 | |||
4588 | wiphy_err(hw->wiphy, "Firmware restarted successfully\n"); | ||
4589 | |||
4590 | return; | ||
4591 | fail: | ||
4592 | mwl8k_fw_unlock(hw); | ||
4593 | |||
4594 | wiphy_err(hw->wiphy, "Firmware restart failed\n"); | ||
4515 | } | 4595 | } |
4516 | 4596 | ||
4517 | static int mwl8k_config(struct ieee80211_hw *hw, u32 changed) | 4597 | static int mwl8k_config(struct ieee80211_hw *hw, u32 changed) |
@@ -5024,7 +5104,11 @@ mwl8k_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, | |||
5024 | for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) { | 5104 | for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) { |
5025 | rc = mwl8k_check_ba(hw, stream); | 5105 | rc = mwl8k_check_ba(hw, stream); |
5026 | 5106 | ||
5027 | if (!rc) | 5107 | /* If HW restart is in progress mwl8k_post_cmd will |
5108 | * return -EBUSY. Avoid retrying mwl8k_check_ba in | ||
5109 | * such cases | ||
5110 | */ | ||
5111 | if (!rc || rc == -EBUSY) | ||
5028 | break; | 5112 | break; |
5029 | /* | 5113 | /* |
5030 | * HW queues take time to be flushed, give them | 5114 | * HW queues take time to be flushed, give them |
@@ -5263,12 +5347,15 @@ fail: | |||
5263 | mwl8k_release_firmware(priv); | 5347 | mwl8k_release_firmware(priv); |
5264 | } | 5348 | } |
5265 | 5349 | ||
5350 | #define MAX_RESTART_ATTEMPTS 1 | ||
5266 | static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image, | 5351 | static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image, |
5267 | bool nowait) | 5352 | bool nowait) |
5268 | { | 5353 | { |
5269 | struct mwl8k_priv *priv = hw->priv; | 5354 | struct mwl8k_priv *priv = hw->priv; |
5270 | int rc; | 5355 | int rc; |
5356 | int count = MAX_RESTART_ATTEMPTS; | ||
5271 | 5357 | ||
5358 | retry: | ||
5272 | /* Reset firmware and hardware */ | 5359 | /* Reset firmware and hardware */ |
5273 | mwl8k_hw_reset(priv); | 5360 | mwl8k_hw_reset(priv); |
5274 | 5361 | ||
@@ -5290,6 +5377,16 @@ static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image, | |||
5290 | /* Reclaim memory once firmware is successfully loaded */ | 5377 | /* Reclaim memory once firmware is successfully loaded */ |
5291 | mwl8k_release_firmware(priv); | 5378 | mwl8k_release_firmware(priv); |
5292 | 5379 | ||
5380 | if (rc && count) { | ||
5381 | /* FW did not start successfully; | ||
5382 | * lets try one more time | ||
5383 | */ | ||
5384 | count--; | ||
5385 | wiphy_err(hw->wiphy, "Trying to reload the firmware again\n"); | ||
5386 | msleep(20); | ||
5387 | goto retry; | ||
5388 | } | ||
5389 | |||
5293 | return rc; | 5390 | return rc; |
5294 | } | 5391 | } |
5295 | 5392 | ||
@@ -5365,7 +5462,14 @@ static int mwl8k_probe_hw(struct ieee80211_hw *hw) | |||
5365 | goto err_free_queues; | 5462 | goto err_free_queues; |
5366 | } | 5463 | } |
5367 | 5464 | ||
5368 | memset(priv->ampdu, 0, sizeof(priv->ampdu)); | 5465 | /* |
5466 | * When hw restart is requested, | ||
5467 | * mac80211 will take care of clearing | ||
5468 | * the ampdu streams, so do not clear | ||
5469 | * the ampdu state here | ||
5470 | */ | ||
5471 | if (!priv->hw_restart_in_progress) | ||
5472 | memset(priv->ampdu, 0, sizeof(priv->ampdu)); | ||
5369 | 5473 | ||
5370 | /* | 5474 | /* |
5371 | * Temporarily enable interrupts. Initial firmware host | 5475 | * Temporarily enable interrupts. Initial firmware host |
@@ -5439,10 +5543,20 @@ static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image) | |||
5439 | { | 5543 | { |
5440 | int i, rc = 0; | 5544 | int i, rc = 0; |
5441 | struct mwl8k_priv *priv = hw->priv; | 5545 | struct mwl8k_priv *priv = hw->priv; |
5546 | struct mwl8k_vif *vif, *tmp_vif; | ||
5442 | 5547 | ||
5443 | mwl8k_stop(hw); | 5548 | mwl8k_stop(hw); |
5444 | mwl8k_rxq_deinit(hw, 0); | 5549 | mwl8k_rxq_deinit(hw, 0); |
5445 | 5550 | ||
5551 | /* | ||
5552 | * All the existing interfaces are re-added by the ieee80211_reconfig; | ||
5553 | * which means driver should remove existing interfaces before calling | ||
5554 | * ieee80211_restart_hw | ||
5555 | */ | ||
5556 | if (priv->hw_restart_in_progress) | ||
5557 | list_for_each_entry_safe(vif, tmp_vif, &priv->vif_list, list) | ||
5558 | mwl8k_remove_vif(priv, vif); | ||
5559 | |||
5446 | for (i = 0; i < mwl8k_tx_queues(priv); i++) | 5560 | for (i = 0; i < mwl8k_tx_queues(priv); i++) |
5447 | mwl8k_txq_deinit(hw, i); | 5561 | mwl8k_txq_deinit(hw, i); |
5448 | 5562 | ||
@@ -5454,6 +5568,9 @@ static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image) | |||
5454 | if (rc) | 5568 | if (rc) |
5455 | goto fail; | 5569 | goto fail; |
5456 | 5570 | ||
5571 | if (priv->hw_restart_in_progress) | ||
5572 | return rc; | ||
5573 | |||
5457 | rc = mwl8k_start(hw); | 5574 | rc = mwl8k_start(hw); |
5458 | if (rc) | 5575 | if (rc) |
5459 | goto fail; | 5576 | goto fail; |
@@ -5524,6 +5641,8 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv) | |||
5524 | INIT_WORK(&priv->finalize_join_worker, mwl8k_finalize_join_worker); | 5641 | INIT_WORK(&priv->finalize_join_worker, mwl8k_finalize_join_worker); |
5525 | /* Handle watchdog ba events */ | 5642 | /* Handle watchdog ba events */ |
5526 | INIT_WORK(&priv->watchdog_ba_handle, mwl8k_watchdog_ba_events); | 5643 | INIT_WORK(&priv->watchdog_ba_handle, mwl8k_watchdog_ba_events); |
5644 | /* To reload the firmware if it crashes */ | ||
5645 | INIT_WORK(&priv->fw_reload, mwl8k_hw_restart_work); | ||
5527 | 5646 | ||
5528 | /* TX reclaim and RX tasklets. */ | 5647 | /* TX reclaim and RX tasklets. */ |
5529 | tasklet_init(&priv->poll_tx_task, mwl8k_tx_poll, (unsigned long)hw); | 5648 | tasklet_init(&priv->poll_tx_task, mwl8k_tx_poll, (unsigned long)hw); |
@@ -5667,6 +5786,9 @@ static int __devinit mwl8k_probe(struct pci_dev *pdev, | |||
5667 | rc = mwl8k_init_firmware(hw, priv->fw_pref, true); | 5786 | rc = mwl8k_init_firmware(hw, priv->fw_pref, true); |
5668 | if (rc) | 5787 | if (rc) |
5669 | goto err_stop_firmware; | 5788 | goto err_stop_firmware; |
5789 | |||
5790 | priv->hw_restart_in_progress = false; | ||
5791 | |||
5670 | return rc; | 5792 | return rc; |
5671 | 5793 | ||
5672 | err_stop_firmware: | 5794 | err_stop_firmware: |