wifi: rtw88: channel switch in AP mode
v5 cover wifi: rtw88: channel switch in AP mode
From: Mehmet Fide <[email protected]>
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, makes the TIM beacon update a wiphy work, then implements channel_switch_beacon on top of the firmware's beacon page.
v5:
- 2/3 is new: update_beacon_work becomes a wiphy work, so that it can look at the channel switch state under the wiphy lock; it checks RTW_FLAG_RUNNING instead of being cancelled in rtw_core_stop(), which runs without the wiphy lock on the IPS path (Ping-Ke)
- 3/3: rtw_fw_csa_active/start/stop() assert the wiphy lock (Ping-Ke)
- 3/3: hw_scan() takes the mutex with guard() (Ping-Ke)
- 3/3: a comment on the association download names the STA+AP combination it skips the download for (Ping-Ke)
- the whole set ran on a PROVE_LOCKING kernel without a lockdep report:
switches with counts 1 to 100, hostapd stopped and restarted during a countdown, scans during a countdown, a firmware crash during a countdown, three stop/idle/start cycles, IBSS, 50 switches with a station attached, and on the RTL8822CU a station interface associating while the AP interface counted down
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 (3):
wifi: rtw88: download the beacon the reserved page was built with
wifi: rtw88: update the beacon from a wiphy work
wifi: rtw88: support channel switch in AP mode
drivers/net/wireless/realtek/rtw88/fw.c | 107 +++++++++++++++--- drivers/net/wireless/realtek/rtw88/fw.h | 6 +- drivers/net/wireless/realtek/rtw88/mac80211.c | 36 +++++- drivers/net/wireless/realtek/rtw88/main.c | 13 ++- drivers/net/wireless/realtek/rtw88/main.h | 4 +- 5 files changed, 141 insertions(+), 25 deletions(-)
Signature · 1 line
2.55.0
PATCH v5 1/3 wifi: rtw88: download the beacon the reserved page was built with
From: Mehmet Fide <[email protected]>
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.
--- drivers/net/wireless/realtek/rtw88/fw.c | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-)
drivers/net/wireless/realtek/rtw88/fw.c +21 -13
@@ -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" ); }
Signature · 1 line
2.55.0
PATCH v5 2/3 wifi: rtw88: update the beacon from a wiphy work
In reply to Mehmet FideFrom: Mehmet Fide <[email protected]>
set_tim() queues update_beacon_work to rebuild the reserved page with the new TIM. Make it a wiphy work so that it runs under the wiphy lock like the other beacon downloads and can look at mac80211 state; the next patch needs that to tell whether a channel switch is being announced.
rtw_core_stop() runs without the wiphy lock on the IPS path and can no longer cancel the work, so the work checks RTW_FLAG_RUNNING under the driver mutex instead, as the watchdog and the C2H work do.
---
New in v5.
drivers/net/wireless/realtek/rtw88/fw.c | 10 +++++++++- drivers/net/wireless/realtek/rtw88/fw.h | 2 +- drivers/net/wireless/realtek/rtw88/mac80211.c | 2 +- drivers/net/wireless/realtek/rtw88/main.c | 4 ++-- drivers/net/wireless/realtek/rtw88/main.h | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-)
drivers/net/wireless/realtek/rtw88/fw.c +9 -1
@@ -1802,14 +1802,22 @@ int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev)return ret; } -void rtw_fw_update_beacon_work (struct work_struct *work) +void rtw_fw_update_beacon_work (struct wiphy *wiphy,struct wiphy_work *work) { struct rtw_dev *rtwdev = container_of(work, struct rtw_dev, update_beacon_work); +lockdep_assert_wiphy (wiphy); +mutex_lock (&rtwdev->mutex); + +if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags)) + goto out; +rtw_fw_download_rsvd_page (rtwdev);rtw_send_rsvd_page_h2c (rtwdev); + +out:mutex_unlock (&rtwdev->mutex); }
drivers/net/wireless/realtek/rtw88/fw.h +1 -1
@@ -863,7 +863,7 @@ 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); -void rtw_fw_update_beacon_work (struct work_struct *work); +void rtw_fw_update_beacon_work (struct wiphy *wiphy,struct wiphy_work *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);
drivers/net/wireless/realtek/rtw88/mac80211.c +1 -1
@@ -551,7 +551,7 @@ static int rtw_ops_set_tim(struct ieee80211_hw *hw, struct ieee80211_sta *sta, {struct rtw_dev *rtwdev = hw->priv; -ieee80211_queue_work (hw, &rtwdev->update_beacon_work); +wiphy_work_queue (hw->wiphy, &rtwdev->update_beacon_work);return 0 ; }
drivers/net/wireless/realtek/rtw88/main.c +2 -2
@@ -1554,7 +1554,6 @@ void rtw_core_stop(struct rtw_dev *rtwdev)mutex_unlock (&rtwdev->mutex);cancel_work_sync (&rtwdev->c2h_work); -cancel_work_sync (&rtwdev->update_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);
@@ -2169,7 +2168,8 @@ int rtw_core_init(struct rtw_dev *rtwdev)INIT_WORK (&rtwdev->c2h_work, rtw_c2h_work);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); +wiphy_work_init (&rtwdev->update_beacon_work, + rtw_fw_update_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);
drivers/net/wireless/realtek/rtw88/main.h +1 -1
@@ -2093,7 +2093,7 @@ struct rtw_dev {struct work_struct c2h_work;struct work_struct ips_work;struct work_struct fw_recovery_work; -struct work_struct update_beacon_work; +struct wiphy_work update_beacon_work;/* used to protect txqs list */ spinlock_t txq_lock;
Signature · 1 line
2.55.0
Ping-Ke Shih
maintainerRE: [PATCH rtw-next v5 2/3] wifi: rtw88: update the beacon from a wiphy work
Quoted 5 lines · Mehmet Fide
From: Mehmet Fide <[email protected]>
set_tim()queuesupdate_beacon_workto rebuild the reserved page with the new TIM. Make it a wiphy work so that it runs under the wiphy lock like the other beacon downloads and can look at mac80211 state;
What "mac80211 state" did you mean? Without wiphy_work, it can't state it?
Quoted 7 lines
the
next patch needs that to tell whether a channel switch is being announced.
rtw_core_stop()runs without the wiphy lock on the IPS path and can no longer cancel the work, so the work checksRTW_FLAG_RUNNINGunder the driver mutex instead, as the watchdog and the C2H work do.
rtw88 doesn't use wiphy work/lock yet. and you are the first one to use wiphy_work. Mixing to use wiphy work/lock and ieee80211_work with driver lock looks not good. Should we just use ieee80211_work to avoid confusing?
Or we should migrate to wiphy work/lock?
For me, I prefer you use ieee80211_work with driver lock for now, because I guess I don't have time to verify the migration...
Ping-Ke
Ping-Ke Shih
maintainerRE: [PATCH rtw-next v5 2/3] wifi: rtw88: update the beacon from a wiphy work
Quoted 17 lines · Ping-Ke Shih
Quoted 5 lines · Mehmet Fide
From: Mehmet Fide <[email protected]>
set_tim()queuesupdate_beacon_workto rebuild the reserved page with the new TIM. Make it a wiphy work so that it runs under the wiphy lock like the other beacon downloads and can look at mac80211 state;What "mac80211 state" did you mean? Without
wiphy_work, it can't state it?Quoted 7 lines
the
next patch needs that to tell whether a channel switch is being announced.
rtw_core_stop()runs without the wiphy lock on the IPS path and can no longer cancel the work, so the work checksRTW_FLAG_RUNNINGunder the driver mutex instead, as the watchdog and the C2H work do.
(forgot to say)
Or you should add wiphy lock on IPS path, and cancel the work.
Quoted 12 lines
rtw88 doesn't use wiphy work/lock yet. and you are the first one to use
wiphy_work. Mixing to use wiphy work/lock andieee80211_workwith driver lock looks not good. Should we just useieee80211_workto avoid confusing?Or we should migrate to wiphy work/lock?
For me, I prefer you use
ieee80211_workwith driver lock for now, because I guess I don't have time to verify the migration...Ping-Ke
Re: [PATCH rtw-next v5 2/3] wifi: rtw88: update the beacon from a wiphy work
Hi Ping-Ke,
Quoted 2 lines · On 2026-10-12 Ping-Ke Shih wrote:
What "mac80211 state" did you mean? Without
wiphy_work, it can't state it?
bss_conf.csa_active: patch 3 gates the TIM download of this work on it, and mac80211 writes it under the wiphy lock. That was the only reason for the wiphy work; the work needs nothing else from mac80211.
Quoted 8 lines
rtw88 doesn't use wiphy work/lock yet. and you are the first one to use
wiphy_work. Mixing to use wiphy work/lock andieee80211_workwith driver lock looks not good. Should we just useieee80211_workto avoid confusing?Or we should migrate to wiphy work/lock?
For me, I prefer you use
ieee80211_workwith driver lock for now, because I guess I don't have time to verify the migration...
Agreed, v6 does that and this patch goes away: update_beacon_work stays as it is, cancelled in rtw_core_stop(). The countdown work of patch 3 becomes an ieee80211 delayed work under the driver mutex as well, and the gate in the TIM download reads the driver's own csa_vif under the mutex instead of mac80211's csa_active, so no wiphy lock is needed anywhere in rtw88. The countdown work still reads csa_active once per beacon interval as its exit condition, a plain read like mt76 does in its beacon path.
Quoted 1 line
Or you should add wiphy lock on IPS path, and cancel the work.
Not needed once both works are regular ones: rtw_core_stop() cancels them, and the IPS path and the firmware recovery both go through it.
Mehmet
PATCH v5 3/3 wifi: rtw88: support channel switch in AP mode
In reply to Mehmet FideFrom: Mehmet Fide <[email protected]>
hostapd's CHAN_SWITCH is refused because the driver does not announce channel switch support, so an AP on rtw88 can only change its channel by being torn down and started again.
Declare WIPHY_FLAG_HAS_CHANNEL_SWITCH and implement ieee80211_ops::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 renew the countdown, and ieee80211_csa_finish() is called once it completes. IBSS, which the flag enables too, shares the page and the work.
The work is a wiphy delayed work of the device and csa_vif names the interface it counts down for; the wiphy lock serializes the work with the mac80211 state it reads and the driver mutex with the page build. The countdown is cancelled when the AP stops, when the interface goes away, on WoWLAN suspend and before a firmware recovery restarts the hardware. The other page downloads that can run
during a countdown, the TIM update, the PG backup on set_key and the one on association, are skipped then, since every beacon fetch advances the countdown; the next countdown download, at most one beacon interval later, carries their changes. A scan is refused while a switch is announced, before the fallback to a software scan: it would take the AP off the channel its stations count down to.
---
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 | 65 ++++++++++++++++++- drivers/net/wireless/realtek/rtw88/fw.h | 4 ++ drivers/net/wireless/realtek/rtw88/mac80211.c | 34 ++++++++-- drivers/net/wireless/realtek/rtw88/main.c | 9 ++- drivers/net/wireless/realtek/rtw88/main.h | 2 + 5 files changed, 107 insertions(+), 7 deletions(-)
drivers/net/wireless/realtek/rtw88/fw.c +64 -1
@@ -1802,6 +1802,35 @@ int rtw_fw_download_rsvd_page(struct rtw_dev *rtwdev)return ret; } +bool rtw_fw_csa_active (struct rtw_dev *rtwdev) +{ +lockdep_assert_wiphy (rtwdev->hw->wiphy); + +return rtwdev->csa_vif && rtwdev->csa_vif->bss_conf.csa_active; +} + +void rtw_fw_csa_start (struct rtw_dev *rtwdev,struct ieee80211_vif *vif) +{ +u32 interval =ieee80211_tu_to_usec (vif->bss_conf.beacon_int); + +lockdep_assert_wiphy (rtwdev->hw->wiphy); + + rtwdev->csa_vif = vif; +wiphy_delayed_work_queue (rtwdev->hw->wiphy, &rtwdev->csa_beacon_work, +usecs_to_jiffies (interval)); +} + +void rtw_fw_csa_stop (struct rtw_dev *rtwdev,struct ieee80211_vif *vif) +{ +lockdep_assert_wiphy (rtwdev->hw->wiphy); + +if (!vif || rtwdev->csa_vif != vif) +return ; + +wiphy_delayed_work_cancel (rtwdev->hw->wiphy, &rtwdev->csa_beacon_work); + rtwdev->csa_vif =NULL ; +} +void rtw_fw_update_beacon_work (struct wiphy *wiphy,struct wiphy_work *work) { struct rtw_dev *rtwdev = container_of(work, struct rtw_dev,
@@ -1811,7 +1840,8 @@ void rtw_fw_update_beacon_work(struct wiphy *wiphy, struct wiphy_work *work)mutex_lock (&rtwdev->mutex); -if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags)) +if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags) || +rtw_fw_csa_active (rtwdev)) goto out;rtw_fw_download_rsvd_page (rtwdev);
@@ -1821,6 +1851,39 @@ void rtw_fw_update_beacon_work(struct wiphy *wiphy, struct wiphy_work *work)mutex_unlock (&rtwdev->mutex); } +void rtw_fw_csa_beacon_work (struct wiphy *wiphy,struct wiphy_work *work) +{ + struct rtw_dev *rtwdev = container_of(work, struct rtw_dev, + csa_beacon_work.work); +struct ieee80211_vif *vif = rtwdev->csa_vif; +unsigned int delay; + +lockdep_assert_wiphy (wiphy); + +mutex_lock (&rtwdev->mutex); + +if (!vif->bss_conf.csa_active) { + rtwdev->csa_vif =NULL ; + goto out; + } + + delay =ieee80211_tu_to_usec (vif->bss_conf.beacon_int); + +if (!ieee80211_beacon_cntdwn_is_complete (vif,0 )) { +rtw_fw_download_rsvd_page (rtwdev); +rtw_send_rsvd_page_h2c (rtwdev); + +wiphy_delayed_work_queue (wiphy, &rtwdev->csa_beacon_work, +usecs_to_jiffies (delay)); + }else { + rtwdev->csa_vif =NULL ; +ieee80211_csa_finish (vif,0 ); + } + +out: +mutex_unlock (&rtwdev->mutex); +} +static void rtw_fw_read_fifo_page (struct rtw_dev *rtwdev,u32 offset,u32 size,u32 *buf,u32 residue,u16 start_pg) {
drivers/net/wireless/realtek/rtw88/fw.h +4 -0
@@ -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 wiphy *wiphy,struct wiphy_work *work); +void rtw_fw_csa_beacon_work (struct wiphy *wiphy,struct wiphy_work *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);
drivers/net/wireless/realtek/rtw88/mac80211.c +29 -5
@@ -235,6 +235,8 @@ static void rtw_ops_remove_interface(struct ieee80211_hw *hw,rtw_dbg (rtwdev,RTW_DBG_STATE ,"stop vif %pM mac_id %d on port %d\n" , vif->addr, rtwvif->mac_id, rtwvif->port); +rtw_fw_csa_stop (rtwdev, vif); +mutex_lock (&rtwdev->mutex);rtw_leave_lps_deep (rtwdev);
@@ -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 of the STA+AP combination: keep the AP's countdown */ +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,8 @@ 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); +if (conf->csa_active) +rtw_fw_csa_start (rtwdev, vif); }if (changed &BSS_CHANGED_BEACON_ENABLED ) {
@@ -489,6 +496,8 @@ static void rtw_ops_stop_ap(struct ieee80211_hw *hw, {struct rtw_dev *rtwdev = hw->priv; +rtw_fw_csa_stop (rtwdev, vif); +mutex_lock (&rtwdev->mutex);rtw_write32_clr (rtwdev,REG_TCR ,BIT_TCR_UPDATE_HGQMD );rtw_write16 (rtwdev,REG_ATIMWND ,ATIMWND_DEFAULT );
@@ -556,6 +565,15 @@ 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; + +rtw_fw_csa_start (rtwdev, vif); +} +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 +644,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 +865,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 +913,23 @@ 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); + +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 +996,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,
drivers/net/wireless/realtek/rtw88/main.c +8 -1
@@ -676,6 +676,10 @@ static void rtw_fw_recovery_work(struct work_struct *work) struct rtw_dev *rtwdev = container_of(work, struct rtw_dev, fw_recovery_work); +wiphy_lock (rtwdev->hw->wiphy); +rtw_fw_csa_stop (rtwdev, rtwdev->csa_vif); +wiphy_unlock (rtwdev->hw->wiphy); +mutex_lock (&rtwdev->mutex);__fw_recovery_work (rtwdev);mutex_unlock (&rtwdev->mutex);
@@ -2170,6 +2174,8 @@ int rtw_core_init(struct rtw_dev *rtwdev)INIT_WORK (&rtwdev->fw_recovery_work, rtw_fw_recovery_work);wiphy_work_init (&rtwdev->update_beacon_work, rtw_fw_update_beacon_work); +wiphy_delayed_work_init (&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 +2299,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 ;
drivers/net/wireless/realtek/rtw88/main.h +2 -0
@@ -2094,6 +2094,8 @@ struct rtw_dev {struct work_struct ips_work;struct work_struct fw_recovery_work;struct wiphy_work update_beacon_work; +struct wiphy_delayed_work csa_beacon_work; +struct ieee80211_vif *csa_vif;/* used to protect txqs list */ spinlock_t txq_lock;
Signature · 1 line
2.55.0
Ping-Ke Shih
maintainerRE: [PATCH rtw-next v5 3/3] wifi: rtw88: support channel switch in AP mode
drivers/net/wireless/realtek/rtw88/fw.c void rtw_fw_update_beacon_work(struct wiphy *wiphy, struct wiphy_work *work) · line 1844
Quoted 10 lines · Mehmet Fide
void
rtw_fw_update_beacon_work(struct wiphy *wiphy, struct wiphy_work *work){
structrtw_dev*rtwdev = container_of(work, structrtw_dev,drivers/net/wireless/realtek/rtw88/fw.c +2
-1@@ -1811,7 +1840,8 @@ void rtw_fw_update_beacon_work(struct wiphy *wiphy, struct wiphy_work *work)mutex_lock (&rtwdev->mutex); -if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags)) +if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags) || +rtw_fw_csa_active (rtwdev))
As my comment in patch 2/3, I think we should cancel the work at core_stop.
Quoted 3 lines
drivers/net/wireless/realtek/rtw88/fw.c +0
-0goto out;rtw_fw_download_rsvd_page (rtwdev);
[...]
drivers/net/wireless/realtek/rtw88/mac80211.c static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw, · line 447
Quoted 6 lines
drivers/net/wireless/realtek/rtw88/mac80211.c +2
-0@@ -438,6 +443,8 @@ 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); +if (conf->csa_active) +rtw_fw_csa_start (rtwdev, vif);
Is this a necessary case? Can't rtw_ops_channel_switch_beacon() handle it?
Quoted 3 lines
drivers/net/wireless/realtek/rtw88/mac80211.c +0
-0}if (changed &BSS_CHANGED_BEACON_ENABLED ) {
[...]
Re: [PATCH rtw-next v5 3/3] wifi: rtw88: support channel switch in AP mode
Hi Ping-Ke,
Quoted 5 lines · On 2026-10-12 Ping-Ke Shih wrote:
drivers/net/wireless/realtek/rtw88/fw.c void rtw_fw_update_beacon_work(struct wiphy *wiphy, struct wiphy_work *work) · line 1844
Quoted 3 lines
drivers/net/wireless/realtek/rtw88/fw.c +2
-1-if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags)) +if (!test_bit (RTW_FLAG_RUNNING , rtwdev->flags) || +rtw_fw_csa_active (rtwdev))As my comment in patch 2/3, I think we should cancel the work at
core_stop.
Yes. In v6 rtw_core_stop() cancels csa_beacon_work next to update_beacon_work and clears csa_vif, which also covers the firmware recovery, so the wiphy_lock() in rtw_fw_recovery_work() goes away.
Quoted 4 lines
drivers/net/wireless/realtek/rtw88/mac80211.c static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw, · line 447
Quoted 2 lines
drivers/net/wireless/realtek/rtw88/mac80211.c +2
-0+if (conf->csa_active) +rtw_fw_csa_start (rtwdev, vif);Is this a necessary case? Can't
rtw_ops_channel_switch_beacon()handle it?
It is the restart case. After ieee80211_restart_hw(), ieee80211_reconfig() replays BSS_CHANGED_BEACON with csa_active still set, and drv_channel_switch_beacon() is only called from __ieee80211_channel_switch(), so without this the countdown would not resume after a firmware recovery. v6 says so in a comment, and the same path stops the countdown when the beacon comes back without csa_active, which is how mac80211 ends a switch.
Mehmet