From: Stefan Sperling Subject: add country code support to qwx To: tech@openbsd.org Date: Thu, 28 May 2026 18:06:17 +0200 Send the country code command to firmware in response to a firmware event which we have been ignoring so far. Once the driver sets the country code the firmware sends yet another event which reinitializes the channel map (we already handle this event, but without a country code it always sets channels for the "world" domain). This seems to partly address the problem where qwx fails to associate with APs sometimes. I haven't had a lot of time to test this yet so I'm not sure the problem is really solved. But it doesn't make things worse. ok? handle country code events sent by qwx firmware M sys/dev/ic/qwx.c | 176+ 2- M sys/dev/ic/qwxvar.h | 4+ 0- 2 files changed, 180 insertions(+), 2 deletions(-) commit - eff8b8da346985beb6d74907fff6ffddefcb1ff2 commit + 0011eb694ed868029b1350be16b78dba35476655 blob - c3ac7f8adc456241da1a308e91693bbc782165e7 blob + 553e2b8dd4ac76e3e225558ee38039a9f5de4c00 --- sys/dev/ic/qwx.c +++ sys/dev/ic/qwx.c @@ -421,6 +421,7 @@ qwx_stop(struct ifnet *ifp) qwx_del_task(sc, systq, &sc->setkey_task); qwx_del_task(sc, systq, &sc->ba_task); qwx_del_task(sc, systq, &sc->bgscan_task); + qwx_del_task(sc, systq, &sc->set_cc_task); refcnt_finalize(&sc->task_refs, "qwxstop"); qwx_setkey_clear(sc); @@ -1097,6 +1098,7 @@ qwx_newstate(struct ieee80211com *ic, enum ieee80211_s #if 0 qwx_del_task(sc, systq, &sc->bgscan_done_task); #endif + qwx_del_task(sc, systq, &sc->set_cc_task); } sc->ns_nstate = nstate; @@ -13907,6 +13909,50 @@ qwx_vdev_install_key_compl_event(struct qwx_softc *sc, wakeup(&sc->install_key_done); } +int +qwx_reg_11d_new_cc_event(struct qwx_softc *sc, struct mbuf *m) +{ + const struct wmi_11d_new_cc_ev *ev; + const void **tb; + int ret; + + tb = qwx_wmi_tlv_parse_alloc(sc, mtod(m, void *), m->m_pkthdr.len); + if (tb == NULL) { + ret = ENOMEM; + printf("%s: failed to parse tlv: %d\n", + sc->sc_dev.dv_xname, ret); + return ret; + } + + ev = tb[WMI_TAG_11D_NEW_COUNTRY_EVENT]; + if (!ev) { + printf("%s: failed to fetch 11d new cc ev\n", + sc->sc_dev.dv_xname); + free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb)); + return EPROTO; + } +#if 0 + spin_lock_bh(&ab->base_lock); +#endif + memcpy(&sc->new_alpha2, &ev->new_alpha2, 2); + sc->new_alpha2[2] = '\0'; +#if 0 + spin_unlock_bh(&ab->base_lock); +#endif + + DNPRINTF(QWX_D_WMI, "event 11d new cc %c%c\n", + sc->new_alpha2[0], sc->new_alpha2[1]); + + free(tb, M_DEVBUF, WMI_TAG_MAX * sizeof(*tb)); + + sc->state_11d = ATH11K_11D_IDLE; + sc->completed_11d_scan = 1; + wakeup(&sc->completed_11d_scan); + + qwx_add_task(sc, systq, &sc->set_cc_task); + + return 0; +} void qwx_wmi_tlv_op_rx(struct qwx_softc *sc, struct mbuf *m) { @@ -14027,10 +14073,10 @@ qwx_wmi_tlv_op_rx(struct qwx_softc *sc, struct mbuf *m case WMI_WOW_WAKEUP_HOST_EVENTID: ath11k_wmi_event_wow_wakeup_host(ab, skb); break; +#endif case WMI_11D_NEW_COUNTRY_EVENTID: - ath11k_reg_11d_new_cc_event(ab, skb); + qwx_reg_11d_new_cc_event(sc, m); break; -#endif case WMI_DIAG_EVENTID: /* Ignore. These events trigger tracepoints in Linux. */ break; @@ -18808,6 +18854,133 @@ qwx_wmi_send_scan_chan_list_cmd(struct qwx_softc *sc, } int +qwx_wmi_send_set_current_country_cmd(struct qwx_softc *sc, + int pdev_id, struct wmi_set_current_country_params *param) +{ + struct qwx_pdev_wmi *wmi = &sc->wmi.wmi[pdev_id]; + struct wmi_set_current_country_cmd *cmd; + struct mbuf *m; + int ret; + + m = qwx_wmi_alloc_mbuf(sizeof(*cmd)); + if (!m) + return ENOMEM; + + cmd = (struct wmi_set_current_country_cmd *)(mtod(m, uint8_t *) + + sizeof(struct ath11k_htc_hdr) + sizeof(struct wmi_cmd_hdr)); + cmd->tlv_header = + FIELD_PREP(WMI_TLV_TAG, WMI_TAG_SET_CURRENT_COUNTRY_CMD) | + FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE); + + cmd->pdev_id = pdev_id; + memcpy(&cmd->new_alpha2, ¶m->alpha2, 3); + + ret = qwx_wmi_cmd_send(wmi, m, WMI_SET_CURRENT_COUNTRY_CMDID); + if (ret) { + if (ret != ESHUTDOWN) { + printf("%s: failed to send " + "WMI_SET_CURRENT_COUNTRY_CMDID: %d\n", + sc->sc_dev.dv_xname, ret); + } + m_freem(m); + return ret; + } + + DNPRINTF(QWX_D_WMI, + "%s: cmd set current country pdev id %d alpha2 %c%c\n", + __func__, pdev_id, param->alpha2[0], param->alpha2[1]); + + return ret; +} + +int +qwx_wmi_send_init_country_cmd(struct qwx_softc *sc, int pdev_id, + struct wmi_init_country_params *init_cc_params) +{ + struct qwx_pdev_wmi *wmi = &sc->wmi.wmi[pdev_id]; + struct wmi_init_country_cmd *cmd; + struct mbuf *m; + int ret; + + m = qwx_wmi_alloc_mbuf(sizeof(*cmd)); + if (!m) + return ENOMEM; + + cmd = (struct wmi_init_country_cmd *)(mtod(m, uint8_t *) + + sizeof(struct ath11k_htc_hdr) + sizeof(struct wmi_cmd_hdr)); + cmd->tlv_header = + FIELD_PREP(WMI_TLV_TAG, + WMI_TAG_SET_INIT_COUNTRY_CMD) | + FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE); + + cmd->pdev_id = pdev_id; + + switch (init_cc_params->flags) { + case ALPHA_IS_SET: + cmd->init_cc_type = WMI_COUNTRY_INFO_TYPE_ALPHA; + memcpy((uint8_t *)&cmd->cc_info.alpha2, + init_cc_params->cc_info.alpha2, 3); + break; + case CC_IS_SET: + cmd->init_cc_type = WMI_COUNTRY_INFO_TYPE_COUNTRY_CODE; + cmd->cc_info.country_code = + init_cc_params->cc_info.country_code; + break; + case REGDMN_IS_SET: + cmd->init_cc_type = WMI_COUNTRY_INFO_TYPE_REGDOMAIN; + cmd->cc_info.regdom_id = init_cc_params->cc_info.regdom_id; + break; + default: + DPRINTF("%s: unknown cc params flags: 0x%x", + init_cc_params->flags); + ret = EINVAL; + goto err; + } + + ret = qwx_wmi_cmd_send(wmi, m, WMI_SET_INIT_COUNTRY_CMDID); + if (ret) { + printf("%s: failed to send WMI_SET_INIT_COUNTRY CMD :%d\n", + sc->sc_dev.dv_xname, ret); + goto err; + } + + DNPRINTF(QWX_D_WMI, "cmd set init country"); + + return 0; +err: + m_freem(m); + return ret; +} + +void +qwx_set_cc_task(void *arg) +{ + struct qwx_softc *sc = arg; + struct wmi_set_current_country_params set_current_param = {}; + struct wmi_init_country_params init_country_param = {}; + int i; + + if (sc->hw_params.current_cc_support) { + memcpy(&set_current_param.alpha2, sc->new_alpha2, 2); + for (i = 0; i < sc->num_radios; i++) { + qwx_wmi_send_set_current_country_cmd(sc, i, + &set_current_param); + } + } else { + init_country_param.flags = ALPHA_IS_SET; + memcpy(&init_country_param.cc_info.alpha2, sc->new_alpha2, 2); + init_country_param.cc_info.alpha2[2] = 0; + for (i = 0; i < sc->num_radios; i++) { + qwx_wmi_send_init_country_cmd(sc, i, + &init_country_param); + } + } + + refcnt_rele_wake(&sc->task_refs); +} + + +int qwx_wmi_send_11d_scan_start_cmd(struct qwx_softc *sc, struct wmi_11d_scan_start_params *param, uint8_t pdev_id) { @@ -26747,6 +26920,7 @@ qwx_attach(struct qwx_softc *sc) task_set(&sc->setkey_task, qwx_setkey_task, sc); task_set(&sc->ba_task, qwx_ba_task, sc); task_set(&sc->bgscan_task, qwx_bgscan_task, sc); + task_set(&sc->set_cc_task, qwx_set_cc_task, sc); timeout_set_proc(&sc->scan.timeout, qwx_scan_timeout, sc); #if NBPFILTER > 0 qwx_radiotap_attach(sc); blob - cc59b3764f7b0c40b23cee270fa0a112009b2ffb blob + 3d4c9865bd00034c5491dcdf9c224a8fb252f9fd --- sys/dev/ic/qwxvar.h +++ sys/dev/ic/qwxvar.h @@ -1869,6 +1869,10 @@ struct qwx_softc { struct task ba_task; struct qwx_ba_task_data ba_rx; + /* Task for firmware country code updates. */ + uint8_t new_alpha2[3]; + struct task set_cc_task; + enum ath11k_11d_state state_11d; int completed_11d_scan; uint32_t vdev_id_11d_scan;