]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
mt76: mmio: introduce mt76x02_check_tx_hang watchdog
authorLorenzo Bianconi <lorenzo.bianconi@redhat.com>
Fri, 28 Dec 2018 14:44:09 +0000 (15:44 +0100)
committerFelix Fietkau <nbd@nbd.name>
Fri, 11 Jan 2019 14:10:19 +0000 (15:10 +0100)
Port mt76x02_check_tx_hang watchdog from vendor driver in order to
perform a device reset when tx mac/dma logic hangs. Tx mac/dma stuck
has been observed when the device is heavy loaded or in a noisy
environment. Moreover introduce wdt delayed work in order to run
tx_hang watchdog. For the moment run mt76x02_check_tx_hang watchdog
just on mt76x2 devices since the issue has not been observed on mt76x0
driver yet

Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
drivers/net/wireless/mediatek/mt76/mt76.h
drivers/net/wireless/mediatek/mt76/mt76x02.h
drivers/net/wireless/mediatek/mt76/mt76x02_debugfs.c
drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
drivers/net/wireless/mediatek/mt76/mt76x02_util.c
drivers/net/wireless/mediatek/mt76/mt76x2/pci_init.c
drivers/net/wireless/mediatek/mt76/mt76x2/pci_main.c

index 5cd508a686095fcd80bbc71a34fc30fcee075d86..8ef430d8afc4abb7a2d45bc913f3184424811780 100644 (file)
@@ -421,6 +421,7 @@ struct mt76_dev {
        struct mt76_queue q_tx[__MT_TXQ_MAX];
        struct mt76_queue q_rx[__MT_RXQ_MAX];
        const struct mt76_queue_ops *queue_ops;
+       int tx_dma_idx[4];
 
        wait_queue_head_t tx_wait;
        struct sk_buff_head status_list;
index f383fdd914c562dfee3bb85210ce7c60f3b6ff90..f594e8c82500291e2f7b94716e10091421fd2e39 100644 (file)
@@ -28,6 +28,9 @@
 
 #define MT_CALIBRATE_INTERVAL  HZ
 
+#define MT_WATCHDOG_TIME       (HZ / 10)
+#define MT_TX_HANG_TH          10
+
 #define MT_MAX_CHAINS          2
 struct mt76x02_rx_freq_cal {
        s8 high_gain[MT_MAX_CHAINS];
@@ -79,6 +82,7 @@ struct mt76x02_dev {
        struct tasklet_struct pre_tbtt_tasklet;
        struct delayed_work cal_work;
        struct delayed_work mac_work;
+       struct delayed_work wdt_work;
 
        u32 aggr_stats[32];
 
@@ -89,6 +93,9 @@ struct mt76x02_dev {
        u8 tbtt_count;
        u16 beacon_int;
 
+       u32 tx_hang_reset;
+       u8 tx_hang_check;
+
        struct mt76x02_calibration cal;
 
        s8 target_power;
@@ -142,6 +149,7 @@ s8 mt76x02_tx_get_max_txpwr_adj(struct mt76x02_dev *dev,
                                const struct ieee80211_tx_rate *rate);
 s8 mt76x02_tx_get_txpwr_adj(struct mt76x02_dev *dev, s8 txpwr,
                            s8 max_txpwr_adj);
+void mt76x02_wdt_work(struct work_struct *work);
 void mt76x02_tx_set_txpwr_auto(struct mt76x02_dev *dev, s8 txpwr);
 void mt76x02_set_tx_ackto(struct mt76x02_dev *dev);
 void mt76x02_set_coverage_class(struct ieee80211_hw *hw,
index a9d52ba1e2702cbddee55b2632670bc7bb8780b0..7580c5c986ffe5226f4c91feccaf73cd445aabc0 100644 (file)
@@ -133,5 +133,7 @@ void mt76x02_init_debugfs(struct mt76x02_dev *dev)
                                    read_txpower);
 
        debugfs_create_devm_seqfile(dev->mt76.dev, "agc", dir, read_agc);
+
+       debugfs_create_u32("tx_hang_reset", 0400, dir, &dev->tx_hang_reset);
 }
 EXPORT_SYMBOL_GPL(mt76x02_init_debugfs);
index cc584ea4a8d7e7bda3654c0f60ed793a428e8304..6adea0278284a2625288268a0137d2f5185aaf0b 100644 (file)
@@ -943,7 +943,6 @@ void mt76x02_mac_work(struct work_struct *work)
                dev->aggr_stats[idx++] += val >> 16;
        }
 
-       /* XXX: check beacon stuck for ap mode */
        if (!dev->beacon_mask)
                mt76x02_check_mac_err(dev);
 
index 6974acc75e2b253ff0da503c2a87a2b1e756e58a..6236c6121c01cd1588a9edbb14943067b7b600a9 100644 (file)
@@ -385,3 +385,127 @@ void mt76x02_mac_start(struct mt76x02_dev *dev)
                           MT_INT_TX_STAT);
 }
 EXPORT_SYMBOL_GPL(mt76x02_mac_start);
+
+static bool mt76x02_tx_hang(struct mt76x02_dev *dev)
+{
+       u32 dma_idx, prev_dma_idx;
+       struct mt76_queue *q;
+       int i;
+
+       for (i = 0; i < 4; i++) {
+               q = &dev->mt76.q_tx[i];
+
+               if (!q->queued)
+                       continue;
+
+               prev_dma_idx = dev->mt76.tx_dma_idx[i];
+               dma_idx = ioread32(&q->regs->dma_idx);
+               dev->mt76.tx_dma_idx[i] = dma_idx;
+
+               if (prev_dma_idx == dma_idx)
+                       break;
+       }
+
+       return i < 4;
+}
+
+static void mt76x02_watchdog_reset(struct mt76x02_dev *dev)
+{
+       u32 mask = dev->mt76.mmio.irqmask;
+       int i;
+
+       ieee80211_stop_queues(dev->mt76.hw);
+       set_bit(MT76_RESET, &dev->mt76.state);
+
+       tasklet_disable(&dev->pre_tbtt_tasklet);
+       tasklet_disable(&dev->tx_tasklet);
+
+       for (i = 0; i < ARRAY_SIZE(dev->mt76.napi); i++)
+               napi_disable(&dev->mt76.napi[i]);
+
+       mutex_lock(&dev->mt76.mutex);
+
+       if (dev->beacon_mask)
+               mt76_clear(dev, MT_BEACON_TIME_CFG,
+                          MT_BEACON_TIME_CFG_BEACON_TX |
+                          MT_BEACON_TIME_CFG_TBTT_EN);
+
+       mt76x02_irq_disable(dev, mask);
+
+       /* perform device reset */
+       mt76_clear(dev, MT_TXOP_CTRL_CFG, MT_TXOP_ED_CCA_EN);
+       mt76_wr(dev, MT_MAC_SYS_CTRL, 0);
+       mt76_clear(dev, MT_WPDMA_GLO_CFG,
+                  MT_WPDMA_GLO_CFG_TX_DMA_EN | MT_WPDMA_GLO_CFG_RX_DMA_EN);
+       usleep_range(5000, 10000);
+       mt76_wr(dev, MT_INT_SOURCE_CSR, 0xffffffff);
+
+       /* let fw reset DMA */
+       mt76_set(dev, 0x734, 0x3);
+
+       for (i = 0; i < ARRAY_SIZE(dev->mt76.q_tx); i++)
+               mt76_queue_tx_cleanup(dev, i, true);
+
+       for (i = 0; i < ARRAY_SIZE(dev->mt76.q_rx); i++)
+               mt76_queue_rx_reset(dev, i);
+
+       mt76_wr(dev, MT_MAC_SYS_CTRL,
+               MT_MAC_SYS_CTRL_ENABLE_TX | MT_MAC_SYS_CTRL_ENABLE_RX);
+       mt76_set(dev, MT_WPDMA_GLO_CFG,
+                MT_WPDMA_GLO_CFG_TX_DMA_EN | MT_WPDMA_GLO_CFG_RX_DMA_EN);
+       if (dev->ed_monitor)
+               mt76_set(dev, MT_TXOP_CTRL_CFG, MT_TXOP_ED_CCA_EN);
+
+       if (dev->beacon_mask)
+               mt76_set(dev, MT_BEACON_TIME_CFG,
+                        MT_BEACON_TIME_CFG_BEACON_TX |
+                        MT_BEACON_TIME_CFG_TBTT_EN);
+
+       mt76x02_irq_enable(dev, mask);
+
+       mutex_unlock(&dev->mt76.mutex);
+
+       clear_bit(MT76_RESET, &dev->mt76.state);
+
+       tasklet_enable(&dev->tx_tasklet);
+       tasklet_schedule(&dev->tx_tasklet);
+
+       tasklet_enable(&dev->pre_tbtt_tasklet);
+
+       for (i = 0; i < ARRAY_SIZE(dev->mt76.napi); i++) {
+               napi_enable(&dev->mt76.napi[i]);
+               napi_schedule(&dev->mt76.napi[i]);
+       }
+
+       ieee80211_wake_queues(dev->mt76.hw);
+
+       mt76_txq_schedule_all(&dev->mt76);
+}
+
+static void mt76x02_check_tx_hang(struct mt76x02_dev *dev)
+{
+       if (mt76x02_tx_hang(dev)) {
+               if (++dev->tx_hang_check < MT_TX_HANG_TH)
+                       return;
+
+               mt76x02_watchdog_reset(dev);
+
+               dev->tx_hang_reset++;
+               dev->tx_hang_check = 0;
+               memset(dev->mt76.tx_dma_idx, 0xff,
+                      sizeof(dev->mt76.tx_dma_idx));
+       } else {
+               dev->tx_hang_check = 0;
+       }
+}
+
+void mt76x02_wdt_work(struct work_struct *work)
+{
+       struct mt76x02_dev *dev = container_of(work, struct mt76x02_dev,
+                                              wdt_work.work);
+
+       mt76x02_check_tx_hang(dev);
+
+       ieee80211_queue_delayed_work(mt76_hw(dev), &dev->wdt_work,
+                                    MT_WATCHDOG_TIME);
+}
index aac64d0969b8db9d6739763331dce998e457ff7c..3c878f18e05d9ae6cd30fad0d4d0583d4ad3290f 100644 (file)
@@ -93,6 +93,8 @@ void mt76x02_init_device(struct mt76x02_dev *dev)
                                         MT_DMA_HDR_LEN;
                wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION);
        } else {
+               INIT_DELAYED_WORK(&dev->wdt_work, mt76x02_wdt_work);
+
                mt76x02_dfs_init_detector(dev);
 
                wiphy->reg_notifier = mt76x02_regd_notifier;
index 9f313824eeadb098a6f2432e18df2f8483ec4a18..34418e57554211fb69085e33317568429746929e 100644 (file)
@@ -301,6 +301,7 @@ void mt76x2_stop_hardware(struct mt76x02_dev *dev)
 {
        cancel_delayed_work_sync(&dev->cal_work);
        cancel_delayed_work_sync(&dev->mac_work);
+       cancel_delayed_work_sync(&dev->wdt_work);
        mt76x02_mcu_set_radio_state(dev, false);
        mt76x2_mac_stop(dev, false);
 }
index b54a32397486f5bf06b769231716866e1fa579fc..6a87b88eb036fbdb24886cdd02b8f22e1e082fe8 100644 (file)
@@ -34,6 +34,8 @@ mt76x2_start(struct ieee80211_hw *hw)
 
        ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mac_work,
                                     MT_CALIBRATE_INTERVAL);
+       ieee80211_queue_delayed_work(mt76_hw(dev), &dev->wdt_work,
+                                    MT_WATCHDOG_TIME);
 
        set_bit(MT76_STATE_RUNNING, &dev->mt76.state);