diff options
author | Arik Nemtsov <arik@wizery.com> | 2012-03-03 15:18:00 -0500 |
---|---|---|
committer | Luciano Coelho <coelho@ti.com> | 2012-03-08 07:25:18 -0500 |
commit | 55df5afb13718cda49128fa5985556df91d07765 (patch) | |
tree | bb6c9279e12b90af623ba6ea4b17ea85f679e251 | |
parent | 8ccd16e6cb17a1e961617cc67798bbb222e4cd13 (diff) |
wl12xx: implement SW Tx watchdog
Track freed FW blocks during Tx. If no blocks were freed during a
predefined timeout, initiate a HW recovery. This helps in situations
when the FW watchdog fails.
Don't trigger recovery during activities that can temporarily stop
Tx. This includes:
- scanning
- buffering packets for sleeping stations (AP role)
- ROC on any role
Signed-off-by: Arik Nemtsov <arik@wizery.com>
Signed-off-by: Eliad Peller <eliad@wizery.com>
Signed-off-by: Luciano Coelho <coelho@ti.com>
-rw-r--r-- | drivers/net/wireless/wl12xx/cmd.c | 8 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/conf.h | 3 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/main.c | 110 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/scan.c | 6 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/tx.c | 4 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/tx.h | 1 | ||||
-rw-r--r-- | drivers/net/wireless/wl12xx/wl12xx.h | 3 |
7 files changed, 135 insertions, 0 deletions
diff --git a/drivers/net/wireless/wl12xx/cmd.c b/drivers/net/wireless/wl12xx/cmd.c index 1ef212f6440f..3414fc11e9ba 100644 --- a/drivers/net/wireless/wl12xx/cmd.c +++ b/drivers/net/wireless/wl12xx/cmd.c | |||
@@ -1818,6 +1818,14 @@ int wl12xx_croc(struct wl1271 *wl, u8 role_id) | |||
1818 | goto out; | 1818 | goto out; |
1819 | 1819 | ||
1820 | __clear_bit(role_id, wl->roc_map); | 1820 | __clear_bit(role_id, wl->roc_map); |
1821 | |||
1822 | /* | ||
1823 | * Rearm the tx watchdog when removing the last ROC. This prevents | ||
1824 | * recoveries due to just finished ROCs - when Tx hasn't yet had | ||
1825 | * a chance to get out. | ||
1826 | */ | ||
1827 | if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) >= WL12XX_MAX_ROLES) | ||
1828 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
1821 | out: | 1829 | out: |
1822 | return ret; | 1830 | return ret; |
1823 | } | 1831 | } |
diff --git a/drivers/net/wireless/wl12xx/conf.h b/drivers/net/wireless/wl12xx/conf.h index cc50faaf03d1..3e581e19424c 100644 --- a/drivers/net/wireless/wl12xx/conf.h +++ b/drivers/net/wireless/wl12xx/conf.h | |||
@@ -690,6 +690,9 @@ struct conf_tx_settings { | |||
690 | */ | 690 | */ |
691 | u8 tmpl_short_retry_limit; | 691 | u8 tmpl_short_retry_limit; |
692 | u8 tmpl_long_retry_limit; | 692 | u8 tmpl_long_retry_limit; |
693 | |||
694 | /* Time in ms for Tx watchdog timer to expire */ | ||
695 | u32 tx_watchdog_timeout; | ||
693 | }; | 696 | }; |
694 | 697 | ||
695 | enum { | 698 | enum { |
diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 95a76a5f9eeb..39002363611e 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c | |||
@@ -217,6 +217,7 @@ static struct conf_drv_settings default_conf = { | |||
217 | .basic_rate_5 = CONF_HW_BIT_RATE_6MBPS, | 217 | .basic_rate_5 = CONF_HW_BIT_RATE_6MBPS, |
218 | .tmpl_short_retry_limit = 10, | 218 | .tmpl_short_retry_limit = 10, |
219 | .tmpl_long_retry_limit = 10, | 219 | .tmpl_long_retry_limit = 10, |
220 | .tx_watchdog_timeout = 5000, | ||
220 | }, | 221 | }, |
221 | .conn = { | 222 | .conn = { |
222 | .wake_up_event = CONF_WAKE_UP_EVENT_DTIM, | 223 | .wake_up_event = CONF_WAKE_UP_EVENT_DTIM, |
@@ -553,6 +554,80 @@ static void wl1271_rx_streaming_timer(unsigned long data) | |||
553 | ieee80211_queue_work(wl->hw, &wlvif->rx_streaming_disable_work); | 554 | ieee80211_queue_work(wl->hw, &wlvif->rx_streaming_disable_work); |
554 | } | 555 | } |
555 | 556 | ||
557 | /* wl->mutex must be taken */ | ||
558 | void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl) | ||
559 | { | ||
560 | /* if the watchdog is not armed, don't do anything */ | ||
561 | if (wl->tx_allocated_blocks == 0) | ||
562 | return; | ||
563 | |||
564 | cancel_delayed_work(&wl->tx_watchdog_work); | ||
565 | ieee80211_queue_delayed_work(wl->hw, &wl->tx_watchdog_work, | ||
566 | msecs_to_jiffies(wl->conf.tx.tx_watchdog_timeout)); | ||
567 | } | ||
568 | |||
569 | static void wl12xx_tx_watchdog_work(struct work_struct *work) | ||
570 | { | ||
571 | struct delayed_work *dwork; | ||
572 | struct wl1271 *wl; | ||
573 | |||
574 | dwork = container_of(work, struct delayed_work, work); | ||
575 | wl = container_of(dwork, struct wl1271, tx_watchdog_work); | ||
576 | |||
577 | mutex_lock(&wl->mutex); | ||
578 | |||
579 | if (unlikely(wl->state == WL1271_STATE_OFF)) | ||
580 | goto out; | ||
581 | |||
582 | /* Tx went out in the meantime - everything is ok */ | ||
583 | if (unlikely(wl->tx_allocated_blocks == 0)) | ||
584 | goto out; | ||
585 | |||
586 | /* | ||
587 | * if a ROC is in progress, we might not have any Tx for a long | ||
588 | * time (e.g. pending Tx on the non-ROC channels) | ||
589 | */ | ||
590 | if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) { | ||
591 | wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to ROC", | ||
592 | wl->conf.tx.tx_watchdog_timeout); | ||
593 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
594 | goto out; | ||
595 | } | ||
596 | |||
597 | /* | ||
598 | * if a scan is in progress, we might not have any Tx for a long | ||
599 | * time | ||
600 | */ | ||
601 | if (wl->scan.state != WL1271_SCAN_STATE_IDLE) { | ||
602 | wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to scan", | ||
603 | wl->conf.tx.tx_watchdog_timeout); | ||
604 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
605 | goto out; | ||
606 | } | ||
607 | |||
608 | /* | ||
609 | * AP might cache a frame for a long time for a sleeping station, | ||
610 | * so rearm the timer if there's an AP interface with stations. If | ||
611 | * Tx is genuinely stuck we will most hopefully discover it when all | ||
612 | * stations are removed due to inactivity. | ||
613 | */ | ||
614 | if (wl->active_sta_count) { | ||
615 | wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms. AP has " | ||
616 | " %d stations", | ||
617 | wl->conf.tx.tx_watchdog_timeout, | ||
618 | wl->active_sta_count); | ||
619 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
620 | goto out; | ||
621 | } | ||
622 | |||
623 | wl1271_error("Tx stuck (in FW) for %d ms. Starting recovery", | ||
624 | wl->conf.tx.tx_watchdog_timeout); | ||
625 | wl12xx_queue_recovery_work(wl); | ||
626 | |||
627 | out: | ||
628 | mutex_unlock(&wl->mutex); | ||
629 | } | ||
630 | |||
556 | static void wl1271_conf_init(struct wl1271 *wl) | 631 | static void wl1271_conf_init(struct wl1271 *wl) |
557 | { | 632 | { |
558 | 633 | ||
@@ -745,6 +820,18 @@ static void wl12xx_fw_status(struct wl1271 *wl, | |||
745 | 820 | ||
746 | wl->tx_allocated_blocks -= freed_blocks; | 821 | wl->tx_allocated_blocks -= freed_blocks; |
747 | 822 | ||
823 | /* | ||
824 | * If the FW freed some blocks: | ||
825 | * If we still have allocated blocks - re-arm the timer, Tx is | ||
826 | * not stuck. Otherwise, cancel the timer (no Tx currently). | ||
827 | */ | ||
828 | if (freed_blocks) { | ||
829 | if (wl->tx_allocated_blocks) | ||
830 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
831 | else | ||
832 | cancel_delayed_work(&wl->tx_watchdog_work); | ||
833 | } | ||
834 | |||
748 | avail = le32_to_cpu(status->tx_total) - wl->tx_allocated_blocks; | 835 | avail = le32_to_cpu(status->tx_total) - wl->tx_allocated_blocks; |
749 | 836 | ||
750 | /* | 837 | /* |
@@ -1418,6 +1505,7 @@ int wl1271_plt_stop(struct wl1271 *wl) | |||
1418 | cancel_work_sync(&wl->netstack_work); | 1505 | cancel_work_sync(&wl->netstack_work); |
1419 | cancel_work_sync(&wl->recovery_work); | 1506 | cancel_work_sync(&wl->recovery_work); |
1420 | cancel_delayed_work_sync(&wl->elp_work); | 1507 | cancel_delayed_work_sync(&wl->elp_work); |
1508 | cancel_delayed_work_sync(&wl->tx_watchdog_work); | ||
1421 | 1509 | ||
1422 | mutex_lock(&wl->mutex); | 1510 | mutex_lock(&wl->mutex); |
1423 | wl1271_power_off(wl); | 1511 | wl1271_power_off(wl); |
@@ -1789,6 +1877,7 @@ static void wl1271_op_stop(struct ieee80211_hw *hw) | |||
1789 | cancel_work_sync(&wl->netstack_work); | 1877 | cancel_work_sync(&wl->netstack_work); |
1790 | cancel_work_sync(&wl->tx_work); | 1878 | cancel_work_sync(&wl->tx_work); |
1791 | cancel_delayed_work_sync(&wl->elp_work); | 1879 | cancel_delayed_work_sync(&wl->elp_work); |
1880 | cancel_delayed_work_sync(&wl->tx_watchdog_work); | ||
1792 | 1881 | ||
1793 | /* let's notify MAC80211 about the remaining pending TX frames */ | 1882 | /* let's notify MAC80211 about the remaining pending TX frames */ |
1794 | wl12xx_tx_reset(wl, true); | 1883 | wl12xx_tx_reset(wl, true); |
@@ -2218,6 +2307,12 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl, | |||
2218 | 2307 | ||
2219 | if (wl->scan.state != WL1271_SCAN_STATE_IDLE && | 2308 | if (wl->scan.state != WL1271_SCAN_STATE_IDLE && |
2220 | wl->scan_vif == vif) { | 2309 | wl->scan_vif == vif) { |
2310 | /* | ||
2311 | * Rearm the tx watchdog just before idling scan. This | ||
2312 | * prevents just-finished scans from triggering the watchdog | ||
2313 | */ | ||
2314 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
2315 | |||
2221 | wl->scan.state = WL1271_SCAN_STATE_IDLE; | 2316 | wl->scan.state = WL1271_SCAN_STATE_IDLE; |
2222 | memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); | 2317 | memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); |
2223 | wl->scan_vif = NULL; | 2318 | wl->scan_vif = NULL; |
@@ -3129,6 +3224,13 @@ static void wl1271_op_cancel_hw_scan(struct ieee80211_hw *hw, | |||
3129 | if (ret < 0) | 3224 | if (ret < 0) |
3130 | goto out_sleep; | 3225 | goto out_sleep; |
3131 | } | 3226 | } |
3227 | |||
3228 | /* | ||
3229 | * Rearm the tx watchdog just before idling scan. This | ||
3230 | * prevents just-finished scans from triggering the watchdog | ||
3231 | */ | ||
3232 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
3233 | |||
3132 | wl->scan.state = WL1271_SCAN_STATE_IDLE; | 3234 | wl->scan.state = WL1271_SCAN_STATE_IDLE; |
3133 | memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); | 3235 | memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); |
3134 | wl->scan_vif = NULL; | 3236 | wl->scan_vif = NULL; |
@@ -4138,6 +4240,13 @@ void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid) | |||
4138 | __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); | 4240 | __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map); |
4139 | wl12xx_free_link(wl, wlvif, &hlid); | 4241 | wl12xx_free_link(wl, wlvif, &hlid); |
4140 | wl->active_sta_count--; | 4242 | wl->active_sta_count--; |
4243 | |||
4244 | /* | ||
4245 | * rearm the tx watchdog when the last STA is freed - give the FW a | ||
4246 | * chance to return STA-buffered packets before complaining. | ||
4247 | */ | ||
4248 | if (wl->active_sta_count == 0) | ||
4249 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
4141 | } | 4250 | } |
4142 | 4251 | ||
4143 | static int wl12xx_sta_add(struct wl1271 *wl, | 4252 | static int wl12xx_sta_add(struct wl1271 *wl, |
@@ -5212,6 +5321,7 @@ static struct ieee80211_hw *wl1271_alloc_hw(void) | |||
5212 | INIT_WORK(&wl->tx_work, wl1271_tx_work); | 5321 | INIT_WORK(&wl->tx_work, wl1271_tx_work); |
5213 | INIT_WORK(&wl->recovery_work, wl1271_recovery_work); | 5322 | INIT_WORK(&wl->recovery_work, wl1271_recovery_work); |
5214 | INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work); | 5323 | INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work); |
5324 | INIT_DELAYED_WORK(&wl->tx_watchdog_work, wl12xx_tx_watchdog_work); | ||
5215 | 5325 | ||
5216 | wl->freezable_wq = create_freezable_workqueue("wl12xx_wq"); | 5326 | wl->freezable_wq = create_freezable_workqueue("wl12xx_wq"); |
5217 | if (!wl->freezable_wq) { | 5327 | if (!wl->freezable_wq) { |
diff --git a/drivers/net/wireless/wl12xx/scan.c b/drivers/net/wireless/wl12xx/scan.c index e43a6b2c1d91..fcba055ef196 100644 --- a/drivers/net/wireless/wl12xx/scan.c +++ b/drivers/net/wireless/wl12xx/scan.c | |||
@@ -55,6 +55,12 @@ void wl1271_scan_complete_work(struct work_struct *work) | |||
55 | vif = wl->scan_vif; | 55 | vif = wl->scan_vif; |
56 | wlvif = wl12xx_vif_to_data(vif); | 56 | wlvif = wl12xx_vif_to_data(vif); |
57 | 57 | ||
58 | /* | ||
59 | * Rearm the tx watchdog just before idling scan. This | ||
60 | * prevents just-finished scans from triggering the watchdog | ||
61 | */ | ||
62 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
63 | |||
58 | wl->scan.state = WL1271_SCAN_STATE_IDLE; | 64 | wl->scan.state = WL1271_SCAN_STATE_IDLE; |
59 | memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); | 65 | memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch)); |
60 | wl->scan.req = NULL; | 66 | wl->scan.req = NULL; |
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c index 8f78fddcb723..43ae49143d68 100644 --- a/drivers/net/wireless/wl12xx/tx.c +++ b/drivers/net/wireless/wl12xx/tx.c | |||
@@ -226,6 +226,10 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct wl12xx_vif *wlvif, | |||
226 | wl->tx_blocks_available -= total_blocks; | 226 | wl->tx_blocks_available -= total_blocks; |
227 | wl->tx_allocated_blocks += total_blocks; | 227 | wl->tx_allocated_blocks += total_blocks; |
228 | 228 | ||
229 | /* If the FW was empty before, arm the Tx watchdog */ | ||
230 | if (wl->tx_allocated_blocks == total_blocks) | ||
231 | wl12xx_rearm_tx_watchdog_locked(wl); | ||
232 | |||
229 | ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); | 233 | ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); |
230 | wl->tx_allocated_pkts[ac]++; | 234 | wl->tx_allocated_pkts[ac]++; |
231 | 235 | ||
diff --git a/drivers/net/wireless/wl12xx/tx.h b/drivers/net/wireless/wl12xx/tx.h index e3977b55a710..5cf8c32d40d1 100644 --- a/drivers/net/wireless/wl12xx/tx.h +++ b/drivers/net/wireless/wl12xx/tx.h | |||
@@ -227,5 +227,6 @@ void wl12xx_rearm_rx_streaming(struct wl1271 *wl, unsigned long *active_hlids); | |||
227 | 227 | ||
228 | /* from main.c */ | 228 | /* from main.c */ |
229 | void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid); | 229 | void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid); |
230 | void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl); | ||
230 | 231 | ||
231 | #endif | 232 | #endif |
diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 6e13a3073e9f..749a15a75d38 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h | |||
@@ -495,6 +495,9 @@ struct wl1271 { | |||
495 | 495 | ||
496 | /* last wlvif we transmitted from */ | 496 | /* last wlvif we transmitted from */ |
497 | struct wl12xx_vif *last_wlvif; | 497 | struct wl12xx_vif *last_wlvif; |
498 | |||
499 | /* work to fire when Tx is stuck */ | ||
500 | struct delayed_work tx_watchdog_work; | ||
498 | }; | 501 | }; |
499 | 502 | ||
500 | struct wl1271_station { | 503 | struct wl1271_station { |