Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
sys/qwz: implement regulatory domain for 2.4Ghz and 5Ghz
To:
OpenBSD tech <tech@openbsd.org>
Date:
Tue, 19 May 2026 03:20:48 +0200

Download raw body.

Thread
  • Kirill A. Korinsky:

    sys/qwz: implement regulatory domain for 2.4Ghz and 5Ghz

tech@,

I'd like to add support of regulatory domain to qwz.

Without regulatory domain support it uses world, and some chanells not
available from firmware point of view when AP can actually use it.

With this patch my honor can connect to 112 and similar channels.

Ok?

Index: sys/dev/ic/qwz.c
===================================================================
RCS file: /home/cvs/src/sys/dev/ic/qwz.c,v
diff -u -p -r1.29 qwz.c
--- sys/dev/ic/qwz.c	18 May 2026 13:47:32 -0000	1.29
+++ sys/dev/ic/qwz.c	19 May 2026 01:16:14 -0000
@@ -10523,6 +10523,57 @@ qwz_create_reg_rules_from_wmi(uint32_t n
 	return reg_rule_ptr;
 }
 
+struct cur_reg_rule *
+qwz_create_ext_reg_rules_from_wmi(uint32_t num_reg_rules,
+    struct wmi_regulatory_ext_rule *wmi_reg_rule)
+{
+	struct cur_reg_rule *reg_rule_ptr;
+	uint32_t count;
+
+	reg_rule_ptr = mallocarray(num_reg_rules, sizeof(*reg_rule_ptr),
+	    M_DEVBUF, M_NOWAIT | M_ZERO);
+	if (!reg_rule_ptr)
+		return NULL;
+
+	for (count = 0; count < num_reg_rules; count++) {
+		reg_rule_ptr[count].start_freq = FIELD_GET(REG_RULE_START_FREQ,
+		    wmi_reg_rule[count].freq_info);
+		reg_rule_ptr[count].end_freq = FIELD_GET(REG_RULE_END_FREQ,
+		    wmi_reg_rule[count].freq_info);
+		reg_rule_ptr[count].max_bw = FIELD_GET(REG_RULE_MAX_BW,
+		    wmi_reg_rule[count].bw_pwr_info);
+		reg_rule_ptr[count].reg_power = FIELD_GET(REG_RULE_REG_PWR,
+		    wmi_reg_rule[count].bw_pwr_info);
+		reg_rule_ptr[count].ant_gain = FIELD_GET(REG_RULE_ANT_GAIN,
+		    wmi_reg_rule[count].bw_pwr_info);
+		reg_rule_ptr[count].flags = FIELD_GET(REG_RULE_FLAGS,
+		    wmi_reg_rule[count].flag_info);
+		reg_rule_ptr[count].psd_flag = FIELD_GET(REG_RULE_PSD_INFO,
+		    wmi_reg_rule[count].psd_power_info);
+		reg_rule_ptr[count].psd_eirp = FIELD_GET(REG_RULE_PSD_EIRP,
+		    wmi_reg_rule[count].psd_power_info);
+	}
+
+	return reg_rule_ptr;
+}
+
+static uint8_t
+qwz_wmi_ignore_num_extra_rules(struct wmi_regulatory_ext_rule *wmi_reg_rule,
+    uint32_t num_reg_rules)
+{
+	uint8_t num_invalid_5ghz_rules = 0;
+	uint32_t count, start_freq;
+
+	for (count = 0; count < num_reg_rules; count++) {
+		start_freq = FIELD_GET(REG_RULE_START_FREQ,
+		    wmi_reg_rule[count].freq_info);
+		if (start_freq >= ATH12K_MIN_6G_FREQ)
+			num_invalid_5ghz_rules++;
+	}
+
+	return num_invalid_5ghz_rules;
+}
+
 int
 qwz_pull_reg_chan_list_update_ev(struct qwz_softc *sc, struct mbuf *m,
     struct cur_regulatory_info *reg_info)
@@ -10639,8 +10690,139 @@ int
 qwz_pull_reg_chan_list_ext_update_ev(struct qwz_softc *sc, struct mbuf *m,
     struct cur_regulatory_info *reg_info)
 {
-	printf("%s: not implemented\n", __func__);
-	return ENOTSUP;
+	const void **tb;
+	const struct wmi_reg_chan_list_cc_ext_event *chan_list_event_hdr;
+	struct wmi_regulatory_ext_rule *wmi_reg_rule;
+	uint32_t num_2ghz_reg_rules, num_5ghz_reg_rules;
+	uint8_t num_invalid_5ghz_ext_rules;
+	int ret;
+
+	DNPRINTF(QWZ_D_WMI, "%s: processing regulatory ext channel list\n",
+	    __func__);
+
+	tb = qwz_wmi_tlv_parse_alloc(sc, mtod(m, void *), m->m_pkthdr.len);
+	if (tb == NULL) {
+		ret = ENOMEM; /* XXX allocation failure or parsing failure? */
+		printf("%s: failed to parse tlv: %d\n", __func__, ret);
+		return ENOMEM;
+	}
+
+	chan_list_event_hdr = tb[WMI_TAG_REG_CHAN_LIST_CC_EXT_EVENT];
+	if (!chan_list_event_hdr) {
+		printf("%s: failed to fetch reg chan list ext update ev\n",
+		    __func__);
+		free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
+		return EPROTO;
+	}
+
+	reg_info->num_2ghz_reg_rules = chan_list_event_hdr->num_2ghz_reg_rules;
+	reg_info->num_5ghz_reg_rules = chan_list_event_hdr->num_5ghz_reg_rules;
+
+	if (reg_info->num_2ghz_reg_rules > MAX_REG_RULES ||
+	    reg_info->num_5ghz_reg_rules > MAX_REG_RULES) {
+		printf("%s: Num reg rules for 2 GHz/5 GHz exceeds max "
+		    "limit (num_2ghz_reg_rules: %d num_5ghz_reg_rules: %d "
+		    "max_rules: %d)\n", __func__, reg_info->num_2ghz_reg_rules,
+		    reg_info->num_5ghz_reg_rules, MAX_REG_RULES);
+		free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
+		return EINVAL;
+	}
+
+	if (!(reg_info->num_2ghz_reg_rules + reg_info->num_5ghz_reg_rules)) {
+		printf("%s: No 2 GHz/5 GHz regulatory rules available in "
+		    "the ext event info\n", __func__);
+		free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
+		return EINVAL;
+	}
+
+	memcpy(reg_info->alpha2, &chan_list_event_hdr->alpha2, REG_ALPHA2_LEN);
+	reg_info->dfs_region = chan_list_event_hdr->dfs_region;
+	reg_info->phybitmap = chan_list_event_hdr->phybitmap;
+	reg_info->num_phy = chan_list_event_hdr->num_phy;
+	reg_info->phy_id = chan_list_event_hdr->phy_id;
+	reg_info->ctry_code = chan_list_event_hdr->country_id;
+	reg_info->reg_dmn_pair = chan_list_event_hdr->domain_code;
+	reg_info->status_code = qwz_wmi_cc_setting_code_to_reg(
+	    chan_list_event_hdr->status_code);
+	reg_info->is_ext_reg_event = true;
+
+	reg_info->min_bw_2ghz = chan_list_event_hdr->min_bw_2ghz;
+	reg_info->max_bw_2ghz = chan_list_event_hdr->max_bw_2ghz;
+	reg_info->min_bw_5ghz = chan_list_event_hdr->min_bw_5ghz;
+	reg_info->max_bw_5ghz = chan_list_event_hdr->max_bw_5ghz;
+
+	num_2ghz_reg_rules = reg_info->num_2ghz_reg_rules;
+	num_5ghz_reg_rules = reg_info->num_5ghz_reg_rules;
+
+	DNPRINTF(QWZ_D_WMI,
+	    "%s: cc_ext %s dfs %d BW: min_2ghz %d max_2ghz %d min_5ghz %d "
+	    "max_5ghz %d phybitmap 0x%x\n", __func__, reg_info->alpha2,
+	    reg_info->dfs_region, reg_info->min_bw_2ghz,
+	    reg_info->max_bw_2ghz, reg_info->min_bw_5ghz,
+	    reg_info->max_bw_5ghz, reg_info->phybitmap);
+
+	DNPRINTF(QWZ_D_WMI,
+	    "%s: num_2ghz_reg_rules %d num_5ghz_reg_rules %d\n", __func__,
+	    num_2ghz_reg_rules, num_5ghz_reg_rules);
+
+	wmi_reg_rule = (struct wmi_regulatory_ext_rule *)
+	    ((uint8_t *)chan_list_event_hdr + sizeof(*chan_list_event_hdr)
+	    + sizeof(struct wmi_tlv));
+
+	if (num_2ghz_reg_rules) {
+		reg_info->reg_rules_2ghz_ptr =
+		    qwz_create_ext_reg_rules_from_wmi(num_2ghz_reg_rules,
+		    wmi_reg_rule);
+		if (!reg_info->reg_rules_2ghz_ptr) {
+			free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
+			printf("%s: Unable to allocate memory for "
+			    "2 GHz rules\n", __func__);
+			return ENOMEM;
+		}
+
+		qwz_print_reg_rule(sc, "2 GHz", num_2ghz_reg_rules,
+		    reg_info->reg_rules_2ghz_ptr);
+	}
+
+	wmi_reg_rule += num_2ghz_reg_rules;
+
+	/*
+	 * Firmware may include 6 GHz rules in the 5 GHz ext rule list.
+	 * Ignore them here until the stack grows real 6 GHz support.
+	 * XXX implement the 6 GHz regulatory rule path as well.
+	 */
+	num_invalid_5ghz_ext_rules = qwz_wmi_ignore_num_extra_rules(
+	    wmi_reg_rule, num_5ghz_reg_rules);
+	if (num_invalid_5ghz_ext_rules) {
+		DNPRINTF(QWZ_D_WMI,
+		    "%s: cc %s 5 GHz reg rules %d from fw, %d invalid "
+		    "5 GHz rules\n", __func__, reg_info->alpha2,
+		    reg_info->num_5ghz_reg_rules,
+		    num_invalid_5ghz_ext_rules);
+		num_5ghz_reg_rules -= num_invalid_5ghz_ext_rules;
+		reg_info->num_5ghz_reg_rules = num_5ghz_reg_rules;
+	}
+
+	if (num_5ghz_reg_rules) {
+		reg_info->reg_rules_5ghz_ptr =
+		    qwz_create_ext_reg_rules_from_wmi(num_5ghz_reg_rules,
+		    wmi_reg_rule);
+		if (!reg_info->reg_rules_5ghz_ptr) {
+			free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
+			printf("%s: Unable to allocate memory for "
+			    "5 GHz rules\n", __func__);
+			return ENOMEM;
+		}
+
+		qwz_print_reg_rule(sc, "5 GHz", num_5ghz_reg_rules,
+		    reg_info->reg_rules_5ghz_ptr);
+	}
+
+	DNPRINTF(QWZ_D_WMI, "%s: processed regulatory ext channel list\n",
+	    __func__);
+
+	free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb));
+	return 0;
 }
 
 void

-- 
wbr, Kirill