All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH rtw-next v6 0/2] wifi: rtw88: channel switch in AP mode
@ 2026-10-13  7:30 Mehmet Fide
  2026-10-13  7:30 ` [PATCH rtw-next v6 1/2] wifi: rtw88: download the beacon the reserved page was built with Mehmet Fide
  2026-10-13  7:30 ` [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode Mehmet Fide
  0 siblings, 2 replies; 7+ messages in thread
From: Mehmet Fide @ 2026-10-13  7:30 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, mehmet.fide

From: Mehmet Fide <mehmet.fide@screeningeagle.com>

An rtw88 AP cannot change its channel: the driver does not announce
channel switch support, so hostapd's CHAN_SWITCH is refused and the
only way is to tear the AP down. The series fixes the beacon download
the reserved page build makes twice, then implements
channel_switch_beacon on top of the firmware's beacon page.

Tested on an RTL8821CU, an RTL8822CU and an RTL8822BU (USB) with
hostapd 2.10 as AP: switches with counts 1 to 50, scans during a
countdown, hostapd stopped and started, the USB driver unbound and
bound and a firmware crash during a countdown, IBSS, a station
interface on the 8822CU associating while its AP counted down, and a
Windows station following the switches without a re-association. The
8821CU and 8822CU set was run on a PROVE_LOCKING kernel without a
lockdep report.

v6:
- the wiphy work patch of v5 is gone: the countdown work is an
  ieee80211 delayed work and csa_vif is driver state, both under the
  driver mutex, so rtw88 takes no wiphy lock anywhere (Ping-Ke)
- rtw_core_stop() cancels the countdown work and clears csa_vif, which
  covers the IPS path and the firmware recovery; the wiphy_lock() in
  the recovery work is gone (Ping-Ke)
- the BSS_CHANGED_BEACON path says why it starts the countdown, the
  replay after a hardware restart, and stops it when the beacon comes
  back without a switch announced (Ping-Ke)
- the downloads are paced from the first one with an absolute due
  time. Queued after each download, they drifted by about 10 ms per
  beacon interval on the ordered mac80211 workqueue; on a count of 50
  the AP switched half a second after its station had, and the
  station re-associated

v4:
- 1/2: Reviewed-by from Luka
- 2/2: the countdown work is back in the device, initialised once,
  with csa_vif naming the interface; no interface iteration and no
  lookup through the reserved page any more (Ping-Ke)
- 2/2: the countdown is cancelled before a firmware recovery restarts
  the hardware, instead of guarding the work with flags (Ping-Ke)
- 2/2: the page download on association skips a running countdown,
  like the TIM update and the PG backup (Luka)
- 2/2: the scan refusal comes before the scan offload test, so that
  firmware without scan offload does not fall back to a software scan
  during a countdown (Luka)

v3:
- 1/2: the beacon out-pointer is initialised in the caller (Luka)
- 2/2: the countdown work lives in the interface, remove_interface
  cancels it unconditionally, the queue wrapper and the comments are
  gone (Ping-Ke)
- 2/2: the TIM update and the PG backup on set_key skip the page
  download while a switch is announced; every beacon fetch advances
  the countdown (Luka)
- 2/2: the commit message names the refused hardware scan (Ping-Ke)

v2:
- 2/2: a hardware scan is refused while a switch is announced
- the rtw89 mention is gone from the cover

Mehmet Fide (2):
  wifi: rtw88: download the beacon the reserved page was built with
  wifi: rtw88: support channel switch in AP mode

 drivers/net/wireless/realtek/rtw88/fw.c       | 120 +++++++++++++++---
 drivers/net/wireless/realtek/rtw88/fw.h       |   4 +
 drivers/net/wireless/realtek/rtw88/mac80211.c |  44 ++++++-
 drivers/net/wireless/realtek/rtw88/main.c     |   6 +-
 drivers/net/wireless/realtek/rtw88/main.h     |   3 +
 5 files changed, 156 insertions(+), 21 deletions(-)


base-commit: fbfa27d9b06f025c5f2cfa79d0e01e6400eeff2a
-- 
2.55.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH rtw-next v6 1/2] wifi: rtw88: download the beacon the reserved page was built with
  2026-10-13  7:30 [PATCH rtw-next v6 0/2] wifi: rtw88: channel switch in AP mode Mehmet Fide
@ 2026-10-13  7:30 ` Mehmet Fide
  2026-10-13  7:30 ` [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode Mehmet Fide
  1 sibling, 0 replies; 7+ messages in thread
From: Mehmet Fide @ 2026-10-13  7:30 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, mehmet.fide

From: Mehmet Fide <mehmet.fide@screeningeagle.com>

rtw_fw_download_rsvd_page() downloads the beacon twice: once as part of
the reserved page and once more on its own, so that the firmware ends up
with a TX descriptor that describes the beacon rather than the whole
page. The second download fetched a new beacon from mac80211 instead of
downloading the one the page already holds.

Besides the extra work, every beacon fetch advances the DTIM count and,
while a channel switch is announced, the CSA countdown; doing it twice
per update lets a countdown that starts at 2 reach 0, which mac80211
warns about. Hand the beacon skb out of the page build and download
that. The hw scan, which downloads the beacon without a page build,
keeps fetching its own.

Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com>
Reviewed-by: Luka Gejak <luka.gejak@linux.dev>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
---

Notes:
    Changes in v6:
    - none
    
    Changes in v5:
    - none
    
    Changes in v4:
    - none, rebased on rtw-next
    
    Changes in v3:
    - the beacon out-pointer is initialised in the caller

 drivers/net/wireless/realtek/rtw88/fw.c | 34 +++++++++++++++----------
 1 file changed, 21 insertions(+), 13 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c
index 945fedcd375b..3cd17a3bb494 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.c
+++ b/drivers/net/wireless/realtek/rtw88/fw.c
@@ -1622,7 +1622,8 @@ static int  __rtw_build_rsvd_page_from_vifs(struct rtw_dev *rtwdev)
 	return 0;
 }
 
-static u8 *rtw_build_rsvd_page(struct rtw_dev *rtwdev, u32 *size)
+static u8 *rtw_build_rsvd_page(struct rtw_dev *rtwdev, u32 *size,
+			       struct sk_buff **beacon)
 {
 	const struct rtw_chip_info *chip = rtwdev->chip;
 	struct ieee80211_hw *hw = rtwdev->hw;
@@ -1702,13 +1703,15 @@ static u8 *rtw_build_rsvd_page(struct rtw_dev *rtwdev, u32 *size)
 	list_for_each_entry(rsvd_pkt, &rtwdev->rsvd_page_list, build_list) {
 		rtw_rsvd_page_list_to_buf(rtwdev, page_size, page_margin,
 					  page, buf, rsvd_pkt);
-		if (page == 0)
+		if (page == 0) {
 			page += rtw_len_to_page(rsvd_pkt->skb->len +
 						tx_desc_sz, page_size);
-		else
+			/* the caller downloads it once more on its own */
+			*beacon = rsvd_pkt->skb;
+		} else {
 			page += rtw_len_to_page(rsvd_pkt->skb->len, page_size);
-
-		kfree_skb(rsvd_pkt->skb);
+			kfree_skb(rsvd_pkt->skb);
+		}
 		rsvd_pkt->skb = NULL;
 	}
 
@@ -1723,11 +1726,12 @@ static u8 *rtw_build_rsvd_page(struct rtw_dev *rtwdev, u32 *size)
 	return NULL;
 }
 
-static int rtw_download_beacon(struct rtw_dev *rtwdev)
+/* the beacon the page was built with, or a fresh one for the hw scan */
+static int rtw_download_beacon(struct rtw_dev *rtwdev, struct sk_buff *beacon)
 {
 	struct ieee80211_hw *hw = rtwdev->hw;
 	struct rtw_rsvd_page *rsvd_pkt;
-	struct sk_buff *skb;
+	struct sk_buff *skb = beacon;
 	int ret = 0;
 
 	rsvd_pkt = list_first_entry_or_null(&rtwdev->rsvd_page_list,
@@ -1744,7 +1748,8 @@ static int rtw_download_beacon(struct rtw_dev *rtwdev)
 		return -EINVAL;
 	}
 
-	skb = rtw_get_rsvd_page_skb(hw, rsvd_pkt);
+	if (!skb)
+		skb = rtw_get_rsvd_page_skb(hw, rsvd_pkt);
 	if (!skb) {
 		rtw_err(rtwdev, "failed to get beacon skb\n");
 		return -ENOMEM;
@@ -1754,18 +1759,20 @@ static int rtw_download_beacon(struct rtw_dev *rtwdev)
 	if (ret)
 		rtw_err(rtwdev, "failed to download drv rsvd page\n");
 
-	dev_kfree_skb(skb);
+	if (!beacon)
+		dev_kfree_skb(skb);
 
 	return ret;
 }
 
 int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev)
 {
-	u8 *buf;
+	struct sk_buff *beacon = NULL;
 	u32 size;
+	u8 *buf;
 	int ret;
 
-	buf = rtw_build_rsvd_page(rtwdev, &size);
+	buf = rtw_build_rsvd_page(rtwdev, &size, &beacon);
 	if (!buf) {
 		rtw_err(rtwdev, "failed to build rsvd page pkt\n");
 		return -ENOMEM;
@@ -1782,13 +1789,14 @@ int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev)
 	 * the beacon again to replace the TX desc header, and we will get
 	 * a correct tx_desc for the beacon in the rsvd page.
 	 */
-	ret = rtw_download_beacon(rtwdev);
+	ret = rtw_download_beacon(rtwdev, beacon);
 	if (ret) {
 		rtw_err(rtwdev, "failed to download beacon\n");
 		goto free;
 	}
 
 free:
+	dev_kfree_skb(beacon);
 	kfree(buf);
 
 	return ret;
@@ -2345,7 +2353,7 @@ int rtw_hw_scan_offload(struct rtw_dev *rtwdev, struct ieee80211_vif *vif,
 	rtw_fw_set_scan_offload(rtwdev, &cs_option, rtwvif, &chan_list);
 out:
 	if (rtwdev->ap_active) {
-		ret = rtw_download_beacon(rtwdev);
+		ret = rtw_download_beacon(rtwdev, NULL);
 		if (ret)
 			rtw_err(rtwdev, "HW scan download beacon failed\n");
 	}
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode
  2026-10-13  7:30 [PATCH rtw-next v6 0/2] wifi: rtw88: channel switch in AP mode Mehmet Fide
  2026-10-13  7:30 ` [PATCH rtw-next v6 1/2] wifi: rtw88: download the beacon the reserved page was built with Mehmet Fide
@ 2026-10-13  7:30 ` Mehmet Fide
  2026-10-19  3:31   ` Ping-Ke Shih
  1 sibling, 1 reply; 7+ messages in thread
From: Mehmet Fide @ 2026-10-13  7:30 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, mehmet.fide

From: Mehmet Fide <mehmet.fide@screeningeagle.com>

hostapd's CHAN_SWITCH is refused because the driver does not announce
channel switch support, so an rtw88 AP can only change its channel by
being torn down and started again.

Declare WIPHY_FLAG_HAS_CHANNEL_SWITCH and implement
channel_switch_beacon: the firmware repeats the beacon held in the
first reserved page, so while a switch is announced the page is
downloaded again every beacon interval to advance the countdown, and
ieee80211_csa_finish() is called once it completes. IBSS shares the
page and the work.

The work is a delayed work of the device and csa_vif names the
interface, both under the driver mutex. The countdown is cancelled when
the AP stops, when the interface goes away, on WoWLAN suspend and in
rtw_core_stop(); the BSS_CHANGED_BEACON replay after a hardware
restart resumes it. The other page downloads during a countdown, the
TIM update, the PG backup and the one on association, are skipped
since every beacon fetch advances it; the next countdown download
carries their changes. A scan is refused: it would take the AP off the
channel its stations count down to.

Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com>
---

Notes:
    Changes in v6:
    - the countdown work is an ieee80211 delayed work under the driver
      mutex, csa_vif is driver state under the mutex; no wiphy lock in
      rtw88 (Ping-Ke)
    - rtw_core_stop() cancels the work and clears csa_vif, which covers the
      IPS path and the firmware recovery (Ping-Ke)
    - the downloads are paced from the first one with an absolute due time:
      queued after each download, the countdown drifted by about 10 ms per
      beacon interval on the ordered mac80211 workqueue, and a station
      switched ahead of the AP on a count of 50
    - the BSS_CHANGED_BEACON path says why it starts the countdown (the
      replay after a hardware restart) and stops it when the beacon comes
      back without a switch announced (Ping-Ke)
    
    Changes in v5:
    - rtw_fw_csa_active/start/stop() assert the wiphy lock
    - hw_scan() takes the mutex with guard()
    - the ASSOC path says why it skips the download: the STA+AP combination
    
    Changes in v4:
    - the work and csa_vif live in rtw_dev; no lookup through the reserved page
    - the countdown is stopped before a firmware recovery restarts the hardware
    - a scan is refused while a switch is announced
    - one beacon fetch per interval during the countdown

 drivers/net/wireless/realtek/rtw88/fw.c       | 81 ++++++++++++++++++-
 drivers/net/wireless/realtek/rtw88/fw.h       |  4 +
 drivers/net/wireless/realtek/rtw88/mac80211.c | 39 +++++++--
 drivers/net/wireless/realtek/rtw88/main.c     |  6 +-
 drivers/net/wireless/realtek/rtw88/main.h     |  3 +
 5 files changed, 125 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c
index 3cd17a3bb494..07412aacd7d9 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.c
+++ b/drivers/net/wireless/realtek/rtw88/fw.c
@@ -1802,14 +1802,91 @@ int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev)
 	return ret;
 }
 
+bool rtw_fw_csa_active(struct rtw_dev *rtwdev)
+{
+	lockdep_assert_held(&rtwdev->mutex);
+
+	return !!rtwdev->csa_vif;
+}
+
+/* paced from the first download: a late one does not delay the next */
+static void rtw_fw_csa_queue(struct rtw_dev *rtwdev, struct ieee80211_vif *vif)
+{
+	u32 interval = ieee80211_tu_to_usec(vif->bss_conf.beacon_int);
+	s64 delay;
+
+	rtwdev->csa_due = ktime_add_us(rtwdev->csa_due, interval);
+	delay = ktime_us_delta(rtwdev->csa_due, ktime_get());
+	ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->csa_beacon_work,
+				     usecs_to_jiffies(max(delay, 0)));
+}
+
+void rtw_fw_csa_start(struct rtw_dev *rtwdev, struct ieee80211_vif *vif)
+{
+	lockdep_assert_held(&rtwdev->mutex);
+
+	if (rtwdev->csa_vif == vif)
+		return;
+
+	rtwdev->csa_vif = vif;
+	rtwdev->csa_due = ktime_get();
+	rtw_fw_csa_queue(rtwdev, vif);
+}
+
+void rtw_fw_csa_stop(struct rtw_dev *rtwdev, struct ieee80211_vif *vif)
+{
+	lockdep_assert_held(&rtwdev->mutex);
+
+	if (!vif || rtwdev->csa_vif != vif)
+		return;
+
+	rtwdev->csa_vif = NULL;
+	cancel_delayed_work(&rtwdev->csa_beacon_work);
+}
+
 void rtw_fw_update_beacon_work(struct work_struct *work)
 {
 	struct rtw_dev *rtwdev = container_of(work, struct rtw_dev,
 					      update_beacon_work);
 
 	mutex_lock(&rtwdev->mutex);
-	rtw_fw_download_rsvd_page(rtwdev);
-	rtw_send_rsvd_page_h2c(rtwdev);
+
+	/* a fetch advances the countdown, the next download carries the TIM */
+	if (!rtw_fw_csa_active(rtwdev)) {
+		rtw_fw_download_rsvd_page(rtwdev);
+		rtw_send_rsvd_page_h2c(rtwdev);
+	}
+
+	mutex_unlock(&rtwdev->mutex);
+}
+
+void rtw_fw_csa_beacon_work(struct work_struct *work)
+{
+	struct rtw_dev *rtwdev = container_of(work, struct rtw_dev,
+					      csa_beacon_work.work);
+	struct ieee80211_vif *vif;
+
+	mutex_lock(&rtwdev->mutex);
+
+	vif = rtwdev->csa_vif;
+	if (!vif)
+		goto out;
+
+	if (!vif->bss_conf.csa_active) {
+		rtwdev->csa_vif = NULL;
+		goto out;
+	}
+
+	if (!ieee80211_beacon_cntdwn_is_complete(vif, 0)) {
+		rtw_fw_download_rsvd_page(rtwdev);
+		rtw_send_rsvd_page_h2c(rtwdev);
+		rtw_fw_csa_queue(rtwdev, vif);
+	} else {
+		rtwdev->csa_vif = NULL;
+		ieee80211_csa_finish(vif, 0);
+	}
+
+out:
 	mutex_unlock(&rtwdev->mutex);
 }
 
diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h
index 48ad9ceab6ea..d8e58e4ed836 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.h
+++ b/drivers/net/wireless/realtek/rtw88/fw.h
@@ -863,7 +863,11 @@ void rtw_add_rsvd_page_pno(struct rtw_dev *rtwdev,
 void rtw_add_rsvd_page_sta(struct rtw_dev *rtwdev,
 			   struct rtw_vif *rtwvif);
 int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev);
+bool rtw_fw_csa_active(struct rtw_dev *rtwdev);
+void rtw_fw_csa_start(struct rtw_dev *rtwdev, struct ieee80211_vif *vif);
+void rtw_fw_csa_stop(struct rtw_dev *rtwdev, struct ieee80211_vif *vif);
 void rtw_fw_update_beacon_work(struct work_struct *work);
+void rtw_fw_csa_beacon_work(struct work_struct *work);
 void rtw_send_rsvd_page_h2c(struct rtw_dev *rtwdev);
 int rtw_dump_drv_rsvd_page(struct rtw_dev *rtwdev,
 			   u32 offset, u32 size, u32 *buf);
diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c
index 2a9b09fa76e7..182aecc76820 100644
--- a/drivers/net/wireless/realtek/rtw88/mac80211.c
+++ b/drivers/net/wireless/realtek/rtw88/mac80211.c
@@ -237,6 +237,8 @@ static void rtw_ops_remove_interface(struct ieee80211_hw *hw,
 
 	mutex_lock(&rtwdev->mutex);
 
+	rtw_fw_csa_stop(rtwdev, vif);
+
 	rtw_leave_lps_deep(rtwdev);
 
 	rtw_txq_cleanup(rtwdev, vif->txq);
@@ -395,8 +397,11 @@ static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw,
 		if (vif->cfg.assoc) {
 			rtw_coex_connect_notify(rtwdev, COEX_ASSOCIATE_FINISH);
 
-			rtw_fw_download_rsvd_page(rtwdev);
-			rtw_send_rsvd_page_h2c(rtwdev);
+			/* a station associating while the AP counts down */
+			if (!rtw_fw_csa_active(rtwdev)) {
+				rtw_fw_download_rsvd_page(rtwdev);
+				rtw_send_rsvd_page_h2c(rtwdev);
+			}
 			rtw_fw_default_port(rtwdev, rtwvif);
 			rtw_coex_media_status_notify(rtwdev, vif->cfg.assoc);
 			if (rtw_bf_support)
@@ -438,6 +443,11 @@ static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw,
 		rtw_set_dtim_period(rtwdev, conf->dtim_period);
 		rtw_fw_download_rsvd_page(rtwdev);
 		rtw_send_rsvd_page_h2c(rtwdev);
+		/* restart replay resumes it, a beacon without CSA ends it */
+		if (conf->csa_active)
+			rtw_fw_csa_start(rtwdev, vif);
+		else
+			rtw_fw_csa_stop(rtwdev, vif);
 	}
 
 	if (changed & BSS_CHANGED_BEACON_ENABLED) {
@@ -490,6 +500,7 @@ static void rtw_ops_stop_ap(struct ieee80211_hw *hw,
 	struct rtw_dev *rtwdev = hw->priv;
 
 	mutex_lock(&rtwdev->mutex);
+	rtw_fw_csa_stop(rtwdev, vif);
 	rtw_write32_clr(rtwdev, REG_TCR, BIT_TCR_UPDATE_HGQMD);
 	rtw_write16(rtwdev, REG_ATIMWND, ATIMWND_DEFAULT);
 	rtwdev->ap_active = false;
@@ -556,6 +567,17 @@ static int rtw_ops_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
 	return 0;
 }
 
+static void rtw_ops_channel_switch_beacon(struct ieee80211_hw *hw,
+					  struct ieee80211_vif *vif,
+					  struct cfg80211_chan_def *chandef)
+{
+	struct rtw_dev *rtwdev = hw->priv;
+
+	mutex_lock(&rtwdev->mutex);
+	rtw_fw_csa_start(rtwdev, vif);
+	mutex_unlock(&rtwdev->mutex);
+}
+
 static int rtw_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 			   struct ieee80211_vif *vif, struct ieee80211_sta *sta,
 			   struct ieee80211_key_conf *key)
@@ -626,7 +648,8 @@ static int rtw_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 	}
 
 	/* download new cam settings for PG to backup */
-	if (rtw_get_lps_deep_mode(rtwdev) == LPS_DEEP_MODE_PG)
+	if (rtw_get_lps_deep_mode(rtwdev) == LPS_DEEP_MODE_PG &&
+	    !rtw_fw_csa_active(rtwdev))
 		rtw_fw_download_rsvd_page(rtwdev);
 
 out:
@@ -846,6 +869,7 @@ static int rtw_ops_suspend(struct ieee80211_hw *hw,
 	int ret;
 
 	mutex_lock(&rtwdev->mutex);
+	rtw_fw_csa_stop(rtwdev, rtwdev->csa_vif);
 	ret = rtw_wow_suspend(rtwdev, wowlan);
 	if (ret)
 		rtw_err(rtwdev, "failed to suspend for wow %d\n", ret);
@@ -893,20 +917,24 @@ static int rtw_ops_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	struct rtw_dev *rtwdev = hw->priv;
 	int ret;
 
+	guard(mutex)(&rtwdev->mutex);
+
+	/* a scan takes the AP off the channel its stations count down to */
+	if (rtw_fw_csa_active(rtwdev))
+		return -EBUSY;
+
 	if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_SCAN_OFFLOAD))
 		return 1;
 
 	if (test_bit(RTW_FLAG_SCANNING, rtwdev->flags))
 		return -EBUSY;
 
-	mutex_lock(&rtwdev->mutex);
 	rtw_hw_scan_start(rtwdev, vif, req);
 	ret = rtw_hw_scan_offload(rtwdev, vif, true);
 	if (ret) {
 		rtw_hw_scan_abort(rtwdev);
 		rtw_err(rtwdev, "HW scan failed with status: %d\n", ret);
 	}
-	mutex_unlock(&rtwdev->mutex);
 
 	return ret;
 }
@@ -973,6 +1001,7 @@ const struct ieee80211_ops rtw_ops = {
 	.sta_add		= rtw_ops_sta_add,
 	.sta_remove		= rtw_ops_sta_remove,
 	.set_tim		= rtw_ops_set_tim,
+	.channel_switch_beacon	= rtw_ops_channel_switch_beacon,
 	.set_key		= rtw_ops_set_key,
 	.ampdu_action		= rtw_ops_ampdu_action,
 	.can_aggregate_in_amsdu	= rtw_ops_can_aggregate_in_amsdu,
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 0f23498b5c96..48e8cad221b9 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -1550,11 +1550,13 @@ void rtw_core_stop(struct rtw_dev *rtwdev)
 
 	clear_bit(RTW_FLAG_RUNNING, rtwdev->flags);
 	clear_bit(RTW_FLAG_FW_RUNNING, rtwdev->flags);
+	rtwdev->csa_vif = NULL;
 
 	mutex_unlock(&rtwdev->mutex);
 
 	cancel_work_sync(&rtwdev->c2h_work);
 	cancel_work_sync(&rtwdev->update_beacon_work);
+	cancel_delayed_work_sync(&rtwdev->csa_beacon_work);
 	cancel_delayed_work_sync(&rtwdev->watch_dog_work);
 	cancel_delayed_work_sync(&coex->bt_relink_work);
 	cancel_delayed_work_sync(&coex->bt_reenable_work);
@@ -2170,6 +2172,7 @@ int rtw_core_init(struct rtw_dev *rtwdev)
 	INIT_WORK(&rtwdev->ips_work, rtw_ips_work);
 	INIT_WORK(&rtwdev->fw_recovery_work, rtw_fw_recovery_work);
 	INIT_WORK(&rtwdev->update_beacon_work, rtw_fw_update_beacon_work);
+	INIT_DELAYED_WORK(&rtwdev->csa_beacon_work, rtw_fw_csa_beacon_work);
 	INIT_WORK(&rtwdev->ba_work, rtw_txq_ba_work);
 	skb_queue_head_init(&rtwdev->c2h_queue);
 	skb_queue_head_init(&rtwdev->coex.queue);
@@ -2293,7 +2296,8 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
 	hw->wiphy->available_antennas_rx = hal->antenna_rx;
 
 	hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS |
-			    WIPHY_FLAG_TDLS_EXTERNAL_SETUP;
+			    WIPHY_FLAG_TDLS_EXTERNAL_SETUP |
+			    WIPHY_FLAG_HAS_CHANNEL_SWITCH;
 
 	hw->wiphy->features |= NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR;
 	hw->wiphy->max_scan_ssids = RTW_SCAN_MAX_SSIDS;
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index d59f6e323adf..bc3ebfec11ae 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -2094,6 +2094,9 @@ struct rtw_dev {
 	struct work_struct ips_work;
 	struct work_struct fw_recovery_work;
 	struct work_struct update_beacon_work;
+	struct delayed_work csa_beacon_work;
+	struct ieee80211_vif *csa_vif;
+	ktime_t csa_due;
 
 	/* used to protect txqs list */
 	spinlock_t txq_lock;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* RE: [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode
  2026-10-13  7:30 ` [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode Mehmet Fide
@ 2026-10-19  3:31   ` Ping-Ke Shih
  2026-10-19  8:31     ` Mehmet Fide
  0 siblings, 1 reply; 7+ messages in thread
From: Ping-Ke Shih @ 2026-10-19  3:31 UTC (permalink / raw)
  To: Mehmet Fide
  Cc: Bitterblue Smith, linux-wireless@vger.kernel.org,
	linux-kernel@vger.kernel.org, mehmet.fide@screeningeagle.com

Mehmet Fide <mehmet.fide@gmail.com> wrote:

[...]

>     - the downloads are paced from the first one with an absolute due time:
>       queued after each download, the countdown drifted by about 10 ms per
>       beacon interval on the ordered mac80211 workqueue, and a station
>       switched ahead of the AP on a count of 50

If you download beacon to firmware twice within a period of beacon_int,
a countdown in the air will not present, no? Like this

t (TU)                       0            100             200           300
download beacon            5                   4      3               2
firmware TX beacon           x            x               x             x
air                          5            5               3             2

The 4 is missing.

> +/* paced from the first download: a late one does not delay the next */
> +static void rtw_fw_csa_queue(struct rtw_dev *rtwdev, struct ieee80211_vif *vif)
> +{
> +       u32 interval = ieee80211_tu_to_usec(vif->bss_conf.beacon_int);
> +       s64 delay;
> +
> +       rtwdev->csa_due = ktime_add_us(rtwdev->csa_due, interval);
> +       delay = ktime_us_delta(rtwdev->csa_due, ktime_get());
> +       ieee80211_queue_delayed_work(rtwdev->hw, &rtwdev->csa_beacon_work,
> +                                    usecs_to_jiffies(max(delay, 0)));
> +}

Otherwise, looks good to me. 


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode
  2026-10-19  3:31   ` Ping-Ke Shih
@ 2026-10-19  8:31     ` Mehmet Fide
  2026-10-19  8:52       ` Ping-Ke Shih
  0 siblings, 1 reply; 7+ messages in thread
From: Mehmet Fide @ 2026-10-19  8:31 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, mehmet.fide

Hi Ping-Ke,

On 2026-10-19, Ping-Ke Shih wrote:
> If you download beacon to firmware twice within a period of beacon_int,
> a countdown in the air will not present, no? Like this
[...]
> The 4 is missing.

Yes, when a download runs late past the next TBTT. The absolute due
time keeps the pace, one download per beacon interval on average, so
the AP switches when its stations expect it; v5 queued relative to
the previous download and drifted 10 ms per beacon on USB. The price
of a download that late is one stale beacon and one count never sent.
A station takes its switch time from the counts it sees; at worst one
whose first count is the stale one switches one interval late.

Forced on an RTL8821CU with a 90 ms delay on every second download: a
sniffer saw 9, 8, 8, 6, 4, 4, 2, 2 for count 10, the AP finished on
time, and a Windows station followed ten such switches (counts 10 and
50) without a re-association. Without the delay it happened once in
nine switches.

If you would rather not have it: timing the downloads from the TSF,
10 ms after each TBTT, leaves nearly a whole interval before a beacon
goes out without the download. I tried that briefly on the 8821CU;
with a 60 ms delay every count stays on the air. Which do you prefer?

Thanks,
Mehmet

^ permalink raw reply	[flat|nested] 7+ messages in thread

* RE: [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode
  2026-10-19  8:31     ` Mehmet Fide
@ 2026-10-19  8:52       ` Ping-Ke Shih
  2026-10-19 10:56         ` Mehmet Fide
  0 siblings, 1 reply; 7+ messages in thread
From: Ping-Ke Shih @ 2026-10-19  8:52 UTC (permalink / raw)
  To: Mehmet Fide
  Cc: Bitterblue Smith, linux-wireless@vger.kernel.org,
	linux-kernel@vger.kernel.org, mehmet.fide@screeningeagle.com

Mehmet Fide <mehmet.fide@gmail.com> wrote:
> Hi Ping-Ke,
> 
> On 2026-10-19, Ping-Ke Shih wrote:
> > If you download beacon to firmware twice within a period of beacon_int,
> > a countdown in the air will not present, no? Like this
> [...]
> > The 4 is missing.
> 
> Yes, when a download runs late past the next TBTT. The absolute due
> time keeps the pace, one download per beacon interval on average, so
> the AP switches when its stations expect it; v5 queued relative to
> the previous download and drifted 10 ms per beacon on USB. The price
> of a download that late is one stale beacon and one count never sent.
> A station takes its switch time from the counts it sees; at worst one
> whose first count is the stale one switches one interval late.
> 
> Forced on an RTL8821CU with a 90 ms delay on every second download: a
> sniffer saw 9, 8, 8, 6, 4, 4, 2, 2 for count 10, the AP finished on
> time, and a Windows station followed ten such switches (counts 10 and
> 50) without a re-association. Without the delay it happened once in
> nine switches.
> 
> If you would rather not have it: timing the downloads from the TSF,
> 10 ms after each TBTT, leaves nearly a whole interval before a beacon
> goes out without the download. I tried that briefly on the 8821CU;
> with a 60 ms delay every count stays on the air. Which do you prefer?

It'd be better to see every count stays on the air. Will you implement
to handle TBTT events? Can you share a draft first or a separate patch
(e.g. patch 3/3 to improve missing count)? 

Ping-Ke



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode
  2026-10-19  8:52       ` Ping-Ke Shih
@ 2026-10-19 10:56         ` Mehmet Fide
  0 siblings, 0 replies; 7+ messages in thread
From: Mehmet Fide @ 2026-10-19 10:56 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: Bitterblue Smith, linux-wireless, linux-kernel, mehmet.fide

Hi Ping-Ke,

On 2026-10-19, Ping-Ke Shih wrote:
> It'd be better to see every count stays on the air. Will you implement
> to handle TBTT events? Can you share a draft first or a separate patch
> (e.g. patch 3/3 to improve missing count)?

The USB chips raise no TBTT interrupt, so the countdown work reads the
TSF and queues itself for 10 ms after the next TBTT. That gives a
download about 80 ms to run late before a beacon goes out without it;
one later than that is still followed by its successor at once, as in
v6. A TSF that moved by more than a beacon interval, an IBSS merge or
a reset, restarts the schedule from the TBTT ahead.

On the 8821CU the downloads land 13 to 22 ms after the TBTT; with a
60 ms delay on every second download the sniffer saw 9, 8, 7, 6, 5,
4, 3, 2, 1 for count 10, with 95 ms it saw 9, 8, 8, 6, 6, 4, 4, 2, 2,
and the AP switched at the same time in all cases.

The idea, on top of 2/2:

	/* csa_start: the first download goes with the TBTT ahead */
	tsf = read_tsf(rtwvif->port);     /* 64 bit, 0x560 for port 0 */
	csa_tbtt = tsf - tsf % beacon_int;
	csa_queue();

	/* csa_queue, after every download */
	csa_tbtt += beacon_int;           /* the TBTT this download is for */
	delay = csa_tbtt + 10ms - read_tsf();
	if (delay > beacon_int + 10ms || delay < -beacon_int)
		/* the TSF moved (IBSS merge, reset): start over */
		csa_tbtt = next TBTT after now, delay = csa_tbtt + 10ms - now;
	queue_delayed_work(csa_beacon_work, max(delay, 0));

So a download on time runs 10 ms after its TBTT; one late by less
than ~80 ms still makes the next beacon; one later than that gets a
delay of zero for its successor, the catch-up of v6. Nothing else
changes, the download and ieee80211_csa_finish() stay as they are.

One thing I do not have: the TSF registers of the ports other than 0,
for an AP that comes up next to a station interface. rtw88 only names
REG_USTIME_TSF; is it 0x568 for port 1, and where are ports 2 to 4?
If this is the way you want it, it becomes patch 3/3 of v7 with 1/2
and 2/2 unchanged.

Thanks,
Mehmet

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-19 10:56 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13  7:30 [PATCH rtw-next v6 0/2] wifi: rtw88: channel switch in AP mode Mehmet Fide
2026-10-13  7:30 ` [PATCH rtw-next v6 1/2] wifi: rtw88: download the beacon the reserved page was built with Mehmet Fide
2026-10-13  7:30 ` [PATCH rtw-next v6 2/2] wifi: rtw88: support channel switch in AP mode Mehmet Fide
2026-10-19  3:31   ` Ping-Ke Shih
2026-10-19  8:31     ` Mehmet Fide
2026-10-19  8:52       ` Ping-Ke Shih
2026-10-19 10:56         ` Mehmet Fide

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.