Download raw body.
sys/qwz: implement regulatory domain for 2.4Ghz and 5Ghz
On Tue, May 19, 2026 at 03:20:48AM +0200, Kirill A. Korinsky wrote:
> 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?
Looks like ported from the Linux ath12k driver, which is good.
Works on the Samsung, can't spot any new regressions.
One nit pick inline.
Otherwise ok mglocker@
> 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
I don't think this function needs to be static.
> +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
>
sys/qwz: implement regulatory domain for 2.4Ghz and 5Ghz