From: Marcus Glocker Subject: Re: sys/qwz: implement regulatory domain for 2.4Ghz and 5Ghz To: "Kirill A. Korinsky" Cc: tech@openbsd.org Date: Tue, 19 May 2026 07:21:54 +0200 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 >