Download raw body.
add country code support to qwx
On Thu, May 28, 2026 at 06:06:17PM +0200, Stefan Sperling wrote:
> 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?
>
do you really want to keep those if 0s in there?
ok mlarkin either way, please see below for a question
>
> 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
I know you didnt change this part, but the workflow around canceling
background scans and active scans burned me in both urtwx and mwx. for example,
if the results of a previous scan come back in the middle of an auth/assoc
sequence, you get yanked out of that transaction and back to a weird halfway
done state. it takes a full ifconfig down/up sequence to reset net80211 back to
a known good configuration. eventually if you try enough times, you can get
associated in the window where the old scans haven't yet completed.
It appears that after you're in RUN state, old scans completing don't matter
and don't drive the net80211 state machine that way anymore.
I mention this because i generally have a hard time connecting qwx (taking
several attempts usually if it works at all), and now that I know more about
what is going on under the hood, I'm wondering if something similar might
be going on with qwx? are we missing a scan cancel? (this scan cancel above
is if 0'ed out...)
thoughts?
> + 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;
>
add country code support to qwx