Index | Thread | Search

From:
Mike Larkin <mlarkin@nested.page>
Subject:
Re: qwx: add 40MHz channels and fix 11n phy mode
To:
tech@openbsd.org
Date:
Thu, 28 May 2026 15:32:48 -0700

Download raw body.

Thread
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;
>