From: Yin-Ming Li An MT76_REMOVED check alone cannot synchronize an accepted MCU event with removal. A handler can pass the check and then arm scan, coredump or ROC activity after the corresponding consumer has been cancelled. Serialize unsolicited event side effects with a teardown gate. Leave command responses outside this gate. Preserve event delivery during firmware initialization, which precedes hardware registration: the gate uses a dedicated stop flag and retains the existing REMOVED check rather than requiring MT76_STATE_REGISTERED. Use GFP_ATOMIC for RSSI notifications from the atomic event dispatch path. Add a common MT7921 unregister helper that drains initialization, closes the event gate and disables reset, MAC and PM work before unregistering mac80211. Shut down the ROC timer and drain its work after closing the event producer. Work disabling rejects late queue attempts from bus workers and NAPI, whereas cancellation alone does not. Keep the bus and PM wake path operational while draining work that needs them. Then disable PM work and park the TX worker. Request driver ownership under the PM mutex and set pm->suspended to suppress power-save scheduling before entering mac80211 teardown. If ownership acquisition succeeds, teardown commands can run without the disabled PM wake work. The bus implementations log ownership failures and removal continues best-effort. Clear MT76_STATE_REGISTERED before entering mac80211 so the state reflects unregistration. Bus drivers still have to stop their asynchronous producers. The following patches wire the helper into PCIe and SDIO removal; the existing reset-work REMOVED guards remain in place. Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: Yin-Ming Li --- drivers/net/wireless/mediatek/mt76/mac80211.c | 8 +++-- drivers/net/wireless/mediatek/mt76/mt7921/init.c | 35 +++++++++++++++++++++ drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 36 ++++++++++------------ drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h | 1 + drivers/net/wireless/mediatek/mt76/mt792x.h | 3 ++ 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index f92277770488aaca2a186a88fcd279086b9ee0f3..be8cd122cc04b904ccc1e413df9717e03be94a15 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -602,7 +602,7 @@ void mt76_unregister_phy(struct mt76_phy *phy) { struct mt76_dev *dev = phy->dev; - if (!test_bit(MT76_STATE_REGISTERED, &phy->state)) + if (!test_and_clear_bit(MT76_STATE_REGISTERED, &phy->state)) return; if (IS_ENABLED(CONFIG_MT76_LEDS)) @@ -808,7 +808,11 @@ void mt76_unregister_device(struct mt76_dev *dev) { struct ieee80211_hw *hw = dev->hw; - if (!test_bit(MT76_STATE_REGISTERED, &dev->phy.state)) + /* + * Publish the unregistering state before entering mac80211. + * Drivers must separately quiesce their asynchronous work producers. + */ + if (!test_and_clear_bit(MT76_STATE_REGISTERED, &dev->phy.state)) return; if (IS_ENABLED(CONFIG_MT76_LEDS)) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c index 49be2fa4e3fa178e03764fb03c627413b8967c86..7ceb6c5894d0d5539fd7580be17250b0cf4d3535 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c @@ -167,6 +167,40 @@ static void mt7921_init_work(struct work_struct *work) mt76_connac_mcu_set_deep_sleep(&dev->mt76, dev->pm.ds_enable); } +void mt7921_unregister_device(struct mt792x_dev *dev) +{ + struct mt76_connac_pm *pm = &dev->pm; + + cancel_work_sync(&dev->init_work); + + /* Wait for accepted events and reject new unsolicited events. */ + spin_lock_bh(&dev->mcu_event_lock); + dev->mcu_events_stopped = true; + spin_unlock_bh(&dev->mcu_event_lock); + + /* Keep the bus and PM wake path available while running work drains. */ + disable_work_sync(&dev->reset_work); + disable_delayed_work_sync(&dev->mphy.mac_work); + cancel_delayed_work_sync(&dev->phy.scan_work); + timer_shutdown_sync(&dev->phy.roc_timer); + cancel_work_sync(&dev->phy.roc_work); + + /* Stop PM producers before mac80211 tears down its queues. */ + disable_delayed_work_sync(&pm->ps_work); + mt76_worker_disable(&dev->mt76.tx_worker); + disable_work_sync(&pm->wake_work); + + /* Keep the device awake for teardown commands without PM work. */ + mutex_lock(&pm->mutex); + __mt792x_mcu_drv_pmctrl(dev); + mutex_unlock(&pm->mutex); + wake_up(&pm->wait); + pm->suspended = true; + + mt76_unregister_device(&dev->mt76); +} +EXPORT_SYMBOL_GPL(mt7921_unregister_device); + int mt7921_register_device(struct mt792x_dev *dev) { struct ieee80211_hw *hw = mt76_hw(dev); @@ -177,6 +211,7 @@ int mt7921_register_device(struct mt792x_dev *dev) dev->mt76.phy.priv = &dev->phy; dev->mt76.tx_worker.fn = mt792x_tx_worker; + spin_lock_init(&dev->mcu_event_lock); INIT_DELAYED_WORK(&dev->pm.ps_work, mt792x_pm_power_save_work); INIT_WORK(&dev->pm.wake_work, mt792x_pm_wake_work); spin_lock_init(&dev->pm.wake.lock); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 4e85b7644a9945f966ecf2fa12d17d2333533a0c..78328838abe583fdaad12d3eec97a7bf8dfff73a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -276,7 +276,7 @@ mt7921_mcu_rssi_monitor_iter(void *priv, u8 *mac, else nl_event = NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW; - ieee80211_cqm_rssi_notify(vif, nl_event, rssi, GFP_KERNEL); + ieee80211_cqm_rssi_notify(vif, nl_event, rssi, GFP_ATOMIC); } static void @@ -297,14 +297,11 @@ mt7921_mcu_rx_unsolicited_event(struct mt792x_dev *dev, struct sk_buff *skb) { struct mt76_connac2_mcu_rxd *rxd; - /* See mt7921_mcu_uni_rx_unsolicited_event(). MCU_EVENT_SCAN_DONE and - * MCU_EVENT_COREDUMP in particular arm scan_work and coredump.work - * after mt7921s_unregister_device() has already cancelled them. - */ - if (test_bit(MT76_REMOVED, &dev->mphy.state)) { - dev_kfree_skb(skb); - return; - } + /* Serialize event side effects with mt7921_unregister_device(). */ + spin_lock_bh(&dev->mcu_event_lock); + if (dev->mcu_events_stopped || + test_bit(MT76_REMOVED, &dev->mphy.state)) + goto free_skb; rxd = (struct mt76_connac2_mcu_rxd *)skb->data; switch (rxd->eid) { @@ -314,7 +311,7 @@ mt7921_mcu_rx_unsolicited_event(struct mt792x_dev *dev, struct sk_buff *skb) case MCU_EVENT_SCHED_SCAN_DONE: case MCU_EVENT_SCAN_DONE: mt7921_mcu_scan_event(dev, skb); - return; + goto out; case MCU_EVENT_DBG_MSG: mt7921_mcu_debug_msg_event(dev, skb); break; @@ -322,7 +319,7 @@ mt7921_mcu_rx_unsolicited_event(struct mt792x_dev *dev, struct sk_buff *skb) dev->fw_assert = true; mt76_connac_mcu_coredump_event(&dev->mt76, skb, &dev->coredump); - return; + goto out; case MCU_EVENT_LP_INFO: mt7921_mcu_low_power_event(dev, skb); break; @@ -335,7 +332,10 @@ mt7921_mcu_rx_unsolicited_event(struct mt792x_dev *dev, struct sk_buff *skb) default: break; } +free_skb: dev_kfree_skb(skb); +out: + spin_unlock_bh(&dev->mcu_event_lock); } static void @@ -344,14 +344,10 @@ mt7921_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, { struct mt76_connac2_mcu_rxd *rxd; - /* Every handler below either calls into mac80211 or arms a work item or - * timer that does. mac80211 is already unregistered once the device is - * being removed, so drop the event instead. - */ - if (test_bit(MT76_REMOVED, &dev->mphy.state)) { - dev_kfree_skb(skb); - return; - } + spin_lock_bh(&dev->mcu_event_lock); + if (dev->mcu_events_stopped || + test_bit(MT76_REMOVED, &dev->mphy.state)) + goto out; rxd = (struct mt76_connac2_mcu_rxd *)skb->data; @@ -362,6 +358,8 @@ mt7921_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, default: break; } +out: + spin_unlock_bh(&dev->mcu_event_lock); dev_kfree_skb(skb); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h index fb8654b7c5e7e713abbb989e3fee15dc93c20e2a..1a3c25cb30eeb6ae8321cc072adebed351722eca 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h @@ -260,6 +260,7 @@ mt7921_l1_rmw(struct mt792x_dev *dev, u32 addr, u32 mask, u32 val) #define mt7921_l1_set(dev, addr, val) mt7921_l1_rmw(dev, addr, 0, val) #define mt7921_l1_clear(dev, addr, val) mt7921_l1_rmw(dev, addr, val, 0) +void mt7921_unregister_device(struct mt792x_dev *dev); int mt7921_mac_init(struct mt792x_dev *dev); bool mt7921_mac_wtbl_update(struct mt792x_dev *dev, int idx, u32 mask); int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 9567ff883b285145552e1367233cb76a077e11ad..f515e091dad64c7d21540cf9729107087a359b74 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -323,6 +323,9 @@ struct mt792x_dev { struct mt76_connac_pm pm; struct mt76_connac_coredump coredump; + /* MT7921 unsolicited event dispatch versus unregister. */ + spinlock_t mcu_event_lock; + bool mcu_events_stopped; const struct mt792x_hif_ops *hif_ops; const struct mt792x_irq_map *irq_map; const struct mt792x_pcie_reg *pcie_reg; -- 2.43.0