Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
qwx: add 40MHz channels and fix 11n phy mode
To:
tech@openbsd.org
Date:
Thu, 28 May 2026 21:36:21 +0200

Download raw body.

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