rt2x00: Implement watchdog monitoring
authorIvo van Doorn <ivdoorn@gmail.com>
Sun, 11 Jul 2010 10:25:46 +0000 (12:25 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 12 Jul 2010 20:05:34 +0000 (16:05 -0400)
Implement watchdog monitoring for USB devices (PCI support can
be added later). This will determine if URBs being uploaded to
the hardware are actually returning. Both rt2500usb and rt2800usb
have shown that URBs being uploaded can remain hanging without
being released by the hardware.
By using this watchdog, a queue can be reset when this occurs.
For rt2800usb it has been tested that the connection is preserved
even though this interruption.

Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/rt2x00/rt2500usb.c
drivers/net/wireless/rt2x00/rt2800usb.c
drivers/net/wireless/rt2x00/rt2x00.h
drivers/net/wireless/rt2x00/rt2x00dev.c
drivers/net/wireless/rt2x00/rt2x00lib.h
drivers/net/wireless/rt2x00/rt2x00link.c
drivers/net/wireless/rt2x00/rt2x00queue.c
drivers/net/wireless/rt2x00/rt2x00queue.h
drivers/net/wireless/rt2x00/rt2x00usb.c
drivers/net/wireless/rt2x00/rt2x00usb.h
drivers/net/wireless/rt2x00/rt73usb.c

index 0b7888d43f37952e2647aace8b0dd8030a08bde3..009323e6c20f903eb95122c0bbcea76c7f6eb3aa 100644 (file)
@@ -1736,6 +1736,7 @@ static int rt2500usb_probe_hw(struct rt2x00_dev *rt2x00dev)
                __set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags);
                __set_bit(DRIVER_REQUIRE_COPY_IV, &rt2x00dev->flags);
        }
+       __set_bit(DRIVER_SUPPORT_WATCHDOG, &rt2x00dev->flags);
 
        /*
         * Set the rssi offset.
@@ -1772,6 +1773,7 @@ static const struct rt2x00lib_ops rt2500usb_rt2x00_ops = {
        .rfkill_poll            = rt2500usb_rfkill_poll,
        .link_stats             = rt2500usb_link_stats,
        .reset_tuner            = rt2500usb_reset_tuner,
+       .watchdog               = rt2x00usb_watchdog,
        .write_tx_desc          = rt2500usb_write_tx_desc,
        .write_beacon           = rt2500usb_write_beacon,
        .get_tx_data_len        = rt2500usb_get_tx_data_len,
index e4ab4db66ca0b4200d62c84224afe2fdff821964..5aa7563155cd30ca17ef4167929bda8df121d98f 100644 (file)
@@ -633,6 +633,7 @@ static int rt2800usb_probe_hw(struct rt2x00_dev *rt2x00dev)
        if (!modparam_nohwcrypt)
                __set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags);
        __set_bit(DRIVER_SUPPORT_LINK_TUNING, &rt2x00dev->flags);
+       __set_bit(DRIVER_SUPPORT_WATCHDOG, &rt2x00dev->flags);
 
        /*
         * Set the rssi offset.
@@ -655,6 +656,7 @@ static const struct rt2x00lib_ops rt2800usb_rt2x00_ops = {
        .link_stats             = rt2800_link_stats,
        .reset_tuner            = rt2800_reset_tuner,
        .link_tuner             = rt2800_link_tuner,
+       .watchdog               = rt2x00usb_watchdog,
        .write_tx_desc          = rt2800usb_write_tx_desc,
        .write_tx_data          = rt2800usb_write_tx_data,
        .write_beacon           = rt2800_write_beacon,
index bf5e3f37e7052721c17468bbee9874ee42517a8b..97b6261fee4f71cdca5ad3f7e345fce5c6feb6b4 100644 (file)
@@ -332,6 +332,11 @@ struct link {
         * Work structure for scheduling periodic link tuning.
         */
        struct delayed_work work;
+
+       /*
+        * Work structure for scheduling periodic watchdog monitoring.
+        */
+       struct delayed_work watchdog_work;
 };
 
 /*
@@ -543,6 +548,7 @@ struct rt2x00lib_ops {
                             struct link_qual *qual);
        void (*link_tuner) (struct rt2x00_dev *rt2x00dev,
                            struct link_qual *qual, const u32 count);
+       void (*watchdog) (struct rt2x00_dev *rt2x00dev);
 
        /*
         * TX control handlers
@@ -648,6 +654,7 @@ enum rt2x00_flags {
        DRIVER_SUPPORT_CONTROL_FILTERS,
        DRIVER_SUPPORT_CONTROL_FILTER_PSPOLL,
        DRIVER_SUPPORT_LINK_TUNING,
+       DRIVER_SUPPORT_WATCHDOG,
 
        /*
         * Driver configuration
index 12ee7bdedd02600aa61e5ee61362e47198b86151..0906e14b347fab45d0d23abe7542a1820dc29cc5 100644 (file)
@@ -69,6 +69,11 @@ int rt2x00lib_enable_radio(struct rt2x00_dev *rt2x00dev)
         */
        rt2x00lib_toggle_rx(rt2x00dev, STATE_RADIO_RX_ON);
 
+       /*
+        * Start watchdog monitoring.
+        */
+       rt2x00link_start_watchdog(rt2x00dev);
+
        /*
         * Start the TX queues.
         */
@@ -88,6 +93,11 @@ void rt2x00lib_disable_radio(struct rt2x00_dev *rt2x00dev)
        ieee80211_stop_queues(rt2x00dev->hw);
        rt2x00queue_stop_queues(rt2x00dev);
 
+       /*
+        * Stop watchdog monitoring.
+        */
+       rt2x00link_stop_watchdog(rt2x00dev);
+
        /*
         * Disable RX.
         */
index ed27de1de57bd02d635f0f3fe18a1b4e1415b14b..dc5c6574aaf4f13efb6f43213da5fce85d8040be 100644 (file)
@@ -30,6 +30,7 @@
 /*
  * Interval defines
  */
+#define WATCHDOG_INTERVAL      round_jiffies_relative(HZ)
 #define LINK_TUNE_INTERVAL     round_jiffies_relative(HZ)
 
 /*
@@ -257,11 +258,30 @@ void rt2x00link_stop_tuner(struct rt2x00_dev *rt2x00dev);
 void rt2x00link_reset_tuner(struct rt2x00_dev *rt2x00dev, bool antenna);
 
 /**
- * rt2x00link_register - Initialize link tuning functionality
+ * rt2x00link_start_watchdog - Start periodic watchdog monitoring
  * @rt2x00dev: Pointer to &struct rt2x00_dev.
  *
- * Initialize work structure and all link tuning related
- * parameters. This will not start the link tuning process itself.
+ * This start the watchdog periodic work, this work will
+ *be executed periodically until &rt2x00link_stop_watchdog has
+ * been called.
+ */
+void rt2x00link_start_watchdog(struct rt2x00_dev *rt2x00dev);
+
+/**
+ * rt2x00link_stop_watchdog - Stop periodic watchdog monitoring
+ * @rt2x00dev: Pointer to &struct rt2x00_dev.
+ *
+ * After this function completed the watchdog monitoring will not
+ * be running until &rt2x00link_start_watchdog is called.
+ */
+void rt2x00link_stop_watchdog(struct rt2x00_dev *rt2x00dev);
+
+/**
+ * rt2x00link_register - Initialize link tuning & watchdog functionality
+ * @rt2x00dev: Pointer to &struct rt2x00_dev.
+ *
+ * Initialize work structure and all link tuning and watchdog related
+ * parameters. This will not start the periodic work itself.
  */
 void rt2x00link_register(struct rt2x00_dev *rt2x00dev);
 
index 9acfc5c70389af87aa3cd2d333b0df5ebc7c3aab..666cef3f8472e3da35bfd98d6dd74eba1d8d162e 100644 (file)
@@ -407,7 +407,45 @@ static void rt2x00link_tuner(struct work_struct *work)
                                             &link->work, LINK_TUNE_INTERVAL);
 }
 
+void rt2x00link_start_watchdog(struct rt2x00_dev *rt2x00dev)
+{
+       struct link *link = &rt2x00dev->link;
+
+       if (!test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags) ||
+           !test_bit(DRIVER_SUPPORT_WATCHDOG, &rt2x00dev->flags))
+               return;
+
+       ieee80211_queue_delayed_work(rt2x00dev->hw,
+                                    &link->watchdog_work, WATCHDOG_INTERVAL);
+}
+
+void rt2x00link_stop_watchdog(struct rt2x00_dev *rt2x00dev)
+{
+       cancel_delayed_work_sync(&rt2x00dev->link.watchdog_work);
+}
+
+static void rt2x00link_watchdog(struct work_struct *work)
+{
+       struct rt2x00_dev *rt2x00dev =
+           container_of(work, struct rt2x00_dev, link.watchdog_work.work);
+       struct link *link = &rt2x00dev->link;
+
+       /*
+        * When the radio is shutting down we should
+        * immediately cease the watchdog monitoring.
+        */
+       if (!test_bit(DEVICE_STATE_ENABLED_RADIO, &rt2x00dev->flags))
+               return;
+
+       rt2x00dev->ops->lib->watchdog(rt2x00dev);
+
+       if (test_bit(DEVICE_STATE_PRESENT, &rt2x00dev->flags))
+               ieee80211_queue_delayed_work(rt2x00dev->hw,
+                                            &link->watchdog_work, WATCHDOG_INTERVAL);
+}
+
 void rt2x00link_register(struct rt2x00_dev *rt2x00dev)
 {
+       INIT_DELAYED_WORK(&rt2x00dev->link.watchdog_work, rt2x00link_watchdog);
        INIT_DELAYED_WORK(&rt2x00dev->link.work, rt2x00link_tuner);
 }
index 5097fe0f9f51aad29da46a42cb39c6ede182bbb7..a3401d301058870745879c6ec0b19fa2d29ebfda 100644 (file)
@@ -688,9 +688,11 @@ void rt2x00queue_index_inc(struct data_queue *queue, enum queue_index index)
 
        if (index == Q_INDEX) {
                queue->length++;
+               queue->last_index = jiffies;
        } else if (index == Q_INDEX_DONE) {
                queue->length--;
                queue->count++;
+               queue->last_index_done = jiffies;
        }
 
        spin_unlock_irqrestore(&queue->lock, irqflags);
@@ -704,6 +706,8 @@ static void rt2x00queue_reset(struct data_queue *queue)
 
        queue->count = 0;
        queue->length = 0;
+       queue->last_index = jiffies;
+       queue->last_index_done = jiffies;
        memset(queue->index, 0, sizeof(queue->index));
 
        spin_unlock_irqrestore(&queue->lock, irqflags);
index bd54f55a8cb9183ab746665bc0545cf46f5c4de4..191e7775a9c0b418f791a134ca13217b6f91b25d 100644 (file)
@@ -446,6 +446,8 @@ struct data_queue {
        enum data_queue_qid qid;
 
        spinlock_t lock;
+       unsigned long last_index;
+       unsigned long last_index_done;
        unsigned int count;
        unsigned short limit;
        unsigned short threshold;
@@ -598,6 +600,15 @@ static inline int rt2x00queue_threshold(struct data_queue *queue)
        return rt2x00queue_available(queue) < queue->threshold;
 }
 
+/**
+ * rt2x00queue_timeout - Check if a timeout occured for this queue
+ * @queue: Queue to check.
+ */
+static inline int rt2x00queue_timeout(struct data_queue *queue)
+{
+       return time_after(queue->last_index, queue->last_index_done + (HZ / 10));
+}
+
 /**
  * _rt2x00_desc_read - Read a word from the hardware descriptor.
  * @desc: Base descriptor address
index a22837c560fdfda28306f1b617c55fee3cb592d9..ff3a36622d1b88de75353a72f8abc35ee5d26c44 100644 (file)
@@ -292,6 +292,56 @@ void rt2x00usb_kill_tx_queue(struct rt2x00_dev *rt2x00dev,
 }
 EXPORT_SYMBOL_GPL(rt2x00usb_kill_tx_queue);
 
+static void rt2x00usb_watchdog_reset_tx(struct data_queue *queue)
+{
+       struct queue_entry_priv_usb *entry_priv;
+       unsigned short threshold = queue->threshold;
+
+       WARNING(queue->rt2x00dev, "TX queue %d timed out, invoke reset", queue->qid);
+
+       /*
+        * Temporarily disable the TX queue, this will force mac80211
+        * to use the other queues until this queue has been restored.
+        *
+        * Set the queue threshold to the queue limit. This prevents the
+        * queue from being enabled during the txdone handler.
+        */
+       queue->threshold = queue->limit;
+       ieee80211_stop_queue(queue->rt2x00dev->hw, queue->qid);
+
+       /*
+        * Reset all currently uploaded TX frames.
+        */
+       while (!rt2x00queue_empty(queue)) {
+               entry_priv = rt2x00queue_get_entry(queue, Q_INDEX_DONE)->priv_data;
+               usb_kill_urb(entry_priv->urb);
+
+               /*
+                * We need a short delay here to wait for
+                * the URB to be canceled and invoked the tx_done handler.
+                */
+               udelay(200);
+       }
+
+       /*
+        * The queue has been reset, and mac80211 is allowed to use the
+        * queue again.
+        */
+       queue->threshold = threshold;
+       ieee80211_wake_queue(queue->rt2x00dev->hw, queue->qid);
+}
+
+void rt2x00usb_watchdog(struct rt2x00_dev *rt2x00dev)
+{
+       struct data_queue *queue;
+
+       tx_queue_for_each(rt2x00dev, queue) {
+               if (rt2x00queue_timeout(queue))
+                       rt2x00usb_watchdog_reset_tx(queue);
+       }
+}
+EXPORT_SYMBOL_GPL(rt2x00usb_watchdog);
+
 /*
  * RX data handlers.
  */
index 2b7a1889e72f75dcaaa8e885ef0ad09d60e08283..d3d3ddc408759d755a8e55df993ee0c4853084c8 100644 (file)
@@ -399,6 +399,16 @@ void rt2x00usb_kick_tx_queue(struct rt2x00_dev *rt2x00dev,
 void rt2x00usb_kill_tx_queue(struct rt2x00_dev *rt2x00dev,
                              const enum data_queue_qid qid);
 
+/**
+ * rt2x00usb_watchdog - Watchdog for USB communication
+ * @rt2x00dev: Pointer to &struct rt2x00_dev
+ *
+ * Check the health of the USB communication and determine
+ * if timeouts have occured. If this is the case, this function
+ * will reset all communication to restore functionality again.
+ */
+void rt2x00usb_watchdog(struct rt2x00_dev *rt2x00dev);
+
 /*
  * Device initialization handlers.
  */
index 9ea6a672d4e22283a573970023984c3481c89ac4..cad07b69c53e68b65186f43f9f1c887b907de0e5 100644 (file)
@@ -2136,6 +2136,7 @@ static int rt73usb_probe_hw(struct rt2x00_dev *rt2x00dev)
        if (!modparam_nohwcrypt)
                __set_bit(CONFIG_SUPPORT_HW_CRYPTO, &rt2x00dev->flags);
        __set_bit(DRIVER_SUPPORT_LINK_TUNING, &rt2x00dev->flags);
+       __set_bit(DRIVER_SUPPORT_WATCHDOG, &rt2x00dev->flags);
 
        /*
         * Set the rssi offset.
@@ -2251,6 +2252,7 @@ static const struct rt2x00lib_ops rt73usb_rt2x00_ops = {
        .link_stats             = rt73usb_link_stats,
        .reset_tuner            = rt73usb_reset_tuner,
        .link_tuner             = rt73usb_link_tuner,
+       .watchdog               = rt2x00usb_watchdog,
        .write_tx_desc          = rt73usb_write_tx_desc,
        .write_beacon           = rt73usb_write_beacon,
        .get_tx_data_len        = rt73usb_get_tx_data_len,