Download raw body.
qwx: add 40MHz channels and fix 11n phy mode
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;
>
qwx: add 40MHz channels and fix 11n phy mode