From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr2-f35.google.com (mail-wr2-f35.google.com [74.125.225.99]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CB114415F29 for ; Wed, 30 Sep 2026 07:44:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.99 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790754292; cv=none; b=sesV4yLpYUFnB7vYKxk3Bu2ZDUymDsuWjkw/9tbzrHLGjSZOTm5OupVZHO+yw7X0k8GnOFIIQxW3QG5IHSix8Rl9uHdSpZhd4u0Pjq8NgFJdnTKElmTBk92lIrYHiJqw1GyJ5orDqlPCfwCuqGLzWP+wZk25uGf57MPAKhdaf+A= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790754292; c=relaxed/simple; bh=h2L64es3pLRp+Ca8n/gXHr7JMEK9hubNeLUDmn8LiNk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=b5JdqBMEEjnfFuIpqHW3aIn0EoWlPtIIqflk0Dt3UV+bNoZSaJTzx9QlSfV+kYA/pV70k+uTQZ4WsHn37gC9nDNlSIn8ukT1DJzY11SHZ54hfZNUZGE+XWxAl6DXKvWo0jgMJi2a6WnaRf+lSF1PJT+me5v6Np14yfhG9KCPL7I= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=o9vl1PNf; arc=none smtp.client-ip=74.125.225.99 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="o9vl1PNf" Received: by mail-wr2-f35.google.com with SMTP id ffacd0b85a97d-48434392b02so4345300f8f.3 for ; Wed, 30 Sep 2026 00:44:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790754289; x=1791359089; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Anh174fYqL+IyyMyFQRLhu3moaI8jUFeFhObkFLdPrY=; b=o9vl1PNfg9O9YQnv3lnvpsqxhAAuq+EOhlTObhlkjejs1oYigFSlcbzmeT9OAoqWup C9hB/U9ySNl0AIuzx2i2uAt+1gnETicex05aPKYOGCFMhYJ1ez7s5bOwVzy1/6O/HF4F J9x59jq3CHAYoPZBftj0SIvQLQv9y0rLM0dmQ1G0xjLhe/q7MwbVjHHiVbimE0beCj79 8SHTTZbaRvRfJvt0YPlHbxuQoJ854ycCTD6bR4uZweFwPGbJrYtPwujwItmZcRx5D80p HOgyFtEPr5fAfxIvcH/dA8ytKahEOwf0iWwP3u2nxG9Wfc+FCEg0MuuW73ThcfqzVQ67 XK1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790754289; x=1791359089; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=Anh174fYqL+IyyMyFQRLhu3moaI8jUFeFhObkFLdPrY=; b=dav2rLd4atS3CP0sVYyOE6n/jeVTvY8YXfDJuBm3b19TYUEJED+C7OHCGK4+snZ16T hib0teCU2bBbN0KrUV0qXktQ00FSso2MTUwgUiwFgHWBvxFRGPQt9nqn85CPGeBaAhbM KXCB59EXASFgK2oDR++IkruBk2G8IP31F+nW0xOckTU0N5dWFM14Rkv7C6tMM+GGg/8U 0Zd5SBQGYXZ7hnmpJdKkv3byc+cLgt/O08aXaAyQP2MSf3C2KMYRtrRj/0uEym70bBWe VUzFiQJ/+JEh3FicfLlsbI52A3IBO0fuYl98DKctsvP0oHYTy5yc+zhjofqhwh0ojJVy QVRQ== X-Forwarded-Encrypted: i=1; AKwUvBzzT9ulGwMDO8QTBlL/wi046+CgixhsfyJBM8zBqxQ2galK0lZyK+kLN332viyP1XhoP8JhZehh2SFAYCUH5g==@vger.kernel.org X-Gm-Message-State: AFuF++lj4LdEWS/cGZYCpN7VY+l9EoXnLrbVi2X4HyY3kzm74EpUipdB Z+RLOD9UKBcSF7LBfnfIOoS/hopB1v8i0fhvQUCYep/9Y5x6RKfklXjp X-Gm-Gg: AYBFou0bLoM5S1nm0fN73h1Y2kA0sQWJ3Eosda3dd2kjhiLVu1VtYgIc0/06gFn5X/p 6tf16BAPdBy9bKZp9cfYXxRQjeIqOJFa5Eo+HmtCM9ZcLINmOLqh3yFJUGmsCh1B5z0Q5BY+Xk7 a4aETPMy5IhOvwGVq2h7eRe2zmRS6bHHJJPme0ObK4bzNMvYbJNQYVGjltx+bASmRnBpCwx09ta VOWsvwTeqZASOLSSpRnCKFeXFifmVXQrJ9NI8bAdJx56wsTuf02LR6mHHrS+a+D70c0sVI7jzJA KWTCynh7CdUWfTcr+4M37be+csjDHIbr46qRU179q9EcBHbaae6DgZdxiuSEDPEHAuxq2tT2KhM 76X5l6mr/ExNk5NOmioEZo0cDaclkb1LaezT1zDntYHcwmk4WiPiHZd0UiSHOtFgwoe40FZKQ2D 9AJxVDFOqhEYpgfTKCwKdlrk9lmZ1voU6cuX/L6TUQemugteZgF1mOrm3Hy4Dex4Jr4dGf+/rAw 9Pu X-Received: by 2002:a05:600c:a46:b0:498:943:ccc0 with SMTP id 5b1f17b1804b1-4a01afe8457mr7100685e9.6.1790754288819; Wed, 30 Sep 2026 00:44:48 -0700 (PDT) Received: from deb05.proceq.com ([213.160.61.66]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4a017bad33fsm23934595e9.4.2026.09.30.00.44.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 30 Sep 2026 00:44:48 -0700 (PDT) From: Mehmet Fide To: Ping-Ke Shih Cc: Luka Gejak , Bitterblue Smith , linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org, mehmet.fide@screeningeagle.com Subject: [PATCH rtw-next v2 2/2] wifi: rtw88: support channel switch in AP mode Date: Wed, 30 Sep 2026 09:44:44 +0200 Message-ID: <20260930074444.1991223-3-mehmet.fide@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260930074444.1991223-1-mehmet.fide@gmail.com> References: <20260930074444.1991223-1-mehmet.fide@gmail.com> Precedence: bulk X-Mailing-List: linux-wireless@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Mehmet Fide 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, like update_beacon_work: rtw88 runs one beaconing interface, a hw restart replays add_interface without remove_interface, and the wiphy lock serializes it with the mac80211 state it reads. It is cancelled when the AP stops, when the vif goes away and on WoWLAN suspend, and a hardware scan is refused while a switch is announced, since it would take the AP off the channel its stations count down to. Signed-off-by: Mehmet Fide --- drivers/net/wireless/realtek/rtw88/fw.c | 41 +++++++++++++++++ drivers/net/wireless/realtek/rtw88/fw.h | 1 + drivers/net/wireless/realtek/rtw88/mac80211.c | 46 +++++++++++++++++++ drivers/net/wireless/realtek/rtw88/main.c | 4 +- drivers/net/wireless/realtek/rtw88/main.h | 1 + 5 files changed, 92 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c index 49f09a9f4ed6..1ad25e0539c4 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.c +++ b/drivers/net/wireless/realtek/rtw88/fw.c @@ -1813,6 +1813,47 @@ void rtw_fw_update_beacon_work(struct work_struct *work) mutex_unlock(&rtwdev->mutex); } +/* renew the countdown in the firmware's beacon page until it completes */ +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 rtw_rsvd_page *rsvd_pkt; + struct ieee80211_vif *vif; + unsigned int delay; + + lockdep_assert_wiphy(wiphy); + + mutex_lock(&rtwdev->mutex); + + if (!test_bit(RTW_FLAG_RUNNING, rtwdev->flags)) + goto out; + + rsvd_pkt = list_first_entry_or_null(&rtwdev->rsvd_page_list, + struct rtw_rsvd_page, build_list); + if (!rsvd_pkt || rsvd_pkt->type != RSVD_BEACON) + goto out; + + vif = rtwvif_to_vif(rsvd_pkt->rtwvif); + if (!vif->bss_conf.csa_active) + 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 { + 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) { diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h index 48ad9ceab6ea..982f46c03644 100644 --- a/drivers/net/wireless/realtek/rtw88/fw.h +++ b/drivers/net/wireless/realtek/rtw88/fw.h @@ -864,6 +864,7 @@ 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_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); diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c index 2a9b09fa76e7..7c2a373faff8 100644 --- a/drivers/net/wireless/realtek/rtw88/mac80211.c +++ b/drivers/net/wireless/realtek/rtw88/mac80211.c @@ -235,6 +235,10 @@ 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); + if (rtwvif->net_type == RTW_NET_AP_MODE || + rtwvif->net_type == RTW_NET_AD_HOC) + wiphy_delayed_work_cancel(hw->wiphy, &rtwdev->csa_beacon_work); + mutex_lock(&rtwdev->mutex); rtw_leave_lps_deep(rtwdev); @@ -375,6 +379,13 @@ static void rtw_conf_tx(struct rtw_dev *rtwdev, __rtw_conf_tx(rtwdev, rtwvif, ac); } +/* renew the channel switch countdown one beacon interval from now */ +static void rtw_csa_beacon_queue(struct rtw_dev *rtwdev, u16 beacon_int) +{ + wiphy_delayed_work_queue(rtwdev->hw->wiphy, &rtwdev->csa_beacon_work, + usecs_to_jiffies(ieee80211_tu_to_usec(beacon_int))); +} + static void rtw_ops_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *conf, @@ -438,6 +449,9 @@ 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); + /* a hw restart replays the beacon, not channel_switch_beacon */ + if (conf->csa_active) + rtw_csa_beacon_queue(rtwdev, conf->beacon_int); } if (changed & BSS_CHANGED_BEACON_ENABLED) { @@ -489,6 +503,8 @@ static void rtw_ops_stop_ap(struct ieee80211_hw *hw, { struct rtw_dev *rtwdev = hw->priv; + wiphy_delayed_work_cancel(hw->wiphy, &rtwdev->csa_beacon_work); + mutex_lock(&rtwdev->mutex); rtw_write32_clr(rtwdev, REG_TCR, BIT_TCR_UPDATE_HGQMD); rtw_write16(rtwdev, REG_ATIMWND, ATIMWND_DEFAULT); @@ -556,6 +572,16 @@ 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; + + /* the beacon that starts the countdown was just downloaded */ + rtw_csa_beacon_queue(rtwdev, vif->bss_conf.beacon_int); +} + 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) @@ -845,6 +871,8 @@ static int rtw_ops_suspend(struct ieee80211_hw *hw, struct rtw_dev *rtwdev = hw->priv; int ret; + wiphy_delayed_work_cancel(hw->wiphy, &rtwdev->csa_beacon_work); + mutex_lock(&rtwdev->mutex); ret = rtw_wow_suspend(rtwdev, wowlan); if (ret) @@ -887,10 +915,19 @@ static void rtw_reconfig_complete(struct ieee80211_hw *hw, mutex_unlock(&rtwdev->mutex); } +static void rtw_csa_active_iter(void *data, struct ieee80211_vif *vif) +{ + bool *csa_active = data; + + if (vif->bss_conf.csa_active) + *csa_active = true; +} + static int rtw_ops_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_scan_request *req) { struct rtw_dev *rtwdev = hw->priv; + bool csa_active = false; int ret; if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_SCAN_OFFLOAD)) @@ -900,6 +937,14 @@ static int rtw_ops_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return -EBUSY; mutex_lock(&rtwdev->mutex); + + /* the stations count down to the new channel and expect the AP there */ + rtw_iterate_vifs(rtwdev, rtw_csa_active_iter, &csa_active); + if (csa_active) { + mutex_unlock(&rtwdev->mutex); + return -EBUSY; + } + rtw_hw_scan_start(rtwdev, vif, req); ret = rtw_hw_scan_offload(rtwdev, vif, true); if (ret) { @@ -973,6 +1018,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..6a2c333d1a01 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -2170,6 +2170,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); + 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 +2294,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..21d65692e809 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -2094,6 +2094,7 @@ struct rtw_dev { struct work_struct ips_work; struct work_struct fw_recovery_work; struct work_struct update_beacon_work; + struct wiphy_delayed_work csa_beacon_work; /* used to protect txqs list */ spinlock_t txq_lock; -- 2.55.0