From: Mike Larkin Subject: Re: qwx: add 40MHz channels and fix 11n phy mode To: tech@openbsd.org Date: Thu, 28 May 2026 15:32:48 -0700 On Thu, May 28, 2026 at 09:36:21PM +0200, Stefan Sperling wrote: > Make 40 MHz channels work on qwx, and also fix phy mode settings > configured in firmware for 11n mode. > > When qwx_peer_assoc_h_phymode() is called via qwx_assoc() we always > set an 11a/b/g phy mode because 11n negotiation happens later. > We need to update the peer in qwx_run() to configure 11n mode. > For reference, compare how iwx does things. > > This depends on the the other two qwx diffs I sent earlier today. > > ok? > this reads ok to me, so ok mlarkin. I didn't test though; my qwx machine can't be rebooted at the moment. Will let you know if i see fallout later. > M sys/dev/ic/qwx.c | 176+ 17- > M sys/dev/ic/qwxvar.h | 3+ 0- > M sys/dev/pci/if_qwx_pci.c | 3+ 3- > > 3 files changed, 182 insertions(+), 20 deletions(-) > > commit - be9cd6068db445307653bc9502bba152b433ba16 > commit + 915aa15cc7c682e6d6225fdd64fef45064086ee4 > blob - 469af2edef2bbe969142e9423e98007b0ac623fe > blob + 248c6c569b7eb7db7dc78c743adf60fed6c3eb8e > --- sys/dev/ic/qwx.c > +++ sys/dev/ic/qwx.c > @@ -12837,6 +12837,8 @@ qwx_init_channels(struct qwx_softc *sc, struct cur_reg > IEEE80211_CHAN_DYN | > IEEE80211_CHAN_2GHZ | > IEEE80211_CHAN_HT; > + if (!(rule->flags & REGULATORY_CHAN_NO_HT40)) > + chan->ic_flags |= IEEE80211_CHAN_40MHZ; > } > chnum++; > freq = ieee80211_ieee2mhz(chnum, IEEE80211_CHAN_2GHZ); > @@ -12872,6 +12874,8 @@ qwx_init_channels(struct qwx_softc *sc, struct cur_reg > chan->ic_freq = freq; > chan->ic_flags = IEEE80211_CHAN_A | > IEEE80211_CHAN_HT; > + if (!(rule->flags & REGULATORY_CHAN_NO_HT40)) > + chan->ic_flags |= IEEE80211_CHAN_40MHZ; > if (rule->flags & (REGULATORY_CHAN_RADAR | > REGULATORY_CHAN_NO_IR | > REGULATORY_CHAN_INDOOR_ONLY)) { > @@ -23696,6 +23700,7 @@ qwx_mac_vdev_start_restart(struct qwx_softc *sc, struc > struct ieee80211com *ic = &sc->sc_ic; > struct ieee80211_channel *chan = ic->ic_bss->ni_chan; > struct wmi_vdev_start_req_arg arg = {}; > + uint8_t sco = IEEE80211_HTOP0_SCO_SCN; > int ret = 0; > #ifdef notyet > lockdep_assert_held(&ar->conf_mutex); > @@ -23712,7 +23717,25 @@ qwx_mac_vdev_start_restart(struct qwx_softc *sc, struc > arg.channel.band_center_freq2 = chan->ic_freq; > > /* Deduce a legacy mode based on the channel characteristics. */ > - if (IEEE80211_IS_CHAN_5GHZ(chan)) > + if (ieee80211_node_supports_ht(ic->ic_bss) && > + (chan->ic_flags & IEEE80211_CHAN_HT)) { > + arg.channel.allow_ht = 1; > + if (IEEE80211_CHAN_40MHZ_ALLOWED(chan) && > + ieee80211_node_supports_ht_chan40(ic->ic_bss)) > + sco = ic->ic_bss->ni_htop0 & > + IEEE80211_HTOP0_SCO_MASK; > + if (sco == IEEE80211_HTOP0_SCO_SCA) { > + arg.channel.band_center_freq1 = chan->ic_freq + 10; > + arg.channel.mode = IEEE80211_IS_CHAN_5GHZ(chan) ? > + MODE_11NA_HT40 : MODE_11NG_HT40; > + } else if (sco == IEEE80211_HTOP0_SCO_SCB) { > + arg.channel.band_center_freq1 = chan->ic_freq - 10; > + arg.channel.mode = IEEE80211_IS_CHAN_5GHZ(chan) ? > + MODE_11NA_HT40 : MODE_11NG_HT40; > + } else > + arg.channel.mode = IEEE80211_IS_CHAN_5GHZ(chan) ? > + MODE_11NA_HT20 : MODE_11NG_HT20; > + } else if (IEEE80211_IS_CHAN_5GHZ(chan)) > arg.channel.mode = MODE_11A; > else if (ic->ic_bss->ni_flags & IEEE80211_NODE_ERP) > arg.channel.mode = MODE_11G; > @@ -26419,6 +26442,7 @@ qwx_peer_assoc_h_phymode(struct qwx_softc *sc, struct > { > struct ieee80211com *ic = &sc->sc_ic; > enum wmi_phy_mode phymode; > + uint8_t sco; > > switch (ic->ic_curmode) { > case IEEE80211_MODE_11A: > @@ -26431,10 +26455,17 @@ qwx_peer_assoc_h_phymode(struct qwx_softc *sc, struct > phymode = MODE_11G; > break; > case IEEE80211_MODE_11N: > - if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) > - phymode = MODE_11NA_HT20; > + sco = IEEE80211_HTOP0_SCO_SCN; > + if (IEEE80211_CHAN_40MHZ_ALLOWED(ni->ni_chan) && > + ieee80211_node_supports_ht_chan40(ni)) > + sco = ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK; > + if (sco == IEEE80211_HTOP0_SCO_SCA || > + sco == IEEE80211_HTOP0_SCO_SCB) > + phymode = IEEE80211_IS_CHAN_5GHZ(ni->ni_chan) ? > + MODE_11NA_HT40 : MODE_11NG_HT40; > else > - phymode = MODE_11NG_HT20; > + phymode = IEEE80211_IS_CHAN_5GHZ(ni->ni_chan) ? > + MODE_11NA_HT20 : MODE_11NG_HT20; > break; > default: > phymode = MODE_UNKNOWN; > @@ -26490,7 +26521,7 @@ qwx_peer_assoc_h_ht(struct qwx_softc *sc, struct qwx_v > { > struct ieee80211com *ic = &sc->sc_ic; > int i, n; > - uint8_t max_nss; > + uint8_t max_nss, sco; > uint32_t stbc, aggsize, mpdu_density; > #ifdef notyet > lockdep_assert_held(&ar->conf_mutex); > @@ -26498,6 +26529,11 @@ qwx_peer_assoc_h_ht(struct qwx_softc *sc, struct qwx_v > if ((ni->ni_flags & IEEE80211_NODE_HT) == 0) > return; > > + sco = IEEE80211_HTOP0_SCO_SCN; > + if (IEEE80211_CHAN_40MHZ_ALLOWED(ni->ni_chan) && > + ieee80211_node_supports_ht_chan40(ni)) > + sco = ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK; > + > arg->ht_flag = true; > > aggsize = (ni->ni_ampdu_param & IEEE80211_AMPDU_PARAM_LE); > @@ -26511,12 +26547,13 @@ qwx_peer_assoc_h_ht(struct qwx_softc *sc, struct qwx_v > > if (ni->ni_htcaps & IEEE80211_HTCAP_LDPC) > arg->ldpc_flag = true; > -#if 0 > - if (sta->deflink.bandwidth >= IEEE80211_STA_RX_BW_40) { > + > + if (sco == IEEE80211_HTOP0_SCO_SCA || > + sco == IEEE80211_HTOP0_SCO_SCB) { > arg->bw_40 = true; > arg->peer_rate_caps |= WMI_HOST_RC_CW40_FLAG; > } > -#endif > + > if (ieee80211_node_supports_ht_sgi20(ni) || > ieee80211_node_supports_ht_sgi40(ni)) > arg->peer_rate_caps |= WMI_HOST_RC_SGI_FLAG; > @@ -26605,6 +26642,94 @@ qwx_peer_assoc_prepare(struct qwx_softc *sc, struct qw > } > > void > +qwx_updatechan(struct ieee80211com *ic) > +{ > + struct ifnet *ifp = &ic->ic_if; > + struct qwx_softc *sc = ifp->if_softc; > + struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); > + struct ieee80211_node *ni = ic->ic_bss; > + struct qwx_node *nq = (struct qwx_node *)ic->ic_bss; > + int pdev_id = 0; /* TODO: derive pdev ID somehow? */ > + struct peer_assoc_params peer_arg; > + uint32_t old_phymode; > + enum wmi_peer_chwidth chwidth; > + int ret; > + > + old_phymode = nq->phymode; > + > + qwx_peer_assoc_h_phymode(sc, ni, &peer_arg); > + qwx_peer_assoc_h_ht(sc, arvif, ni, &peer_arg); > + > + if (peer_arg.bw_40) > + chwidth = WMI_PEER_CHWIDTH_40MHZ; > + else > + chwidth = WMI_PEER_CHWIDTH_20MHZ; > + > + if (nq->chwidth == chwidth) > + return; > + > + if (nq->chwidth < chwidth) { > + /* > + * BW is upgraded. In this case we send WMI_PEER_PHYMODE > + * followed by WMI_PEER_CHWIDTH. > + */ > + ret = qwx_wmi_set_peer_param(sc, ni->ni_macaddr, > + arvif->vdev_id, pdev_id, WMI_PEER_PHYMODE, > + peer_arg.peer_phymode); > + if (ret) { > + printf("%s: failed to update phymode for peer %s " > + "vdev_id %d\n", > + sc->sc_dev.dv_xname, > + ether_sprintf(ni->ni_macaddr), > + arvif->vdev_id); > + return; > + } > + > + ret = qwx_wmi_set_peer_param(sc, ni->ni_macaddr, > + arvif->vdev_id, pdev_id, WMI_PEER_CHWIDTH, chwidth); > + if (ret) { > + printf("%s: failed to update channel width for peer %s " > + "vdev_id %d\n", > + sc->sc_dev.dv_xname, > + ether_sprintf(ni->ni_macaddr), > + arvif->vdev_id); > + return; > + } > + } else { > + /* > + * BW is downgraded. In this case we send WMI_PEER_CHWIDTH > + * followed by WMI_PEER_PHYMODE. > + */ > + ret = qwx_wmi_set_peer_param(sc, ni->ni_macaddr, > + arvif->vdev_id, pdev_id, WMI_PEER_CHWIDTH, chwidth); > + if (ret) { > + printf("%s: failed to update channel width for peer %s " > + "vdev_id %d\n", > + sc->sc_dev.dv_xname, > + ether_sprintf(ni->ni_macaddr), > + arvif->vdev_id); > + return; > + } > + > + ret = qwx_wmi_set_peer_param(sc, ni->ni_macaddr, > + arvif->vdev_id, pdev_id, WMI_PEER_PHYMODE, > + peer_arg.peer_phymode); > + if (ret) { > + printf("%s: failed to update phymode for peer %s " > + "vdev_id %d\n", > + sc->sc_dev.dv_xname, > + ether_sprintf(ni->ni_macaddr), > + arvif->vdev_id); > + return; > + } > + > + } > + > + nq->phymode = peer_arg.peer_phymode; > + nq->chwidth = chwidth; > +} > + > +void > qwx_rx_agg_start(struct qwx_softc *sc, struct ieee80211_node *ni, uint8_t tid, > uint16_t ssn, uint16_t winsize) > { > @@ -26784,15 +26909,6 @@ qwx_assoc(struct qwx_softc *sc) > } > } > > - if (ni->ni_flags & IEEE80211_NODE_HT) { > - ret = qwx_setup_peer_smps(sc, pdev_id, arvif, ni->ni_macaddr, > - ni->ni_htcaps); > - if (ret) { > - printf("%s: failed to setup SMPS for vdev %d: %d\n", > - sc->sc_dev.dv_xname, arvif->vdev_id, ret); > - return ret; > - } > - } > #if 0 > if (!ath11k_mac_vif_recalc_sta_he_txbf(ar, vif, &he_cap)) { > ath11k_warn(ar->ab, "failed to recalc he txbf for vdev %i on bss %pM\n", > @@ -26822,10 +26938,53 @@ qwx_run(struct qwx_softc *sc) > { > struct ieee80211com *ic = &sc->sc_ic; > struct ieee80211_node *ni = ic->ic_bss; > + struct qwx_node *nq = (struct qwx_node *)ni; > struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */ > uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */ > + struct peer_assoc_params peer_arg; > int ret; > > + /* Update peer again to apply HT and VHT settings. */ > + qwx_peer_assoc_prepare(sc, arvif, ni, &peer_arg, 1); > + > + peer_arg.is_assoc = 1; > + > + sc->peer_assoc_done = 0; > + ret = qwx_wmi_send_peer_assoc_cmd(sc, pdev_id, &peer_arg); > + if (ret) { > + printf("%s: failed to run peer assoc for %s vdev %i: %d\n", > + sc->sc_dev.dv_xname, ether_sprintf(ni->ni_macaddr), > + arvif->vdev_id, ret); > + return ret; > + } > + > + while (!sc->peer_assoc_done) { > + ret = tsleep_nsec(&sc->peer_assoc_done, 0, "qwxassoc", > + SEC_TO_NSEC(1)); > + if (ret) { > + printf("%s: failed to get peer assoc conf event " > + "for %s vdev %i\n", sc->sc_dev.dv_xname, > + ether_sprintf(ni->ni_macaddr), arvif->vdev_id); > + return ret; > + } > + } > + > + if (ni->ni_flags & IEEE80211_NODE_HT) { > + ret = qwx_setup_peer_smps(sc, pdev_id, arvif, ni->ni_macaddr, > + ni->ni_htcaps); > + if (ret) { > + printf("%s: failed to setup SMPS for vdev %d: %d\n", > + sc->sc_dev.dv_xname, arvif->vdev_id, ret); > + return ret; > + } > + } > + > + nq->phymode = peer_arg.peer_phymode; > + if (peer_arg.bw_40) > + nq->chwidth = WMI_PEER_CHWIDTH_40MHZ; > + else > + nq->chwidth = WMI_PEER_CHWIDTH_20MHZ; > + > ret = qwx_wmi_vdev_up(sc, arvif->vdev_id, pdev_id, arvif->aid, > arvif->bssid, NULL, 0, 0); > if (ret) { > blob - 3d4c9865bd00034c5491dcdf9c224a8fb252f9fd > blob + 4e82920be3e083e48eb40eb45c9b515543476608 > --- sys/dev/ic/qwxvar.h > +++ sys/dev/ic/qwxvar.h > @@ -2036,6 +2036,7 @@ void qwx_init_task(void *); > int qwx_newstate(struct ieee80211com *, enum ieee80211_state, int); > void qwx_newstate_task(void *); > int qwx_bgscan(struct ieee80211com *); > +void qwx_updatechan(struct ieee80211com *); > > struct qwx_node { > struct ieee80211_node ni; > @@ -2043,6 +2044,8 @@ struct qwx_node { > unsigned int flags; > #define QWX_NODE_FLAG_HAVE_PAIRWISE_KEY 0x01 > #define QWX_NODE_FLAG_HAVE_GROUP_KEY 0x02 > + uint32_t phymode; > + enum wmi_peer_chwidth chwidth; > }; > > struct ieee80211_node *qwx_node_alloc(struct ieee80211com *); > blob - c98c5c63b5bf02a4312ff08acdf23467e7b717a1 > blob + 674e87b64a305c082f146504eb9be99bb27637d4 > --- sys/dev/pci/if_qwx_pci.c > +++ sys/dev/pci/if_qwx_pci.c > @@ -1091,8 +1091,8 @@ unsupported_wcn6855_soc: > ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b; > ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g; > > - ic->ic_htcaps = IEEE80211_HTCAP_SGI20 | IEEE80211_HTCAP_AMSDU7935; > - ic->ic_htcaps |= > + ic->ic_htcaps = IEEE80211_HTCAP_SGI20 | IEEE80211_HTCAP_SGI40 | > + IEEE80211_HTCAP_CBW20_40 | IEEE80211_HTCAP_AMSDU7935 | > (IEEE80211_HTCAP_SMPS_DIS << IEEE80211_HTCAP_SMPS_SHIFT); > ic->ic_htxcaps = 0; > ic->ic_txbfcaps = 0; > @@ -1122,8 +1122,8 @@ unsupported_wcn6855_soc: > ic->ic_newstate = qwx_newstate; > ic->ic_set_key = qwx_set_key; > ic->ic_delete_key = qwx_delete_key; > -#if 0 > ic->ic_updatechan = qwx_updatechan; > +#if 0 > ic->ic_updateprot = qwx_updateprot; > ic->ic_updateslot = qwx_updateslot; > ic->ic_updateedca = qwx_updateedca; >