[RFC PATCH] wifi: ath12k: keep regulatory frequency ranges per radio
Lang Jianjun
jianjun.lang at ntu.edu.sg
Thu Oct 29 20:34:48 PDT 2026
From: Jianjun Lang <lang.jianjun at ntu.edu.sg>
WCN7850 uses one pdev for multiple bands. When it receives CN rules,
ath12k_regd_update() skips the 5 GHz range because supports_6ghz is set.
There are no 6 GHz rules to extend the remaining 2.4 GHz range, so the
firmware channel list loses every 5 GHz channel.
The ranges also live in ath12k_base. An event for another radio can
replace them before the first radio updates its channel list.
Store the ranges in ath12k_pdev, indexed by nl80211_band. Build each
set locally and publish it together with the regulatory domain under
base_lock. Take a complete snapshot for channel initialization and
regulatory updates. Copy the selected regulatory domain and ranges
under one lock acquisition so a later event cannot mix generations.
Use the presence of a 5 GHz rule range instead of supports_6ghz to
select the 5 GHz update. Use nl80211_band indices for the hardware PHY
lookup and refresh the 5/6 GHz hardware limits for single-pdev devices.
Reject empty hardware/rule intersections before converting MHz to kHz.
This follows the per-radio design proposed during review of the earlier
single-pdev fix. The earlier report and review are linked below.
Fixes: 0d777aa2ca77 ("wifi: ath12k: fix mac pdev frequency range update")
Reported-by: Shenghan Gao <gsh20040816 at gmail.com>
Link: https://lore.kernel.org/20260715065218.41232-1-gsh20040816@gmail.com/
Suggested-by: Baochen Qiang <baochen.qiang at oss.qualcomm.com>
Link: https://lore.kernel.org/9dd4e992-5810-429f-bc54-036c4eb276a7@oss.qualcomm.com/
Assisted-by: LLM
Signed-off-by: Jianjun Lang <lang.jianjun at ntu.edu.sg>
---
RFC notes
---------
This patch follows the earlier discussion linked in the commit message.
Please review the per-radio ownership and snapshot handling.
Testing
-------
Hardware: WCN7850 hw2.0 PCI (17cb:1107, subsystem 105b:e0f7).
Firmware: WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3.
Runtime kernel: Kali 7.1.5+kali-amd64, package 7.1.5-1kali1.
The installed driver failed a 5 GHz scan under CN. With the candidate
modules, the device selected CN and three scans returned 5 GHz BSSs,
including channels 36 and 149. Two scans requested cache flushing.
In a separate test under the world domain (00), a saved 5 GHz profile
associated, obtained an IPv4 address, passed 15 gateway pings, fetched
an HTTPS page, and disconnected and reconnected successfully.
These two tests do not establish full connectivity under CN.
An earlier CN association completed its WPA handshake but timed out
while obtaining an IP address. The cause remains undetermined.
Both matching Kali modules and upstream driver objects compiled with
W=1 without warnings. Seven userspace range cases passed. A deterministic
userspace test reproduced the old mixed-snapshot result and passed with
this change. These fixtures do not emulate firmware or kernel
concurrency. They are available on request.
No suspend/resume, long-duration, 6 GHz, QCN9274 or QCC2072 hardware test
has been completed. CONFIG_ATH12K_DEBUG is disabled in the running Kali
kernel, so no runtime internal range/channel-count trace was captured.
I would appreciate review of the per-radio ownership and snapshot
handling, and testing on split-pdev devices. In particular, the firmware
may provide a superset of rules for a 6 GHz radio; hardware intersection
must continue to restrict that radio's actual range.
AI tools assisted with the code, tests, and this message.
drivers/net/wireless/ath/ath12k/core.h | 15 ++-
drivers/net/wireless/ath/ath12k/mac.c | 19 ++--
drivers/net/wireless/ath/ath12k/reg.c | 149 +++++++++++++------------
drivers/net/wireless/ath/ath12k/reg.h | 4 +-
4 files changed, 100 insertions(+), 87 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 99d71dab7..193389768 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -850,11 +850,18 @@ struct mlo_timestamp {
u32 mlo_comp_timer;
};
+struct ath12k_reg_freq {
+ u32 start_freq;
+ u32 end_freq;
+};
+
struct ath12k_pdev {
struct ath12k *ar;
u32 pdev_id;
u32 hw_link_id;
struct ath12k_pdev_cap cap;
+ /* Protected by ath12k_base::base_lock. */
+ struct ath12k_reg_freq reg_freq[NUM_NL80211_BANDS];
u8 mac_addr[ETH_ALEN];
struct mlo_timestamp timestamp;
};
@@ -871,11 +878,6 @@ struct ath12k_board_data {
size_t len;
};
-struct ath12k_reg_freq {
- u32 start_freq;
- u32 end_freq;
-};
-
struct ath12k_mlo_memory {
struct target_mem_chunk chunk[ATH12K_QMI_WLANFW_MAX_NUM_MEM_SEG_V01];
int mlo_mem_size;
@@ -1138,9 +1140,6 @@ struct ath12k_base {
/* Denote whether MLO is possible within the device */
bool single_chip_mlo_support;
- struct ath12k_reg_freq reg_freq_2ghz;
- struct ath12k_reg_freq reg_freq_5ghz;
- struct ath12k_reg_freq reg_freq_6ghz;
const struct ath12k_mem_profile_based_param *profile_param;
enum ath12k_qmi_mem_mode target_mem_mode;
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 01f8c6572..4f1c71388 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -14136,7 +14136,7 @@ EXPORT_SYMBOL(ath12k_mac_op_set_rekey_data);
void ath12k_mac_update_freq_range(struct ath12k *ar,
u32 freq_low, u32 freq_high)
{
- if (!(freq_low && freq_high))
+ if (!freq_low || freq_low >= freq_high)
return;
if (ar->freq_range.start_freq || ar->freq_range.end_freq) {
@@ -14237,6 +14237,7 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
u32 supported_bands,
struct ieee80211_supported_band *bands[])
{
+ struct ath12k_reg_freq reg_freq[NUM_NL80211_BANDS];
struct ieee80211_supported_band *band;
struct ath12k_wmi_hal_reg_capabilities_ext_arg *reg_cap;
struct ath12k_base *ab = ar->ab;
@@ -14250,6 +14251,10 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
ARRAY_SIZE(ath12k_6ghz_channels)) !=
ATH12K_NUM_CHANS);
+ spin_lock_bh(&ab->base_lock);
+ memcpy(reg_freq, ar->pdev->reg_freq, sizeof(reg_freq));
+ spin_unlock_bh(&ab->base_lock);
+
reg_cap = &ab->hal_reg_cap[ar->pdev_idx];
if (supported_bands & WMI_HOST_WLAN_2GHZ_CAP) {
@@ -14272,9 +14277,9 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
}
freq_low = max(reg_cap->low_2ghz_chan,
- ab->reg_freq_2ghz.start_freq);
+ reg_freq[NL80211_BAND_2GHZ].start_freq);
freq_high = min(reg_cap->high_2ghz_chan,
- ab->reg_freq_2ghz.end_freq);
+ reg_freq[NL80211_BAND_2GHZ].end_freq);
ath12k_mac_update_ch_list(ar, band,
reg_cap->low_2ghz_chan,
@@ -14318,9 +14323,9 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
band->bitrates = ath12k_a_rates;
freq_low = max(reg_cap->low_5ghz_chan,
- ab->reg_freq_6ghz.start_freq);
+ reg_freq[NL80211_BAND_6GHZ].start_freq);
freq_high = min(reg_cap->high_5ghz_chan,
- ab->reg_freq_6ghz.end_freq);
+ reg_freq[NL80211_BAND_6GHZ].end_freq);
ath12k_mac_update_ch_list(ar, band,
reg_cap->low_5ghz_chan,
@@ -14377,9 +14382,9 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar,
}
freq_low = max(reg_cap->low_5ghz_chan,
- ab->reg_freq_5ghz.start_freq);
+ reg_freq[NL80211_BAND_5GHZ].start_freq);
freq_high = min(reg_cap->high_5ghz_chan,
- ab->reg_freq_5ghz.end_freq);
+ reg_freq[NL80211_BAND_5GHZ].end_freq);
ath12k_mac_update_ch_list(ar, band,
reg_cap->low_5ghz_chan,
diff --git a/drivers/net/wireless/ath/ath12k/reg.c b/drivers/net/wireless/ath/ath12k/reg.c
index 7cf3dbc4f..12199f5d6 100644
--- a/drivers/net/wireless/ath/ath12k/reg.c
+++ b/drivers/net/wireless/ath/ath12k/reg.c
@@ -269,11 +269,13 @@ static void ath12k_copy_regd(struct ieee80211_regdomain *regd_orig,
int ath12k_regd_update(struct ath12k *ar, bool init)
{
struct ath12k_wmi_hal_reg_capabilities_ext_arg *reg_cap;
+ struct ath12k_reg_freq reg_freq[NUM_NL80211_BANDS];
u32 phy_id, freq_low, freq_high, supported_bands;
struct ath12k_hw *ah = ath12k_ar_to_ah(ar);
struct ieee80211_hw *hw = ah->hw;
struct ieee80211_regdomain *regd, *regd_copy = NULL;
int ret, regd_len, pdev_id;
+ bool update_regd;
struct ath12k_base *ab;
long time_left;
@@ -288,69 +290,16 @@ int ath12k_regd_update(struct ath12k *ar, bool init)
*/
}
- supported_bands = ar->pdev->cap.supported_bands;
- reg_cap = &ab->hal_reg_cap[ar->pdev_idx];
-
- /* Possible that due to reg change, current limits for supported
- * frequency changed. Update it. As a first step, reset the
- * previous values and then compute and set the new values.
+ /* Only the selected radio publishes the wiphy regulatory domain.
+ * Every radio still needs its own frequency range updated.
*/
- ar->freq_range.start_freq = 0;
- ar->freq_range.end_freq = 0;
-
- if (supported_bands & WMI_HOST_WLAN_2GHZ_CAP) {
- if (ab->hw_params->single_pdev_only) {
- phy_id = ar->pdev->cap.band[WMI_HOST_WLAN_2GHZ_CAP].phy_id;
- reg_cap = &ab->hal_reg_cap[phy_id];
- }
-
- freq_low = max(reg_cap->low_2ghz_chan, ab->reg_freq_2ghz.start_freq);
- freq_high = min(reg_cap->high_2ghz_chan, ab->reg_freq_2ghz.end_freq);
-
- ath12k_mac_update_freq_range(ar, freq_low, freq_high);
- }
-
- if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP && !ar->supports_6ghz) {
- if (ab->hw_params->single_pdev_only) {
- phy_id = ar->pdev->cap.band[WMI_HOST_WLAN_5GHZ_CAP].phy_id;
- reg_cap = &ab->hal_reg_cap[phy_id];
- }
-
- freq_low = max(reg_cap->low_5ghz_chan, ab->reg_freq_5ghz.start_freq);
- freq_high = min(reg_cap->high_5ghz_chan, ab->reg_freq_5ghz.end_freq);
-
- ath12k_mac_update_freq_range(ar, freq_low, freq_high);
- }
-
- if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP && ar->supports_6ghz) {
- freq_low = max(reg_cap->low_5ghz_chan, ab->reg_freq_6ghz.start_freq);
- freq_high = min(reg_cap->high_5ghz_chan, ab->reg_freq_6ghz.end_freq);
-
- ath12k_mac_update_freq_range(ar, freq_low, freq_high);
- }
-
- /* If one of the radios within ah has already updated the regd for
- * the wiphy, then avoid setting regd again
- */
- if (ah->regd_updated)
- return 0;
-
- /* firmware provides reg rules which are similar for 2 GHz and 5 GHz
- * pdev but 6 GHz pdev has superset of all rules including rules for
- * all bands, we prefer 6 GHz pdev's rules to be used for setup of
- * the wiphy regd.
- * If 6 GHz pdev was part of the ath12k_hw, wait for the 6 GHz pdev,
- * else pick the first pdev which calls this function and use its
- * regd to update global hw regd.
- * The regd_updated flag set at the end will not allow any further
- * updates.
- */
- if (ah->use_6ghz_regd && !ar->supports_6ghz)
- return 0;
-
+ update_regd = !ah->regd_updated &&
+ (!ah->use_6ghz_regd || ar->supports_6ghz);
pdev_id = ar->pdev_idx;
spin_lock_bh(&ab->base_lock);
+ if (!update_regd)
+ goto snapshot;
if (init) {
/* Apply the regd received during init through
@@ -382,13 +331,67 @@ int ath12k_regd_update(struct ath12k *ar, bool init)
if (regd_copy)
ath12k_copy_regd(regd, regd_copy);
+snapshot:
+ /* Pair the range with the domain from the same published update. */
+ memcpy(reg_freq, ar->pdev->reg_freq, sizeof(reg_freq));
spin_unlock_bh(&ab->base_lock);
- if (!regd_copy) {
+ if (update_regd && !regd_copy) {
ret = -ENOMEM;
goto err;
}
+ supported_bands = ar->pdev->cap.supported_bands;
+ reg_cap = &ab->hal_reg_cap[ar->pdev_idx];
+
+ /* Possible that due to reg change, current limits for supported
+ * frequency changed. Update it. As a first step, reset the
+ * previous values and then compute and set the new values.
+ */
+ ar->freq_range.start_freq = 0;
+ ar->freq_range.end_freq = 0;
+
+ if (supported_bands & WMI_HOST_WLAN_2GHZ_CAP) {
+ if (ab->hw_params->single_pdev_only) {
+ phy_id = ar->pdev->cap.band[NL80211_BAND_2GHZ].phy_id;
+ reg_cap = &ab->hal_reg_cap[phy_id];
+ }
+
+ freq_low = max(reg_cap->low_2ghz_chan, reg_freq[NL80211_BAND_2GHZ].start_freq);
+ freq_high = min(reg_cap->high_2ghz_chan, reg_freq[NL80211_BAND_2GHZ].end_freq);
+
+ ath12k_mac_update_freq_range(ar, freq_low, freq_high);
+ }
+
+ if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP &&
+ reg_freq[NL80211_BAND_5GHZ].end_freq) {
+ if (ab->hw_params->single_pdev_only) {
+ phy_id = ar->pdev->cap.band[NL80211_BAND_5GHZ].phy_id;
+ reg_cap = &ab->hal_reg_cap[phy_id];
+ }
+
+ freq_low = max(reg_cap->low_5ghz_chan, reg_freq[NL80211_BAND_5GHZ].start_freq);
+ freq_high = min(reg_cap->high_5ghz_chan, reg_freq[NL80211_BAND_5GHZ].end_freq);
+
+ ath12k_mac_update_freq_range(ar, freq_low, freq_high);
+ }
+
+ if (supported_bands & WMI_HOST_WLAN_5GHZ_CAP && ar->supports_6ghz &&
+ reg_freq[NL80211_BAND_6GHZ].end_freq) {
+ if (ab->hw_params->single_pdev_only) {
+ phy_id = ar->pdev->cap.band[NL80211_BAND_5GHZ].phy_id;
+ reg_cap = &ab->hal_reg_cap[phy_id];
+ }
+
+ freq_low = max(reg_cap->low_5ghz_chan, reg_freq[NL80211_BAND_6GHZ].start_freq);
+ freq_high = min(reg_cap->high_5ghz_chan, reg_freq[NL80211_BAND_6GHZ].end_freq);
+
+ ath12k_mac_update_freq_range(ar, freq_low, freq_high);
+ }
+
+ if (!update_regd)
+ return 0;
+
ret = regulatory_set_wiphy_regd(hw->wiphy, regd_copy);
kfree(regd_copy);
@@ -643,7 +646,8 @@ struct ieee80211_regdomain *
ath12k_reg_build_regd(struct ath12k_base *ab,
struct ath12k_reg_info *reg_info,
enum wmi_vdev_type vdev_type,
- enum ieee80211_ap_reg_power power_type)
+ enum ieee80211_ap_reg_power power_type,
+ struct ath12k_reg_freq *reg_freq)
{
struct ieee80211_regdomain *new_regd = NULL;
struct ath12k_reg_rule *reg_rule, *reg_rule_6ghz;
@@ -711,12 +715,12 @@ ath12k_reg_build_regd(struct ath12k_base *ab,
/* Reset start and end frequency for each band
*/
- ab->reg_freq_5ghz.start_freq = INT_MAX;
- ab->reg_freq_5ghz.end_freq = 0;
- ab->reg_freq_2ghz.start_freq = INT_MAX;
- ab->reg_freq_2ghz.end_freq = 0;
- ab->reg_freq_6ghz.start_freq = INT_MAX;
- ab->reg_freq_6ghz.end_freq = 0;
+ reg_freq[NL80211_BAND_5GHZ].start_freq = INT_MAX;
+ reg_freq[NL80211_BAND_5GHZ].end_freq = 0;
+ reg_freq[NL80211_BAND_2GHZ].start_freq = INT_MAX;
+ reg_freq[NL80211_BAND_2GHZ].end_freq = 0;
+ reg_freq[NL80211_BAND_6GHZ].start_freq = INT_MAX;
+ reg_freq[NL80211_BAND_6GHZ].end_freq = 0;
/* Update reg_rules[] below. Firmware is expected to
* send these rules in order(2G rules first and then 5G)
@@ -728,7 +732,7 @@ ath12k_reg_build_regd(struct ath12k_base *ab,
max_bw = min_t(u16, reg_rule->max_bw,
reg_info->max_bw_2g);
flags = ath12k_get_bw_reg_flags(reg_info->max_bw_2g);
- ath12k_reg_update_freq_range(&ab->reg_freq_2ghz, reg_rule);
+ ath12k_reg_update_freq_range(®_freq[NL80211_BAND_2GHZ], reg_rule);
} else if (reg_info->num_5g_reg_rules &&
(j < reg_info->num_5g_reg_rules)) {
reg_rule = reg_info->reg_rules_5g_ptr + j++;
@@ -743,7 +747,7 @@ ath12k_reg_build_regd(struct ath12k_base *ab,
*/
flags = NL80211_RRF_AUTO_BW |
ath12k_get_bw_reg_flags(reg_info->max_bw_5g);
- ath12k_reg_update_freq_range(&ab->reg_freq_5ghz, reg_rule);
+ ath12k_reg_update_freq_range(®_freq[NL80211_BAND_5GHZ], reg_rule);
} else if (reg_info->is_ext_reg_event && reg_6ghz_number &&
(k < reg_6ghz_number)) {
reg_rule = reg_rule_6ghz + k++;
@@ -752,7 +756,7 @@ ath12k_reg_build_regd(struct ath12k_base *ab,
ath12k_get_bw_reg_flags(max_bw_6ghz);
if (reg_rule->psd_flag)
flags |= NL80211_RRF_PSD;
- ath12k_reg_update_freq_range(&ab->reg_freq_6ghz, reg_rule);
+ ath12k_reg_update_freq_range(®_freq[NL80211_BAND_6GHZ], reg_rule);
} else {
break;
}
@@ -929,15 +933,18 @@ int ath12k_reg_handle_chan_list(struct ath12k_base *ab,
enum wmi_vdev_type vdev_type,
enum ieee80211_ap_reg_power power_type)
{
+ struct ath12k_reg_freq reg_freq[NUM_NL80211_BANDS] = {};
struct ieee80211_regdomain *regd = NULL;
int pdev_idx = reg_info->phy_id;
struct ath12k *ar;
- regd = ath12k_reg_build_regd(ab, reg_info, vdev_type, power_type);
+ regd = ath12k_reg_build_regd(ab, reg_info, vdev_type, power_type,
+ reg_freq);
if (!regd)
return -EINVAL;
spin_lock_bh(&ab->base_lock);
+ memcpy(ab->pdevs[pdev_idx].reg_freq, reg_freq, sizeof(reg_freq));
if (test_bit(ATH12K_FLAG_REGISTERED, &ab->dev_flags)) {
/* Once mac is registered, ar is valid and all CC events from
* firmware is considered to be received due to user requests
diff --git a/drivers/net/wireless/ath/ath12k/reg.h b/drivers/net/wireless/ath/ath12k/reg.h
index 7c37c0d2a..4d3d7a4f5 100644
--- a/drivers/net/wireless/ath/ath12k/reg.h
+++ b/drivers/net/wireless/ath/ath12k/reg.h
@@ -11,6 +11,7 @@
#include <net/regulatory.h>
struct ath12k_base;
+struct ath12k_reg_freq;
struct ath12k;
#define ATH12K_REG_UPDATE_TIMEOUT_HZ (3 * HZ)
@@ -106,7 +107,8 @@ void ath12k_regd_update_work(struct work_struct *work);
struct ieee80211_regdomain *ath12k_reg_build_regd(struct ath12k_base *ab,
struct ath12k_reg_info *reg_info,
enum wmi_vdev_type vdev_type,
- enum ieee80211_ap_reg_power power_type);
+ enum ieee80211_ap_reg_power power_type,
+ struct ath12k_reg_freq *reg_freq);
int ath12k_regd_update(struct ath12k *ar, bool init);
int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait);
base-commit: fab2e70afb13485ea4b198b204af33dd15b8132c
--
2.53.0
________________________________
CONFIDENTIALITY: This email is intended solely for the person(s) named and may be confidential and/or privileged. If you are not the intended recipient, please delete it, notify us and do not copy, use, or disclose its contents.
Towards a sustainable earth: Print only when necessary. Thank you.
More information about the ath12k
mailing list