wl12xx: implement SW Tx watchdog
authorArik Nemtsov <arik@wizery.com>
Sat, 3 Mar 2012 20:18:00 +0000 (22:18 +0200)
committerLuciano Coelho <coelho@ti.com>
Thu, 8 Mar 2012 12:25:18 +0000 (14:25 +0200)
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>
drivers/net/wireless/wl12xx/cmd.c
drivers/net/wireless/wl12xx/conf.h
drivers/net/wireless/wl12xx/main.c
drivers/net/wireless/wl12xx/scan.c
drivers/net/wireless/wl12xx/tx.c
drivers/net/wireless/wl12xx/tx.h
drivers/net/wireless/wl12xx/wl12xx.h

index 1ef212f..3414fc1 100644 (file)
@@ -1818,6 +1818,14 @@ int wl12xx_croc(struct wl1271 *wl, u8 role_id)
                goto out;
 
        __clear_bit(role_id, wl->roc_map);
+
+       /*
+        * Rearm the tx watchdog when removing the last ROC. This prevents
+        * recoveries due to just finished ROCs - when Tx hasn't yet had
+        * a chance to get out.
+        */
+       if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) >= WL12XX_MAX_ROLES)
+               wl12xx_rearm_tx_watchdog_locked(wl);
 out:
        return ret;
 }
index cc50faa..3e581e1 100644 (file)
@@ -690,6 +690,9 @@ struct conf_tx_settings {
         */
        u8 tmpl_short_retry_limit;
        u8 tmpl_long_retry_limit;
+
+       /* Time in ms for Tx watchdog timer to expire */
+       u32 tx_watchdog_timeout;
 };
 
 enum {
index 95a76a5..3900236 100644 (file)
@@ -217,6 +217,7 @@ static struct conf_drv_settings default_conf = {
                .basic_rate_5                = CONF_HW_BIT_RATE_6MBPS,
                .tmpl_short_retry_limit      = 10,
                .tmpl_long_retry_limit       = 10,
+               .tx_watchdog_timeout         = 5000,
        },
        .conn = {
                .wake_up_event               = CONF_WAKE_UP_EVENT_DTIM,
@@ -553,6 +554,80 @@ static void wl1271_rx_streaming_timer(unsigned long data)
        ieee80211_queue_work(wl->hw, &wlvif->rx_streaming_disable_work);
 }
 
+/* wl->mutex must be taken */
+void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl)
+{
+       /* if the watchdog is not armed, don't do anything */
+       if (wl->tx_allocated_blocks == 0)
+               return;
+
+       cancel_delayed_work(&wl->tx_watchdog_work);
+       ieee80211_queue_delayed_work(wl->hw, &wl->tx_watchdog_work,
+               msecs_to_jiffies(wl->conf.tx.tx_watchdog_timeout));
+}
+
+static void wl12xx_tx_watchdog_work(struct work_struct *work)
+{
+       struct delayed_work *dwork;
+       struct wl1271 *wl;
+
+       dwork = container_of(work, struct delayed_work, work);
+       wl = container_of(dwork, struct wl1271, tx_watchdog_work);
+
+       mutex_lock(&wl->mutex);
+
+       if (unlikely(wl->state == WL1271_STATE_OFF))
+               goto out;
+
+       /* Tx went out in the meantime - everything is ok */
+       if (unlikely(wl->tx_allocated_blocks == 0))
+               goto out;
+
+       /*
+        * if a ROC is in progress, we might not have any Tx for a long
+        * time (e.g. pending Tx on the non-ROC channels)
+        */
+       if (find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) {
+               wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to ROC",
+                            wl->conf.tx.tx_watchdog_timeout);
+               wl12xx_rearm_tx_watchdog_locked(wl);
+               goto out;
+       }
+
+       /*
+        * if a scan is in progress, we might not have any Tx for a long
+        * time
+        */
+       if (wl->scan.state != WL1271_SCAN_STATE_IDLE) {
+               wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms due to scan",
+                            wl->conf.tx.tx_watchdog_timeout);
+               wl12xx_rearm_tx_watchdog_locked(wl);
+               goto out;
+       }
+
+       /*
+       * AP might cache a frame for a long time for a sleeping station,
+       * so rearm the timer if there's an AP interface with stations. If
+       * Tx is genuinely stuck we will most hopefully discover it when all
+       * stations are removed due to inactivity.
+       */
+       if (wl->active_sta_count) {
+               wl1271_debug(DEBUG_TX, "No Tx (in FW) for %d ms. AP has "
+                            " %d stations",
+                             wl->conf.tx.tx_watchdog_timeout,
+                             wl->active_sta_count);
+               wl12xx_rearm_tx_watchdog_locked(wl);
+               goto out;
+       }
+
+       wl1271_error("Tx stuck (in FW) for %d ms. Starting recovery",
+                    wl->conf.tx.tx_watchdog_timeout);
+       wl12xx_queue_recovery_work(wl);
+
+out:
+       mutex_unlock(&wl->mutex);
+}
+
 static void wl1271_conf_init(struct wl1271 *wl)
 {
 
@@ -745,6 +820,18 @@ static void wl12xx_fw_status(struct wl1271 *wl,
 
        wl->tx_allocated_blocks -= freed_blocks;
 
+       /*
+        * If the FW freed some blocks:
+        * If we still have allocated blocks - re-arm the timer, Tx is
+        * not stuck. Otherwise, cancel the timer (no Tx currently).
+        */
+       if (freed_blocks) {
+               if (wl->tx_allocated_blocks)
+                       wl12xx_rearm_tx_watchdog_locked(wl);
+               else
+                       cancel_delayed_work(&wl->tx_watchdog_work);
+       }
+
        avail = le32_to_cpu(status->tx_total) - wl->tx_allocated_blocks;
 
        /*
@@ -1418,6 +1505,7 @@ int wl1271_plt_stop(struct wl1271 *wl)
        cancel_work_sync(&wl->netstack_work);
        cancel_work_sync(&wl->recovery_work);
        cancel_delayed_work_sync(&wl->elp_work);
+       cancel_delayed_work_sync(&wl->tx_watchdog_work);
 
        mutex_lock(&wl->mutex);
        wl1271_power_off(wl);
@@ -1789,6 +1877,7 @@ static void wl1271_op_stop(struct ieee80211_hw *hw)
        cancel_work_sync(&wl->netstack_work);
        cancel_work_sync(&wl->tx_work);
        cancel_delayed_work_sync(&wl->elp_work);
+       cancel_delayed_work_sync(&wl->tx_watchdog_work);
 
        /* let's notify MAC80211 about the remaining pending TX frames */
        wl12xx_tx_reset(wl, true);
@@ -2218,6 +2307,12 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl,
 
        if (wl->scan.state != WL1271_SCAN_STATE_IDLE &&
            wl->scan_vif == vif) {
+               /*
+                * Rearm the tx watchdog just before idling scan. This
+                * prevents just-finished scans from triggering the watchdog
+                */
+               wl12xx_rearm_tx_watchdog_locked(wl);
+
                wl->scan.state = WL1271_SCAN_STATE_IDLE;
                memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch));
                wl->scan_vif = NULL;
@@ -3129,6 +3224,13 @@ static void wl1271_op_cancel_hw_scan(struct ieee80211_hw *hw,
                if (ret < 0)
                        goto out_sleep;
        }
+
+       /*
+        * Rearm the tx watchdog just before idling scan. This
+        * prevents just-finished scans from triggering the watchdog
+        */
+       wl12xx_rearm_tx_watchdog_locked(wl);
+
        wl->scan.state = WL1271_SCAN_STATE_IDLE;
        memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch));
        wl->scan_vif = NULL;
@@ -4138,6 +4240,13 @@ void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid)
        __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map);
        wl12xx_free_link(wl, wlvif, &hlid);
        wl->active_sta_count--;
+
+       /*
+        * rearm the tx watchdog when the last STA is freed - give the FW a
+        * chance to return STA-buffered packets before complaining.
+        */
+       if (wl->active_sta_count == 0)
+               wl12xx_rearm_tx_watchdog_locked(wl);
 }
 
 static int wl12xx_sta_add(struct wl1271 *wl,
@@ -5212,6 +5321,7 @@ static struct ieee80211_hw *wl1271_alloc_hw(void)
        INIT_WORK(&wl->tx_work, wl1271_tx_work);
        INIT_WORK(&wl->recovery_work, wl1271_recovery_work);
        INIT_DELAYED_WORK(&wl->scan_complete_work, wl1271_scan_complete_work);
+       INIT_DELAYED_WORK(&wl->tx_watchdog_work, wl12xx_tx_watchdog_work);
 
        wl->freezable_wq = create_freezable_workqueue("wl12xx_wq");
        if (!wl->freezable_wq) {
index e43a6b2..fcba055 100644 (file)
@@ -55,6 +55,12 @@ void wl1271_scan_complete_work(struct work_struct *work)
        vif = wl->scan_vif;
        wlvif = wl12xx_vif_to_data(vif);
 
+       /*
+        * Rearm the tx watchdog just before idling scan. This
+        * prevents just-finished scans from triggering the watchdog
+        */
+       wl12xx_rearm_tx_watchdog_locked(wl);
+
        wl->scan.state = WL1271_SCAN_STATE_IDLE;
        memset(wl->scan.scanned_ch, 0, sizeof(wl->scan.scanned_ch));
        wl->scan.req = NULL;
index 8f78fdd..43ae491 100644 (file)
@@ -226,6 +226,10 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct wl12xx_vif *wlvif,
                wl->tx_blocks_available -= total_blocks;
                wl->tx_allocated_blocks += total_blocks;
 
+               /* If the FW was empty before, arm the Tx watchdog */
+               if (wl->tx_allocated_blocks == total_blocks)
+                       wl12xx_rearm_tx_watchdog_locked(wl);
+
                ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb));
                wl->tx_allocated_pkts[ac]++;
 
index e3977b5..5cf8c32 100644 (file)
@@ -227,5 +227,6 @@ void wl12xx_rearm_rx_streaming(struct wl1271 *wl, unsigned long *active_hlids);
 
 /* from main.c */
 void wl1271_free_sta(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 hlid);
+void wl12xx_rearm_tx_watchdog_locked(struct wl1271 *wl);
 
 #endif
index 6e13a30..749a15a 100644 (file)
@@ -495,6 +495,9 @@ struct wl1271 {
 
        /* last wlvif we transmitted from */
        struct wl12xx_vif *last_wlvif;
+
+       /* work to fire when Tx is stuck */
+       struct delayed_work tx_watchdog_work;
 };
 
 struct wl1271_station {