From: Stefan Sperling Subject: qwx: add 40MHz channels and fix 11n phy mode To: tech@openbsd.org Date: Thu, 28 May 2026 21:36:21 +0200 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? 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;