@@ -3194,4 +3194,8 @@ enum rt2800_eeprom_word {
*/
#define BCN_TBTT_OFFSET 64
+/* Watchdog type mask */
+#define RT2800_WATCHDOG_HANG BIT(0)
+#define RT2800_WATCHDOG_DMA_BUSY BIT(1)
+
#endif /* RT2800_H */
@@ -30,9 +30,10 @@
#include "rt2800lib.h"
#include "rt2800.h"
-static bool modparam_watchdog;
-module_param_named(watchdog, modparam_watchdog, bool, S_IRUGO);
-MODULE_PARM_DESC(watchdog, "Enable watchdog to detect tx/rx hangs and reset hardware if detected");
+static unsigned int modparam_watchdog = RT2800_WATCHDOG_DMA_BUSY;
+module_param_named(watchdog, modparam_watchdog, uint, 0444);
+MODULE_PARM_DESC(watchdog, "Enable watchdog to recover tx/rx hangs.\n"
+ "\t\t(0=disabled, 1=hang watchdog, 2=DMA watchdog(default), 3=both)");
/*
* Register access.
@@ -1260,15 +1261,12 @@ static void rt2800_update_survey(struct rt2x00_dev *rt2x00dev)
chan_survey->time_ext_busy += rt2800_register_read(rt2x00dev, CH_BUSY_STA_SEC);
}
-void rt2800_watchdog(struct rt2x00_dev *rt2x00dev)
+static bool rt2800_watchdog_hung(struct rt2x00_dev *rt2x00dev)
{
struct data_queue *queue;
bool hung_tx = false;
bool hung_rx = false;
- if (test_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags))
- return;
-
rt2800_update_survey(rt2x00dev);
queue_for_each(rt2x00dev, queue) {
@@ -1296,18 +1294,72 @@ void rt2800_watchdog(struct rt2x00_dev *rt2x00dev)
}
}
+ if (!hung_tx && !hung_rx)
+ return false;
+
if (hung_tx)
rt2x00_warn(rt2x00dev, "Watchdog TX hung detected\n");
if (hung_rx)
rt2x00_warn(rt2x00dev, "Watchdog RX hung detected\n");
- if (hung_tx || hung_rx) {
- queue_for_each(rt2x00dev, queue)
- queue->wd_count = 0;
+ queue_for_each(rt2x00dev, queue)
+ queue->wd_count = 0;
+
+ return true;
+}
+
+static bool rt2800_watchdog_dma_busy(struct rt2x00_dev *rt2x00dev)
+{
+ bool busy_rx, busy_tx;
+ u32 reg_cfg = rt2800_register_read(rt2x00dev, WPDMA_GLO_CFG);
+ u32 reg_int = rt2800_register_read(rt2x00dev, INT_SOURCE_CSR);
+
+ if (rt2x00_get_field32(reg_cfg, WPDMA_GLO_CFG_RX_DMA_BUSY) &&
+ rt2x00_get_field32(reg_int, INT_SOURCE_CSR_RX_COHERENT))
+ rt2x00dev->rxdma_busy++;
+ else
+ rt2x00dev->rxdma_busy = 0;
+ if (rt2x00_get_field32(reg_cfg, WPDMA_GLO_CFG_TX_DMA_BUSY) &&
+ rt2x00_get_field32(reg_int, INT_SOURCE_CSR_TX_COHERENT))
+ rt2x00dev->txdma_busy++;
+ else
+ rt2x00dev->txdma_busy = 0;
+
+ busy_rx = rt2x00dev->rxdma_busy > 30 ? true : false;
+ busy_tx = rt2x00dev->txdma_busy > 30 ? true : false;
+
+ if (!busy_rx && !busy_tx)
+ return false;
+
+ if (busy_rx)
+ rt2x00_warn(rt2x00dev, "Watchdog RX DMA busy detected\n");
+
+ if (busy_tx)
+ rt2x00_warn(rt2x00dev, "Watchdog TX DMA busy detected\n");
+
+ rt2x00dev->rxdma_busy = 0;
+ rt2x00dev->txdma_busy = 0;
+
+ return true;
+}
+
+void rt2800_watchdog(struct rt2x00_dev *rt2x00dev)
+{
+ bool reset = false;
+
+ if (test_bit(DEVICE_STATE_SCANNING, &rt2x00dev->flags))
+ return;
+
+ if (modparam_watchdog & RT2800_WATCHDOG_DMA_BUSY)
+ reset = rt2800_watchdog_dma_busy(rt2x00dev);
+
+ if (modparam_watchdog & RT2800_WATCHDOG_HANG)
+ reset = rt2800_watchdog_hung(rt2x00dev) || reset;
+
+ if (reset)
ieee80211_restart_hw(rt2x00dev->hw);
- }
}
EXPORT_SYMBOL_GPL(rt2800_watchdog);
@@ -12015,6 +12067,9 @@ int rt2800_probe_hw(struct rt2x00_dev *rt2x00dev)
__set_bit(REQUIRE_TASKLET_CONTEXT, &rt2x00dev->cap_flags);
}
+ /* USB NICs don't support DMA watchdog as INT_SOURCE_CSR is invalid */
+ if (rt2x00_is_usb(rt2x00dev))
+ modparam_watchdog &= ~RT2800_WATCHDOG_DMA_BUSY;
if (modparam_watchdog) {
__set_bit(CAPABILITY_RESTART_HW, &rt2x00dev->cap_flags);
rt2x00dev->link.watchdog_interval = msecs_to_jiffies(100);
@@ -926,6 +926,9 @@ struct rt2x00_dev {
*/
u16 beacon_int;
+ /* Rx/Tx DMA busy watchdog counter */
+ u16 rxdma_busy, txdma_busy;
+
/**
* Timestamp of last received beacon
*/