From: Yin-Ming Li The PCIe remove path sets MT76_REMOVED before unregistering mac80211, making the IRQ handler return without servicing MCU responses. It also disables the IRQ tasklet only after DMA cleanup, so a previously scheduled tasklet can run while queues are being torn down. Use the common work-quiescing helper while IRQs, NAPI and the MCU command queues remain available. Delay publishing REMOVED until mac80211 unregistration returns, so the IRQ handler remains available during teardown even when individual MCU commands fail. Mask both device interrupt sources and flush the write before publishing REMOVED. Synchronize the IRQ, disable its tasklet, and disable and delete RX NAPI before releasing TX tokens. Let common DMA cleanup skip an already-deleted RX NAPI context to avoid disabling it twice. After DMA cleanup has also stopped TX NAPI, drain the tasklet that its last poll might have scheduled. Add a REMOVED guard so this final tasklet cannot access freed queues. Purge scan and coredump state after stopping their producers and before freeing the device. Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: Yin-Ming Li --- drivers/net/wireless/mediatek/mt76/dma.c | 4 ++- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 40 ++++++++++++++++++------- drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 3 ++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index f695a14596062c4cd5445913f014907a421b2c22..25d4640f09eea7335c422b36b9c3ef52fe388830 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -1217,7 +1217,9 @@ void mt76_dma_cleanup(struct mt76_dev *dev) mt76_for_each_q_rx(dev, i) { struct mt76_queue *q = &dev->q_rx[i]; - if (!mt76_queue_is_wed_rro(q)) { + /* A driver may have removed RX NAPI before releasing TX tokens. */ + if (!mt76_queue_is_wed_rro(q) && + test_bit(NAPI_STATE_LISTED, &dev->napi[i].state)) { napi_disable(&dev->napi[i]); netif_napi_del(&dev->napi[i]); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 5180597aed757777dc3d5c287f2bbdee39ce243d..2e05dbaf8b2805522410609e90f422b348826e43 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -43,28 +43,47 @@ static int mt7921e_init_reset(struct mt792x_dev *dev) static void mt7921e_unregister_device(struct mt792x_dev *dev) { int i; - struct mt76_connac_pm *pm = &dev->pm; + struct pci_dev *pdev = to_pci_dev(dev->mt76.dev); struct ieee80211_hw *hw = mt76_hw(dev); if (dev->phy.chip_cap & MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN) wiphy_rfkill_stop_polling(hw->wiphy); - cancel_work_sync(&dev->init_work); - mt76_unregister_device(&dev->mt76); - mt76_for_each_q_rx(&dev->mt76, i) + mt7921_unregister_device(dev); + + /* + * Mask device interrupts before MT76_REMOVED makes the handler return + * IRQ_NONE. Drain the IRQ and tasklet before tearing down the queues. + */ + mt76_wr(dev, dev->irq_map->host_irq_enable, 0); + mt76_wr(dev, dev->pcie_reg->imask, 0); + mt76_rr(dev, dev->pcie_reg->imask); + set_bit(MT76_REMOVED, &dev->mphy.state); + synchronize_irq(pdev->irq); + tasklet_disable(&dev->mt76.irq_tasklet); + + mt76_for_each_q_rx(&dev->mt76, i) { napi_disable(&dev->mt76.napi[i]); - cancel_delayed_work_sync(&pm->ps_work); - cancel_work_sync(&pm->wake_work); - cancel_work_sync(&dev->reset_work); + netif_napi_del(&dev->mt76.napi[i]); + } + skb_queue_purge(&dev->phy.scan_event_list); + cancel_delayed_work_sync(&dev->coredump.work); + skb_queue_purge(&dev->coredump.msg_list); mt76_connac2_tx_token_put(&dev->mt76); - __mt792x_mcu_drv_pmctrl(dev); mt792x_dma_cleanup(dev); + + /* + * TX NAPI is now disabled too; drain a tasklet queued by its last poll. + * MT76_REMOVED makes that tasklet return without touching freed queues. + */ + tasklet_enable(&dev->mt76.irq_tasklet); + tasklet_kill(&dev->mt76.irq_tasklet); + mt76_wr(dev, dev->irq_map->host_irq_enable, 0); + mt792x_wfsys_reset(dev); skb_queue_purge(&dev->mt76.mcu.res_q); - - tasklet_disable(&dev->mt76.irq_tasklet); } static u32 __mt7921_reg_addr(struct mt792x_dev *dev, u32 addr) @@ -458,7 +477,6 @@ static void mt7921_pci_remove(struct pci_dev *pdev) if (of_property_read_bool(dev->mt76.dev->of_node, "wakeup-source")) device_init_wakeup(dev->mt76.dev, false); - set_bit(MT76_REMOVED, &mdev->phy.state); mt7921e_unregister_device(dev); devm_free_irq(&pdev->dev, pdev->irq, dev); mt76_free_device(&dev->mt76); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 8ad94fa583405d561429a1c5d212e9f83d616328..06877816640f51829b50205ab93126385d26f16a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -31,6 +31,9 @@ void mt792x_irq_tasklet(unsigned long data) const struct mt792x_irq_map *irq_map = dev->irq_map; u32 intr, mask = 0; + if (test_bit(MT76_REMOVED, &dev->mphy.state)) + return; + mt76_wr(dev, irq_map->host_irq_enable, 0); intr = mt76_rr(dev, MT_WFDMA0_HOST_INT_STA); -- 2.43.0