[PATCH v2] wifi: rtw89: regd: stop gating 6GHz on the ww domain

From: Matthew Leach

Date: Fri Jun 26 2026 - 07:23:32 EST


The driver maintains its own block_* bitmaps on top of cfg80211's
regulatory enforcement. These bitmaps are indexed by the position of an
entry in the active regd_map (either the built-in rtw89_regd_map or the
table loaded from a firmware element).

The ww domain is handled outside the regd_map; it lives in a standalone
rtw89_ww_regd object, and rtw89_regd_get_index() returns the
RTW89_REGD_MAX_COUNTRY_NUM sentinel for it. Every block_* lookup site
guards against that sentinel and bails out, which means none of the
block_* checks ever apply to ww. The 6GHz check therefore treats ww as
unconditionally blocked at the driver layer, independently of whatever
cfg80211/wireless-regdb permit.

Make ww a first-class entry in the regd_map instead:

- Reserve slot 0 of rtw89_regd_map for the ww entry and drop the
standalone rtw89_ww_regd object.
- In rtw89_recognize_regd_from_elm(), reserve slot 0 of the
firmware-loaded map for ww and populate it from the built-in
definition via the new rtw89_regd_static_ww_entry() helper.
- Drop the rtw89_regd_is_ww() special case in rtw89_regd_get_index()
and identify ww by alpha2 so the check works for entries in either
map.
- Have rtw89_regd_find_reg_by_name() return NULL on miss instead of
silently substituting ww. Callers that need the previous "fall back
to ww" behaviour (rtw89_regd_notifier_apply()) now do so explicitly
against the active map's slot 0, and rtw89_regd_get_index_by_name()
translates NULL into the RTW89_REGD_MAX_COUNTRY_NUM sentinel so the
existing "unknown alpha2" branches in the block_* setup paths
remain.

With ww at a real index the driver's block_* checks apply uniformly for
all bands and cfg80211 / wireless-regdb become the sole source of truth
for what is permitted on the ww domain.

Signed-off-by: Matthew Leach <matthew.leach@xxxxxxxxxxxxx>
---
Changes in v2:
- Re-word the commit message, clarifying the motivation behind the
patch (removing the explicit 6GHz disablement).
- Re-work the patch to play nicely with firmware loaded regulatory
tables by reserving slot 0 in both static and firmware regulatory lists
for the WW domain.
- Link to v1: https://patch.msgid.link/20260617-rtw89-6ghz-regd-fixes-v1-1-33d744a07d16@xxxxxxxxxxxxx

To: Ping-Ke Shih <pkshih@xxxxxxxxxxx>
Cc: linux-wireless@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
---
drivers/net/wireless/realtek/rtw89/core.h | 1 +
drivers/net/wireless/realtek/rtw89/fw.c | 6 ++++--
drivers/net/wireless/realtek/rtw89/regd.c | 32 +++++++++++++++++++++----------
3 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index fd29dbbb120d..dc9eae2cee69 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -7862,6 +7862,7 @@ void rtw89_chip_cfg_txpwr_ul_tb_offset(struct rtw89_dev *rtwdev,
bool rtw89_legacy_rate_to_bitrate(struct rtw89_dev *rtwdev, u8 legacy_rate, u16 *bitrate);
int rtw89_regd_setup(struct rtw89_dev *rtwdev);
int rtw89_regd_init_hint(struct rtw89_dev *rtwdev);
+const struct rtw89_regd *rtw89_regd_static_ww_entry(void);
const char *rtw89_regd_get_string(enum rtw89_regulation_type regd);
void rtw89_traffic_stats_init(struct rtw89_dev *rtwdev,
struct rtw89_traffic_stats *stats);
diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c
index 17704f054727..77f320cae01b 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.c
+++ b/drivers/net/wireless/realtek/rtw89/fw.c
@@ -1347,10 +1347,11 @@ int rtw89_recognize_regd_from_elm(struct rtw89_dev *rtwdev,
{
const struct __rtw89_fw_regd_element *regd_elm = &elm->u.regd;
struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info;
- u32 num_ents = le32_to_cpu(regd_elm->num_ents);
struct rtw89_regd_data *p;
struct rtw89_regd regd;
- u32 i = 0;
+ /* +1 because we reserve index 0 for WW */
+ u32 num_ents = le32_to_cpu(regd_elm->num_ents) + 1;
+ u32 i = 1;

if (num_ents > RTW89_REGD_MAX_COUNTRY_NUM) {
rtw89_warn(rtwdev,
@@ -1373,6 +1374,7 @@ int rtw89_recognize_regd_from_elm(struct rtw89_dev *rtwdev,
return -ENOMEM;

p->nr = num_ents;
+ p->map[0] = *rtw89_regd_static_ww_entry();
rtw89_for_each_in_regd_element(&regd, regd_elm)
p->map[i++] = regd;

diff --git a/drivers/net/wireless/realtek/rtw89/regd.c b/drivers/net/wireless/realtek/rtw89/regd.c
index 28466cb35ea2..27ac87374e6e 100644
--- a/drivers/net/wireless/realtek/rtw89/regd.c
+++ b/drivers/net/wireless/realtek/rtw89/regd.c
@@ -21,10 +21,8 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request

static_assert(BITS_PER_TYPE(unsigned long) >= NUM_OF_RTW89_REGD_FUNC);

-static const struct rtw89_regd rtw89_ww_regd =
- COUNTRY_REGD("00", RTW89_WW, RTW89_WW, RTW89_WW, 0x0);
-
static const struct rtw89_regd rtw89_regd_map[] = {
+ COUNTRY_REGD("00", RTW89_WW, RTW89_WW, RTW89_WW, 0x0),
COUNTRY_REGD("AR", RTW89_MEXICO, RTW89_MEXICO, RTW89_FCC, 0x0),
COUNTRY_REGD("BO", RTW89_FCC, RTW89_FCC, RTW89_NA, 0x0),
COUNTRY_REGD("BR", RTW89_FCC, RTW89_FCC, RTW89_FCC, 0x0),
@@ -316,12 +314,12 @@ static const struct rtw89_regd *rtw89_regd_find_reg_by_name(struct rtw89_dev *rt
return &regd_ctrl->map[i];
}

- return &rtw89_ww_regd;
+ return NULL;
}

static bool rtw89_regd_is_ww(const struct rtw89_regd *regd)
{
- return regd == &rtw89_ww_regd;
+ return !memcmp(regd->alpha2, "00", 2);
}

static u8 rtw89_regd_get_index(struct rtw89_dev *rtwdev, const struct rtw89_regd *regd)
@@ -331,9 +329,6 @@ static u8 rtw89_regd_get_index(struct rtw89_dev *rtwdev, const struct rtw89_regd

BUILD_BUG_ON(ARRAY_SIZE(rtw89_regd_map) > RTW89_REGD_MAX_COUNTRY_NUM);

- if (rtw89_regd_is_ww(regd))
- return RTW89_REGD_MAX_COUNTRY_NUM;
-
return regd - regd_ctrl->map;
}

@@ -342,6 +337,10 @@ static u8 rtw89_regd_get_index_by_name(struct rtw89_dev *rtwdev, const char *alp
const struct rtw89_regd *regd;

regd = rtw89_regd_find_reg_by_name(rtwdev, alpha2);
+
+ if (!regd)
+ return RTW89_REGD_MAX_COUNTRY_NUM;
+
return rtw89_regd_get_index(rtwdev, regd);
}

@@ -721,7 +720,7 @@ int rtw89_regd_init_hint(struct rtw89_dev *rtwdev)
return -EINVAL;

chip_regd = rtw89_regd_find_reg_by_name(rtwdev, rtwdev->efuse.country_code);
- if (!rtw89_regd_is_ww(chip_regd)) {
+ if (chip_regd && !rtw89_regd_is_ww(chip_regd)) {
rtwdev->regulatory.regd = chip_regd;
rtwdev->regulatory.programmed = true;

@@ -743,6 +742,11 @@ int rtw89_regd_init_hint(struct rtw89_dev *rtwdev)
return 0;
}

+const struct rtw89_regd *rtw89_regd_static_ww_entry(void)
+{
+ return &rtw89_regd_map[0];
+}
+
static void rtw89_regd_apply_policy_unii4(struct rtw89_dev *rtwdev,
struct wiphy *wiphy)
{
@@ -859,7 +863,15 @@ static void rtw89_regd_notifier_apply(struct rtw89_dev *rtwdev,
struct wiphy *wiphy,
struct regulatory_request *request)
{
- rtwdev->regulatory.regd = rtw89_regd_find_reg_by_name(rtwdev, request->alpha2);
+ const struct rtw89_regd *regd = rtw89_regd_find_reg_by_name(rtwdev, request->alpha2);
+
+ if (!regd) {
+ /* Fallback to WW domain if name not found. */
+ regd = &rtwdev->regulatory.ctrl.map[0];
+ }
+
+ rtwdev->regulatory.regd = regd;
+
/* This notification might be set from the system of distros,
* and it does not expect the regulatory will be modified by
* connecting to an AP (i.e. country ie).

---
base-commit: 8cd9520d35a6c38db6567e97dd93b1f11f185dc6
change-id: 20260616-rtw89-6ghz-regd-fixes-d8f816880bd0

Best regards,
--
Matt