]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.16-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 6 Oct 2025 10:24:28 +0000 (12:24 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 6 Oct 2025 10:24:28 +0000 (12:24 +0200)
added patches:
wifi-rtw89-fix-use-after-free-in-rtw89_core_tx_kick_off_and_wait.patch
wifi-rtw89-mcc-stop-tx-during-mcc-prepare.patch

queue-6.16/series
queue-6.16/wifi-rtw89-fix-use-after-free-in-rtw89_core_tx_kick_off_and_wait.patch [new file with mode: 0644]
queue-6.16/wifi-rtw89-mcc-stop-tx-during-mcc-prepare.patch [new file with mode: 0644]

index 7e7bd8e96156c2bbb0a6aae10f9b4143d5dc77c9..060ade6938c567ce9746a735dae8af4e4f75a47b 100644 (file)
@@ -1,2 +1,4 @@
 alsa-hda-tas2781-fix-the-order-of-tas2781-calibrated-data.patch
 drm-amdgpu-enable-mes-lr_compute_wa-by-default.patch
+wifi-rtw89-mcc-stop-tx-during-mcc-prepare.patch
+wifi-rtw89-fix-use-after-free-in-rtw89_core_tx_kick_off_and_wait.patch
diff --git a/queue-6.16/wifi-rtw89-fix-use-after-free-in-rtw89_core_tx_kick_off_and_wait.patch b/queue-6.16/wifi-rtw89-fix-use-after-free-in-rtw89_core_tx_kick_off_and_wait.patch
new file mode 100644 (file)
index 0000000..e29a9af
--- /dev/null
@@ -0,0 +1,326 @@
+From stable+bounces-183238-greg=kroah.com@vger.kernel.org Fri Oct  3 17:20:17 2025
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri,  3 Oct 2025 11:20:05 -0400
+Subject: wifi: rtw89: fix use-after-free in rtw89_core_tx_kick_off_and_wait()
+To: stable@vger.kernel.org
+Cc: Fedor Pchelkin <pchelkin@ispras.ru>, Zong-Zhe Yang <kevin_yang@realtek.com>, Ping-Ke Shih <pkshih@realtek.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20251003152005.3176758-2-sashal@kernel.org>
+
+From: Fedor Pchelkin <pchelkin@ispras.ru>
+
+[ Upstream commit 3e31a6bc07312b448fad3b45de578471f86f0e77 ]
+
+There is a bug observed when rtw89_core_tx_kick_off_and_wait() tries to
+access already freed skb_data:
+
+ BUG: KFENCE: use-after-free write in rtw89_core_tx_kick_off_and_wait drivers/net/wireless/realtek/rtw89/core.c:1110
+
+ CPU: 6 UID: 0 PID: 41377 Comm: kworker/u64:24 Not tainted  6.17.0-rc1+ #1 PREEMPT(lazy)
+ Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS edk2-20250523-14.fc42 05/23/2025
+ Workqueue: events_unbound cfg80211_wiphy_work [cfg80211]
+
+ Use-after-free write at 0x0000000020309d9d (in kfence-#251):
+ rtw89_core_tx_kick_off_and_wait drivers/net/wireless/realtek/rtw89/core.c:1110
+ rtw89_core_scan_complete drivers/net/wireless/realtek/rtw89/core.c:5338
+ rtw89_hw_scan_complete_cb drivers/net/wireless/realtek/rtw89/fw.c:7979
+ rtw89_chanctx_proceed_cb drivers/net/wireless/realtek/rtw89/chan.c:3165
+ rtw89_chanctx_proceed drivers/net/wireless/realtek/rtw89/chan.h:141
+ rtw89_hw_scan_complete drivers/net/wireless/realtek/rtw89/fw.c:8012
+ rtw89_mac_c2h_scanofld_rsp drivers/net/wireless/realtek/rtw89/mac.c:5059
+ rtw89_fw_c2h_work drivers/net/wireless/realtek/rtw89/fw.c:6758
+ process_one_work kernel/workqueue.c:3241
+ worker_thread kernel/workqueue.c:3400
+ kthread kernel/kthread.c:463
+ ret_from_fork arch/x86/kernel/process.c:154
+ ret_from_fork_asm arch/x86/entry/entry_64.S:258
+
+ kfence-#251: 0x0000000056e2393d-0x000000009943cb62, size=232, cache=skbuff_head_cache
+
+ allocated by task 41377 on cpu 6 at 77869.159548s (0.009551s ago):
+ __alloc_skb net/core/skbuff.c:659
+ __netdev_alloc_skb net/core/skbuff.c:734
+ ieee80211_nullfunc_get net/mac80211/tx.c:5844
+ rtw89_core_send_nullfunc drivers/net/wireless/realtek/rtw89/core.c:3431
+ rtw89_core_scan_complete drivers/net/wireless/realtek/rtw89/core.c:5338
+ rtw89_hw_scan_complete_cb drivers/net/wireless/realtek/rtw89/fw.c:7979
+ rtw89_chanctx_proceed_cb drivers/net/wireless/realtek/rtw89/chan.c:3165
+ rtw89_chanctx_proceed drivers/net/wireless/realtek/rtw89/chan.c:3194
+ rtw89_hw_scan_complete drivers/net/wireless/realtek/rtw89/fw.c:8012
+ rtw89_mac_c2h_scanofld_rsp drivers/net/wireless/realtek/rtw89/mac.c:5059
+ rtw89_fw_c2h_work drivers/net/wireless/realtek/rtw89/fw.c:6758
+ process_one_work kernel/workqueue.c:3241
+ worker_thread kernel/workqueue.c:3400
+ kthread kernel/kthread.c:463
+ ret_from_fork arch/x86/kernel/process.c:154
+ ret_from_fork_asm arch/x86/entry/entry_64.S:258
+
+ freed by task 1045 on cpu 9 at 77869.168393s (0.001557s ago):
+ ieee80211_tx_status_skb net/mac80211/status.c:1117
+ rtw89_pci_release_txwd_skb drivers/net/wireless/realtek/rtw89/pci.c:564
+ rtw89_pci_release_tx_skbs.isra.0 drivers/net/wireless/realtek/rtw89/pci.c:651
+ rtw89_pci_release_tx drivers/net/wireless/realtek/rtw89/pci.c:676
+ rtw89_pci_napi_poll drivers/net/wireless/realtek/rtw89/pci.c:4238
+ __napi_poll net/core/dev.c:7495
+ net_rx_action net/core/dev.c:7557 net/core/dev.c:7684
+ handle_softirqs kernel/softirq.c:580
+ do_softirq.part.0 kernel/softirq.c:480
+ __local_bh_enable_ip kernel/softirq.c:407
+ rtw89_pci_interrupt_threadfn drivers/net/wireless/realtek/rtw89/pci.c:927
+ irq_thread_fn kernel/irq/manage.c:1133
+ irq_thread kernel/irq/manage.c:1257
+ kthread kernel/kthread.c:463
+ ret_from_fork arch/x86/kernel/process.c:154
+ ret_from_fork_asm arch/x86/entry/entry_64.S:258
+
+It is a consequence of a race between the waiting and the signaling side
+of the completion:
+
+            Waiting thread                            Completing thread
+
+rtw89_core_tx_kick_off_and_wait()
+  rcu_assign_pointer(skb_data->wait, wait)
+  /* start waiting */
+  wait_for_completion_timeout()
+                                                rtw89_pci_tx_status()
+                                                  rtw89_core_tx_wait_complete()
+                                                    rcu_read_lock()
+                                                    /* signals completion and
+                                                     * proceeds further
+                                                     */
+                                                    complete(&wait->completion)
+                                                    rcu_read_unlock()
+                                                  ...
+                                                  /* frees skb_data */
+                                                  ieee80211_tx_status_ni()
+  /* returns (exit status doesn't matter) */
+  wait_for_completion_timeout()
+  ...
+  /* accesses the already freed skb_data */
+  rcu_assign_pointer(skb_data->wait, NULL)
+
+The completing side might proceed and free the underlying skb even before
+the waiting side is fully awoken and run to execution.  Actually the race
+happens regardless of wait_for_completion_timeout() exit status, e.g.
+the waiting side may hit a timeout and the concurrent completing side is
+still able to free the skb.
+
+Skbs which are sent by rtw89_core_tx_kick_off_and_wait() are owned by the
+driver.  They don't come from core ieee80211 stack so no need to pass them
+to ieee80211_tx_status_ni() on completing side.
+
+Introduce a work function which will act as a garbage collector for
+rtw89_tx_wait_info objects and the associated skbs.  Thus no potentially
+heavy locks are required on the completing side.
+
+Found by Linux Verification Center (linuxtesting.org).
+
+Fixes: 1ae5ca615285 ("wifi: rtw89: add function to wait for completion of TX skbs")
+Cc: stable@vger.kernel.org
+Suggested-by: Zong-Zhe Yang <kevin_yang@realtek.com>
+Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
+Acked-by: Ping-Ke Shih <pkshih@realtek.com>
+Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
+Link: https://patch.msgid.link/20250919210852.823912-2-pchelkin@ispras.ru
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/wireless/realtek/rtw89/core.c |   30 ++++++++++++++++++++-----
+ drivers/net/wireless/realtek/rtw89/core.h |   35 ++++++++++++++++++++++++++++--
+ drivers/net/wireless/realtek/rtw89/pci.c  |    3 +-
+ drivers/net/wireless/realtek/rtw89/ser.c  |    2 +
+ 4 files changed, 61 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/wireless/realtek/rtw89/core.c
++++ b/drivers/net/wireless/realtek/rtw89/core.c
+@@ -1050,6 +1050,14 @@ rtw89_core_tx_update_desc_info(struct rt
+       }
+ }
++static void rtw89_tx_wait_work(struct wiphy *wiphy, struct wiphy_work *work)
++{
++      struct rtw89_dev *rtwdev = container_of(work, struct rtw89_dev,
++                                              tx_wait_work.work);
++
++      rtw89_tx_wait_list_clear(rtwdev);
++}
++
+ void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel)
+ {
+       u8 ch_dma;
+@@ -1067,6 +1075,8 @@ int rtw89_core_tx_kick_off_and_wait(stru
+       unsigned long time_left;
+       int ret = 0;
++      lockdep_assert_wiphy(rtwdev->hw->wiphy);
++
+       wait = kzalloc(sizeof(*wait), GFP_KERNEL);
+       if (!wait) {
+               rtw89_core_tx_kick_off(rtwdev, qsel);
+@@ -1074,18 +1084,23 @@ int rtw89_core_tx_kick_off_and_wait(stru
+       }
+       init_completion(&wait->completion);
++      wait->skb = skb;
+       rcu_assign_pointer(skb_data->wait, wait);
+       rtw89_core_tx_kick_off(rtwdev, qsel);
+       time_left = wait_for_completion_timeout(&wait->completion,
+                                               msecs_to_jiffies(timeout));
+-      if (time_left == 0)
+-              ret = -ETIMEDOUT;
+-      else if (!wait->tx_done)
+-              ret = -EAGAIN;
+-      rcu_assign_pointer(skb_data->wait, NULL);
+-      kfree_rcu(wait, rcu_head);
++      if (time_left == 0) {
++              ret = -ETIMEDOUT;
++              list_add_tail(&wait->list, &rtwdev->tx_waits);
++              wiphy_delayed_work_queue(rtwdev->hw->wiphy, &rtwdev->tx_wait_work,
++                                       RTW89_TX_WAIT_WORK_TIMEOUT);
++      } else {
++              if (!wait->tx_done)
++                      ret = -EAGAIN;
++              rtw89_tx_wait_release(wait);
++      }
+       return ret;
+ }
+@@ -4810,6 +4825,7 @@ void rtw89_core_stop(struct rtw89_dev *r
+       wiphy_work_cancel(wiphy, &btc->dhcp_notify_work);
+       wiphy_work_cancel(wiphy, &btc->icmp_notify_work);
+       cancel_delayed_work_sync(&rtwdev->txq_reinvoke_work);
++      wiphy_delayed_work_cancel(wiphy, &rtwdev->tx_wait_work);
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->track_work);
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->chanctx_work);
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->coex_act1_work);
+@@ -5034,6 +5050,7 @@ int rtw89_core_init(struct rtw89_dev *rt
+               INIT_LIST_HEAD(&rtwdev->scan_info.pkt_list[band]);
+       }
+       INIT_LIST_HEAD(&rtwdev->scan_info.chan_list);
++      INIT_LIST_HEAD(&rtwdev->tx_waits);
+       INIT_WORK(&rtwdev->ba_work, rtw89_core_ba_work);
+       INIT_WORK(&rtwdev->txq_work, rtw89_core_txq_work);
+       INIT_DELAYED_WORK(&rtwdev->txq_reinvoke_work, rtw89_core_txq_reinvoke_work);
+@@ -5044,6 +5061,7 @@ int rtw89_core_init(struct rtw89_dev *rt
+       wiphy_delayed_work_init(&rtwdev->coex_rfk_chk_work, rtw89_coex_rfk_chk_work);
+       wiphy_delayed_work_init(&rtwdev->cfo_track_work, rtw89_phy_cfo_track_work);
+       wiphy_delayed_work_init(&rtwdev->mcc_prepare_done_work, rtw89_mcc_prepare_done_work);
++      wiphy_delayed_work_init(&rtwdev->tx_wait_work, rtw89_tx_wait_work);
+       INIT_DELAYED_WORK(&rtwdev->forbid_ba_work, rtw89_forbid_ba_work);
+       wiphy_delayed_work_init(&rtwdev->antdiv_work, rtw89_phy_antdiv_work);
+       rtwdev->txq_wq = alloc_workqueue("rtw89_tx_wq", WQ_UNBOUND | WQ_HIGHPRI, 0);
+--- a/drivers/net/wireless/realtek/rtw89/core.h
++++ b/drivers/net/wireless/realtek/rtw89/core.h
+@@ -3429,9 +3429,12 @@ struct rtw89_phy_rate_pattern {
+       bool enable;
+ };
++#define RTW89_TX_WAIT_WORK_TIMEOUT msecs_to_jiffies(500)
+ struct rtw89_tx_wait_info {
+       struct rcu_head rcu_head;
++      struct list_head list;
+       struct completion completion;
++      struct sk_buff *skb;
+       bool tx_done;
+ };
+@@ -5802,6 +5805,9 @@ struct rtw89_dev {
+       /* used to protect rpwm */
+       spinlock_t rpwm_lock;
++      struct list_head tx_waits;
++      struct wiphy_delayed_work tx_wait_work;
++
+       struct rtw89_cam_info cam_info;
+       struct sk_buff_head c2h_queue;
+@@ -6056,6 +6062,26 @@ rtw89_assoc_link_rcu_dereference(struct
+       list_first_entry_or_null(&p->dlink_pool, typeof(*p->links_inst), dlink_schd); \
+ })
++static inline void rtw89_tx_wait_release(struct rtw89_tx_wait_info *wait)
++{
++      dev_kfree_skb_any(wait->skb);
++      kfree_rcu(wait, rcu_head);
++}
++
++static inline void rtw89_tx_wait_list_clear(struct rtw89_dev *rtwdev)
++{
++      struct rtw89_tx_wait_info *wait, *tmp;
++
++      lockdep_assert_wiphy(rtwdev->hw->wiphy);
++
++      list_for_each_entry_safe(wait, tmp, &rtwdev->tx_waits, list) {
++              if (!completion_done(&wait->completion))
++                      continue;
++              list_del(&wait->list);
++              rtw89_tx_wait_release(wait);
++      }
++}
++
+ static inline int rtw89_hci_tx_write(struct rtw89_dev *rtwdev,
+                                    struct rtw89_core_tx_request *tx_req)
+ {
+@@ -6065,6 +6091,7 @@ static inline int rtw89_hci_tx_write(str
+ static inline void rtw89_hci_reset(struct rtw89_dev *rtwdev)
+ {
+       rtwdev->hci.ops->reset(rtwdev);
++      rtw89_tx_wait_list_clear(rtwdev);
+ }
+ static inline int rtw89_hci_start(struct rtw89_dev *rtwdev)
+@@ -7122,11 +7149,12 @@ static inline struct sk_buff *rtw89_allo
+       return dev_alloc_skb(length);
+ }
+-static inline void rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev,
++static inline bool rtw89_core_tx_wait_complete(struct rtw89_dev *rtwdev,
+                                              struct rtw89_tx_skb_data *skb_data,
+                                              bool tx_done)
+ {
+       struct rtw89_tx_wait_info *wait;
++      bool ret = false;
+       rcu_read_lock();
+@@ -7134,11 +7162,14 @@ static inline void rtw89_core_tx_wait_co
+       if (!wait)
+               goto out;
++      ret = true;
+       wait->tx_done = tx_done;
+-      complete(&wait->completion);
++      /* Don't access skb anymore after completion */
++      complete_all(&wait->completion);
+ out:
+       rcu_read_unlock();
++      return ret;
+ }
+ static inline bool rtw89_is_mlo_1_1(struct rtw89_dev *rtwdev)
+--- a/drivers/net/wireless/realtek/rtw89/pci.c
++++ b/drivers/net/wireless/realtek/rtw89/pci.c
+@@ -464,7 +464,8 @@ static void rtw89_pci_tx_status(struct r
+       struct rtw89_tx_skb_data *skb_data = RTW89_TX_SKB_CB(skb);
+       struct ieee80211_tx_info *info;
+-      rtw89_core_tx_wait_complete(rtwdev, skb_data, tx_status == RTW89_TX_DONE);
++      if (rtw89_core_tx_wait_complete(rtwdev, skb_data, tx_status == RTW89_TX_DONE))
++              return;
+       info = IEEE80211_SKB_CB(skb);
+       ieee80211_tx_info_clear_status(info);
+--- a/drivers/net/wireless/realtek/rtw89/ser.c
++++ b/drivers/net/wireless/realtek/rtw89/ser.c
+@@ -501,7 +501,9 @@ static void ser_reset_trx_st_hdl(struct
+               }
+               drv_stop_rx(ser);
++              wiphy_lock(wiphy);
+               drv_trx_reset(ser);
++              wiphy_unlock(wiphy);
+               /* wait m3 */
+               hal_send_m2_event(ser);
diff --git a/queue-6.16/wifi-rtw89-mcc-stop-tx-during-mcc-prepare.patch b/queue-6.16/wifi-rtw89-mcc-stop-tx-during-mcc-prepare.patch
new file mode 100644 (file)
index 0000000..10c7569
--- /dev/null
@@ -0,0 +1,141 @@
+From stable+bounces-183237-greg=kroah.com@vger.kernel.org Fri Oct  3 17:20:16 2025
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri,  3 Oct 2025 11:20:04 -0400
+Subject: wifi: rtw89: mcc: stop TX during MCC prepare
+To: stable@vger.kernel.org
+Cc: Chih-Kang Chang <gary.chang@realtek.com>, Ping-Ke Shih <pkshih@realtek.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20251003152005.3176758-1-sashal@kernel.org>
+
+From: Chih-Kang Chang <gary.chang@realtek.com>
+
+[ Upstream commit 182c7ff8b87e4edbb2227ede39ae0952da7a0f4a ]
+
+Stop TX during the MCC configuration period to prevent packet leakage.
+The stop time is defined as 'start_tsf - tsf', which means the duration
+from when MCC configuration begins until MCC starts.
+
+Signed-off-by: Chih-Kang Chang <gary.chang@realtek.com>
+Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
+Link: https://patch.msgid.link/20250610130034.14692-6-pkshih@realtek.com
+Stable-dep-of: 3e31a6bc0731 ("wifi: rtw89: fix use-after-free in rtw89_core_tx_kick_off_and_wait()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/wireless/realtek/rtw89/chan.c |   35 ++++++++++++++++++++++++++++++
+ drivers/net/wireless/realtek/rtw89/chan.h |    2 +
+ drivers/net/wireless/realtek/rtw89/core.c |    2 +
+ drivers/net/wireless/realtek/rtw89/core.h |    2 +
+ 4 files changed, 41 insertions(+)
+
+--- a/drivers/net/wireless/realtek/rtw89/chan.c
++++ b/drivers/net/wireless/realtek/rtw89/chan.c
+@@ -1595,6 +1595,35 @@ static bool rtw89_mcc_duration_decision_
+       return false;
+ }
++void rtw89_mcc_prepare_done_work(struct wiphy *wiphy, struct wiphy_work *work)
++{
++      struct rtw89_dev *rtwdev = container_of(work, struct rtw89_dev,
++                                              mcc_prepare_done_work.work);
++
++      lockdep_assert_wiphy(wiphy);
++
++      ieee80211_wake_queues(rtwdev->hw);
++}
++
++static void rtw89_mcc_prepare(struct rtw89_dev *rtwdev, bool start)
++{
++      struct rtw89_mcc_info *mcc = &rtwdev->mcc;
++      struct rtw89_mcc_config *config = &mcc->config;
++
++      if (start) {
++              ieee80211_stop_queues(rtwdev->hw);
++
++              wiphy_delayed_work_queue(rtwdev->hw->wiphy,
++                                       &rtwdev->mcc_prepare_done_work,
++                                       usecs_to_jiffies(config->prepare_delay));
++      } else {
++              wiphy_delayed_work_queue(rtwdev->hw->wiphy,
++                                       &rtwdev->mcc_prepare_done_work, 0);
++              wiphy_delayed_work_flush(rtwdev->hw->wiphy,
++                                       &rtwdev->mcc_prepare_done_work);
++      }
++}
++
+ static int rtw89_mcc_fill_start_tsf(struct rtw89_dev *rtwdev)
+ {
+       struct rtw89_mcc_info *mcc = &rtwdev->mcc;
+@@ -1630,6 +1659,8 @@ static int rtw89_mcc_fill_start_tsf(stru
+       config->start_tsf = start_tsf;
+       config->start_tsf_in_aux_domain = tsf_aux + start_tsf - tsf;
++      config->prepare_delay = start_tsf - tsf;
++
+       return 0;
+ }
+@@ -2219,6 +2250,8 @@ static int rtw89_mcc_start(struct rtw89_
+       rtw89_chanctx_notify(rtwdev, RTW89_CHANCTX_STATE_MCC_START);
+       rtw89_mcc_start_beacon_noa(rtwdev);
++
++      rtw89_mcc_prepare(rtwdev, true);
+       return 0;
+ }
+@@ -2307,6 +2340,8 @@ static void rtw89_mcc_stop(struct rtw89_
+       rtw89_chanctx_notify(rtwdev, RTW89_CHANCTX_STATE_MCC_STOP);
+       rtw89_mcc_stop_beacon_noa(rtwdev);
++
++      rtw89_mcc_prepare(rtwdev, false);
+ }
+ static int rtw89_mcc_update(struct rtw89_dev *rtwdev)
+--- a/drivers/net/wireless/realtek/rtw89/chan.h
++++ b/drivers/net/wireless/realtek/rtw89/chan.h
+@@ -129,6 +129,8 @@ const struct rtw89_chan *__rtw89_mgnt_ch
+ #define rtw89_mgnt_chan_get(rtwdev, link_index) \
+       __rtw89_mgnt_chan_get(rtwdev, __func__, link_index)
++void rtw89_mcc_prepare_done_work(struct wiphy *wiphy, struct wiphy_work *work);
++
+ int rtw89_chanctx_ops_add(struct rtw89_dev *rtwdev,
+                         struct ieee80211_chanctx_conf *ctx);
+ void rtw89_chanctx_ops_remove(struct rtw89_dev *rtwdev,
+--- a/drivers/net/wireless/realtek/rtw89/core.c
++++ b/drivers/net/wireless/realtek/rtw89/core.c
+@@ -4816,6 +4816,7 @@ void rtw89_core_stop(struct rtw89_dev *r
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->coex_bt_devinfo_work);
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->coex_rfk_chk_work);
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->cfo_track_work);
++      wiphy_delayed_work_cancel(wiphy, &rtwdev->mcc_prepare_done_work);
+       cancel_delayed_work_sync(&rtwdev->forbid_ba_work);
+       wiphy_delayed_work_cancel(wiphy, &rtwdev->antdiv_work);
+@@ -5042,6 +5043,7 @@ int rtw89_core_init(struct rtw89_dev *rt
+       wiphy_delayed_work_init(&rtwdev->coex_bt_devinfo_work, rtw89_coex_bt_devinfo_work);
+       wiphy_delayed_work_init(&rtwdev->coex_rfk_chk_work, rtw89_coex_rfk_chk_work);
+       wiphy_delayed_work_init(&rtwdev->cfo_track_work, rtw89_phy_cfo_track_work);
++      wiphy_delayed_work_init(&rtwdev->mcc_prepare_done_work, rtw89_mcc_prepare_done_work);
+       INIT_DELAYED_WORK(&rtwdev->forbid_ba_work, rtw89_forbid_ba_work);
+       wiphy_delayed_work_init(&rtwdev->antdiv_work, rtw89_phy_antdiv_work);
+       rtwdev->txq_wq = alloc_workqueue("rtw89_tx_wq", WQ_UNBOUND | WQ_HIGHPRI, 0);
+--- a/drivers/net/wireless/realtek/rtw89/core.h
++++ b/drivers/net/wireless/realtek/rtw89/core.h
+@@ -5728,6 +5728,7 @@ struct rtw89_mcc_config {
+       struct rtw89_mcc_sync sync;
+       u64 start_tsf;
+       u64 start_tsf_in_aux_domain;
++      u64 prepare_delay;
+       u16 mcc_interval; /* TU */
+       u16 beacon_offset; /* TU */
+ };
+@@ -5858,6 +5859,7 @@ struct rtw89_dev {
+       struct wiphy_delayed_work coex_bt_devinfo_work;
+       struct wiphy_delayed_work coex_rfk_chk_work;
+       struct wiphy_delayed_work cfo_track_work;
++      struct wiphy_delayed_work mcc_prepare_done_work;
+       struct delayed_work forbid_ba_work;
+       struct wiphy_delayed_work antdiv_work;
+       struct rtw89_ppdu_sts_info ppdu_sts;