Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: add 802.11n / HT support to qwx(4)
To:
tech@openbsd.org
Date:
Sat, 2 Aug 2025 18:38:26 +0200

Download raw body.

Thread
On Sat, Aug 02, 2025 at 03:14:10PM +0200, Stefan Sperling wrote:
> This patch adds 802.11n support to qwx(4).
> 
> For now, the channel width remains limited to 20MHz.
> 
> In my testing, both Tx and Rx aggregation are working:

Actually, Tx agg was not working yet.
It does work with this version:

M  sys/dev/ic/qwx.c          |  457+  37-
M  sys/dev/ic/qwxreg.h       |   42+   0-
M  sys/dev/ic/qwxvar.h       |   15+   0-
M  sys/dev/pci/if_qwx_pci.c  |    7+   2-

4 files changed, 521 insertions(+), 39 deletions(-)

commit - da6f6cc687226b802da41be1bfbe018d5012e976
commit + c8e878e3e3ec3f2f3b37fcd5bef6a9c22cba8920
blob - 462b3aeb2427871eef114f05b612c82d3aa7b0c0
blob + cf6b48440f75e491478b6dba06d6fd476e226d2f
--- sys/dev/ic/qwx.c
+++ sys/dev/ic/qwx.c
@@ -373,6 +373,7 @@ qwx_stop(struct ifnet *ifp)
 	task_del(systq, &sc->init_task);
 	qwx_del_task(sc, sc->sc_nswq, &sc->newstate_task);
 	qwx_del_task(sc, systq, &sc->setkey_task);
+	qwx_del_task(sc, systq, &sc->ba_task);
 	qwx_del_task(sc, systq, &sc->bgscan_task);
 	refcnt_finalize(&sc->task_refs, "qwxstop");
 
@@ -976,9 +977,7 @@ qwx_newstate(struct ieee80211com *ic, enum ieee80211_s
 	    nstate != IEEE80211_S_AUTH)
 		return 0;
 	if (ic->ic_state == IEEE80211_S_RUN) {
-#if 0
 		qwx_del_task(sc, systq, &sc->ba_task);
-#endif
 		qwx_del_task(sc, systq, &sc->setkey_task);
 		qwx_setkey_clear(sc);
 
@@ -12705,7 +12704,8 @@ qwx_init_channels(struct qwx_softc *sc, struct cur_reg
 				chan->ic_flags = IEEE80211_CHAN_CCK |
 				    IEEE80211_CHAN_OFDM |
 				    IEEE80211_CHAN_DYN |
-				    IEEE80211_CHAN_2GHZ;
+				    IEEE80211_CHAN_2GHZ |
+				    IEEE80211_CHAN_HT;
 			}
 			chnum++;
 			freq = ieee80211_ieee2mhz(chnum, IEEE80211_CHAN_2GHZ);
@@ -12739,7 +12739,8 @@ qwx_init_channels(struct qwx_softc *sc, struct cur_reg
 				chan->ic_flags = 0;
 			} else {
 				chan->ic_freq = freq;
-				chan->ic_flags = IEEE80211_CHAN_A;
+				chan->ic_flags = IEEE80211_CHAN_A |
+				    IEEE80211_CHAN_HT;
 				if (rule->flags & (REGULATORY_CHAN_RADAR |
 				    REGULATORY_CHAN_NO_IR |
 				    REGULATORY_CHAN_INDOOR_ONLY)) {
@@ -15944,9 +15945,13 @@ qwx_dp_tx_complete_msdu(struct qwx_softc *sc, struct d
 
 	pkt_type = FIELD_GET(HAL_TX_RATE_STATS_INFO0_PKT_TYPE, ts->rate_stats);
 	mcs = FIELD_GET(HAL_TX_RATE_STATS_INFO0_MCS, ts->rate_stats);
-	if (qwx_mac_hw_ratecode_to_legacy_rate(tx_data->ni, mcs, pkt_type,
-	    &rateidx, &rate) == 0)
-		tx_data->ni->ni_txrate = rateidx;
+	if (pkt_type == HAL_TX_RATE_STATS_PKT_TYPE_11A ||
+	    pkt_type == HAL_TX_RATE_STATS_PKT_TYPE_11B) {
+		if (qwx_mac_hw_ratecode_to_legacy_rate(tx_data->ni, mcs, pkt_type,
+		    &rateidx, &rate) == 0)
+			tx_data->ni->ni_txrate = rateidx;
+	} else if (pkt_type == HAL_TX_RATE_STATS_PKT_TYPE_11N)
+		tx_data->ni->ni_txmcs = mcs;
 
 	ieee80211_release_node(ic, tx_data->ni);
 	tx_data->ni = NULL;
@@ -16729,17 +16734,132 @@ qwx_dp_rx_h_ppdu(struct qwx_softc *sc, struct hal_rx_d
 	qwx_dp_rx_h_rate(sc, rx_desc, rxi);
 }
 
-void
+int
 qwx_dp_rx_h_undecap_nwifi(struct qwx_softc *sc, struct qwx_rx_msdu *msdu,
     uint8_t *first_hdr, enum hal_encrypt_type enctype)
 {
-	/*
-	* This function will need to do some work once we are receiving
-	* aggregated frames. For now, it needs to do nothing.
-	*/
+	struct ieee80211_frame *wh;
+	struct ieee80211_qosframe *qwh;
+	uint8_t decap_hdr[DP_MAX_NWIFI_HDR_LEN];
+	uint8_t da[IEEE80211_ADDR_LEN];
+	uint8_t sa[IEEE80211_ADDR_LEN];
+	u_int hdr_len;
+	struct mbuf *m;
+	int off;
 
-	if (!msdu->is_first_msdu)
-		printf("%s: not implemented\n", __func__);
+	/* copy SA & DA and pull decapped header */
+	wh = mtod(msdu->m, struct ieee80211_frame *);
+	hdr_len = ieee80211_get_hdrlen(wh);
+	switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) {
+	case IEEE80211_FC1_DIR_NODS:
+		IEEE80211_ADDR_COPY(da, wh->i_addr1);
+		IEEE80211_ADDR_COPY(sa, wh->i_addr2);
+		break;
+	case IEEE80211_FC1_DIR_TODS:
+		IEEE80211_ADDR_COPY(da, wh->i_addr3);
+		IEEE80211_ADDR_COPY(sa, wh->i_addr2);
+		break;
+	case IEEE80211_FC1_DIR_FROMDS:
+		IEEE80211_ADDR_COPY(da, wh->i_addr1);
+		IEEE80211_ADDR_COPY(sa, wh->i_addr3);
+		break;
+	case IEEE80211_FC1_DIR_DSTODS:
+		IEEE80211_ADDR_COPY(da, wh->i_addr3);
+		IEEE80211_ADDR_COPY(sa,
+		    ((struct ieee80211_frame_addr4 *)wh)->i_addr4);
+		break;
+	}
+	m_adj(msdu->m, hdr_len);
+
+	if (msdu->is_first_msdu) {
+		/*
+		 * The original 802.11 header is valid for the first msdu
+		 * hence we can reuse the same header.
+		 */
+		wh = (struct ieee80211_frame *)first_hdr;
+		hdr_len = ieee80211_get_hdrlen(wh);
+
+		/*
+		 * Each A-MSDU subframe will be reported as a separate MSDU,
+		 * so strip the A-MSDU bit from QoS Ctl.
+		 */
+		if (ieee80211_has_qos(wh)) {
+			qwh = (struct ieee80211_qosframe *)wh;
+			qwh->i_qos[0] &= ~IEEE80211_QOS_AMSDU;
+		}
+#if 0
+		if (!(status->flag & RX_FLAG_IV_STRIPPED)) {
+			memcpy(skb_push(msdu,
+					ath11k_dp_rx_crypto_param_len(ar, enctype)),
+			       (void *)hdr + hdr_len,
+			       ath11k_dp_rx_crypto_param_len(ar, enctype));
+		}
+#endif
+		m = m_makespace(msdu->m, 0, hdr_len, &off);
+		if (m == NULL)
+			return ENOMEM;
+
+		memcpy(mtod(m, void *) + off, wh, hdr_len);
+
+		/*
+		 * Original 802.11 header has a different DA and in
+		 * case of 4addr it may also have different SA.
+		 */
+		wh = mtod(m, struct ieee80211_frame *);
+		switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) {
+		case IEEE80211_FC1_DIR_NODS:
+			IEEE80211_ADDR_COPY(wh->i_addr1, da);
+			IEEE80211_ADDR_COPY(wh->i_addr2, sa);
+			break;
+		case IEEE80211_FC1_DIR_TODS:
+			IEEE80211_ADDR_COPY(wh->i_addr3, da);
+			IEEE80211_ADDR_COPY( wh->i_addr2, sa);
+			break;
+		case IEEE80211_FC1_DIR_FROMDS:
+			IEEE80211_ADDR_COPY(wh->i_addr1, da);
+			IEEE80211_ADDR_COPY(wh->i_addr3, sa);
+			break;
+		case IEEE80211_FC1_DIR_DSTODS:
+			IEEE80211_ADDR_COPY(wh->i_addr3, da);
+			IEEE80211_ADDR_COPY(
+			    ((struct ieee80211_frame_addr4 *)wh)->i_addr4, sa);
+			break;
+		}
+	} else {
+		uint16_t qos_ctl = msdu->tid & IEEE80211_QOS_TID;
+
+		/*  Rebuild QoS header if this is a middle/last msdu */
+		wh->i_fc[0] |= htole16(IEEE80211_FC0_SUBTYPE_QOS);
+
+		/* Reset the order bit as the HT_Control header is stripped */
+		wh->i_fc[1] &= ~(htole16(IEEE80211_FC1_ORDER));
+#if 0
+		if (ath11k_dp_rx_h_msdu_start_mesh_ctl_present(ar->ab, rxcb->rx_desc))
+			qos_ctl |= IEEE80211_QOS_CTL_MESH_CONTROL_PRESENT;
+#endif
+		/* TODO Add other QoS ctl fields when required */
+
+		/* copy decap header before overwriting for reuse below */
+		memcpy(decap_hdr, (uint8_t *)wh, hdr_len);
+#if 0
+		if (!(status->flag & RX_FLAG_IV_STRIPPED)) {
+			memcpy(skb_push(msdu,
+					ath11k_dp_rx_crypto_param_len(ar, enctype)),
+			       (void *)hdr + hdr_len,
+			       ath11k_dp_rx_crypto_param_len(ar, enctype));
+		}
+#endif
+		m = m_makespace(msdu->m, 0, hdr_len + sizeof(qos_ctl), &off);
+		if (m == NULL)
+			return ENOMEM;
+
+		memcpy(mtod(m, void *) + off, decap_hdr, hdr_len);
+		qwh = mtod(m, struct ieee80211_qosframe *);
+		*(u_int16_t *)qwh->i_qos = htole16(qos_ctl);
+	}
+
+	msdu->m = m;
+	return 0;
 }
 
 void
@@ -16821,20 +16941,21 @@ qwx_dp_rx_h_msdu_start_decap_type(struct qwx_softc *sc
 	return sc->hw_params.hw_ops->rx_desc_get_decap_type(desc);
 }
 
-void
+int
 qwx_dp_rx_h_undecap(struct qwx_softc *sc, struct qwx_rx_msdu *msdu,
     struct hal_rx_desc *rx_desc, enum hal_encrypt_type enctype,
     int decrypted)
 {
 	uint8_t *first_hdr;
 	uint8_t decap;
+	int ret = 0;
 
 	first_hdr = qwx_dp_rx_h_80211_hdr(sc, rx_desc);
 	decap = qwx_dp_rx_h_msdu_start_decap_type(sc, rx_desc);
 
 	switch (decap) {
 	case DP_RX_DECAP_TYPE_NATIVE_WIFI:
-		qwx_dp_rx_h_undecap_nwifi(sc, msdu, first_hdr, enctype);
+		ret = qwx_dp_rx_h_undecap_nwifi(sc, msdu, first_hdr, enctype);
 		break;
 	case DP_RX_DECAP_TYPE_RAW:
 		qwx_dp_rx_h_undecap_raw(sc, msdu, enctype, decrypted);
@@ -16863,6 +16984,8 @@ qwx_dp_rx_h_undecap(struct qwx_softc *sc, struct qwx_r
 		break;
 #endif
 	}
+
+	return ret;
 }
 
 int
@@ -16872,7 +16995,7 @@ qwx_dp_rx_h_mpdu(struct qwx_softc *sc, struct qwx_rx_m
 	struct ieee80211com *ic = &sc->sc_ic;
 	int fill_crypto_hdr = 0;
 	enum hal_encrypt_type enctype;
-	int is_decrypted = 0;
+	int is_decrypted = 0, ret;
 #if 0
 	struct ath11k_skb_rxcb *rxcb;
 #endif
@@ -16949,7 +17072,9 @@ qwx_dp_rx_h_mpdu(struct qwx_softc *sc, struct qwx_rx_m
 #if 0
 	ath11k_dp_rx_h_csum_offload(ar, msdu);
 #endif
-	qwx_dp_rx_h_undecap(sc, msdu, rx_desc, enctype, is_decrypted);
+	ret = qwx_dp_rx_h_undecap(sc, msdu, rx_desc, enctype, is_decrypted);
+	if (ret)
+		return ret;
 
 	if (is_decrypted && !fill_crypto_hdr &&
 	    qwx_dp_rx_h_msdu_start_decap_type(sc, rx_desc) !=
@@ -22671,9 +22796,9 @@ qwx_reg_update_chan_list(struct qwx_softc *sc, uint8_t
 		if ((!scan_2ghz && IEEE80211_IS_CHAN_2GHZ(channel)) ||
 		    (!scan_5ghz && IEEE80211_IS_CHAN_5GHZ(channel)))
 			continue;
-#ifdef notyet
-		/* TODO: Set to true/false based on some condition? */
+
 		ch->allow_ht = true;
+#ifdef notyet
 		ch->allow_vht = true;
 		ch->allow_he = true;
 #endif
@@ -22690,13 +22815,24 @@ qwx_reg_update_chan_list(struct qwx_softc *sc, uint8_t
 		ch->maxregpower = ch->maxpower; 
 		ch->antennamax = 0;
 
-		/* TODO: Use appropriate phymodes */
-		if (IEEE80211_IS_CHAN_A(channel))
+		switch (IFM_MODE(ic->ic_media.ifm_cur->ifm_media)) {
+		case IFM_IEEE80211_11A:
 			ch->phy_mode = MODE_11A;
-		else if (IEEE80211_IS_CHAN_G(channel))
+			break;
+		case IFM_IEEE80211_11G:
 			ch->phy_mode = MODE_11G;
-		else
+			break;
+		case IFM_IEEE80211_11B:
 			ch->phy_mode = MODE_11B;
+			break;
+		case IFM_IEEE80211_11N:
+		default:
+			if (IEEE80211_IS_CHAN_A(channel))
+				ch->phy_mode = MODE_11NA_HT20;
+			else
+				ch->phy_mode = MODE_11NG_HT20;
+			break;
+		}
 #ifdef notyet
 		if (channel->band == NL80211_BAND_6GHZ &&
 		    cfg80211_channel_is_psc(channel))
@@ -22895,6 +23031,16 @@ qwx_mac_op_start(struct qwx_pdev *pdev)
 
 	qwx_set_antenna(pdev, pdev->cap.tx_chain_mask, pdev->cap.rx_chain_mask);
 
+	memset(ic->ic_sup_mcs, 0, sizeof(ic->ic_sup_mcs));
+	ic->ic_sup_mcs[0] = 0xff;		/* MCS 0-7 */
+	if (sc->num_rx_chains > 1)
+		ic->ic_sup_mcs[1] = 0xff;		/* MCS 8-15 */
+	if (sc->num_rx_chains > 2)
+		ic->ic_sup_mcs[2] = 0xff;		/* MCS 16-23 */
+	if (sc->num_rx_chains > 3)
+		ic->ic_sup_mcs[3] = 0xff;		/* MCS 24-31 */
+	
+
 	/* TODO: Do we need to enable ANI? */
 
 	ret = qwx_reg_update_chan_list(sc, pdev->pdev_id);
@@ -24689,10 +24835,15 @@ uint8_t
 qwx_dp_tx_get_tid(struct mbuf *m)
 {
 	struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
-	uint16_t qos = ieee80211_get_qos(wh);
-	uint8_t tid = qos & IEEE80211_QOS_TID;
 
-	return tid;
+	if (ieee80211_has_qos(wh)) {
+		uint16_t qos = ieee80211_get_qos(wh);
+		uint8_t tid = qos & IEEE80211_QOS_TID;
+
+		return tid;
+	}
+
+	return HAL_DESC_REO_NON_QOS_TID;
 }
 
 void
@@ -24736,6 +24887,27 @@ qwx_hal_tx_cmd_desc_setup(struct qwx_softc *sc, void *
 #endif
 }
 
+void
+qwx_dp_tx_encap_nwifi(struct mbuf *m)
+{
+	struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *);
+	struct ieee80211_qosframe *qwh;
+	uint8_t *qos_ctl;
+
+	if (!ieee80211_has_qos(wh))
+		return;
+
+	/* Trim QoS info. */
+	qwh = (struct ieee80211_qosframe *)wh;
+	qos_ctl = &qwh->i_qos[0];
+	memmove(mtod(m, void *) + 2, mtod(m, void *),
+	    (void *)qos_ctl - mtod(m, void *));
+	m_adj(m, 2);
+
+	wh = mtod(m, struct ieee80211_frame *);
+	wh->i_fc[0] &= ~IEEE80211_FC0_SUBTYPE_QOS;
+}
+
 int
 qwx_dp_tx(struct qwx_softc *sc, struct qwx_vif *arvif, uint8_t pdev_id,
     struct ieee80211_node *ni, struct mbuf *m)
@@ -24855,15 +25027,14 @@ qwx_dp_tx(struct qwx_softc *sc, struct qwx_vif *arvif,
 	ti.flags1 |= FIELD_PREP(HAL_TCL_DATA_CMD_INFO2_TID_OVERWRITE, 1);
 
 	ti.tid = qwx_dp_tx_get_tid(m);
-#if 0
 	switch (ti.encap_type) {
 	case HAL_TCL_ENCAP_TYPE_NATIVE_WIFI:
-		ath11k_dp_tx_encap_nwifi(skb);
+		qwx_dp_tx_encap_nwifi(m);
 		break;
 	case HAL_TCL_ENCAP_TYPE_RAW:
-		if (!test_bit(ATH11K_FLAG_RAW_MODE, &ab->dev_flags)) {
-			ret = -EINVAL;
-			goto fail_remove_idr;
+		if (!test_bit(ATH11K_FLAG_RAW_MODE, sc->sc_flags)) {
+			m_freem(m);
+			return EINVAL;
 		}
 		break;
 	case HAL_TCL_ENCAP_TYPE_ETHERNET:
@@ -24872,11 +25043,9 @@ qwx_dp_tx(struct qwx_softc *sc, struct qwx_vif *arvif,
 	case HAL_TCL_ENCAP_TYPE_802_3:
 	default:
 		/* TODO: Take care of other encap modes as well */
-		ret = -EINVAL;
-		atomic_inc(&ab->soc_stats.tx_err.misc_fail);
-		goto fail_remove_idr;
+		m_freem(m);
+		return EINVAL;
 	}
-#endif
 	ret = bus_dmamap_load_mbuf(sc->sc_dmat, tx_data->map,
 	    m, BUS_DMA_WRITE | BUS_DMA_NOWAIT);
 	if (ret && ret != EFBIG) {
@@ -25830,6 +25999,12 @@ qwx_peer_assoc_h_phymode(struct qwx_softc *sc, struct 
 	case IEEE80211_MODE_11G:
 		phymode = MODE_11G;
 		break;
+	case IEEE80211_MODE_11N:
+		if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan))
+			phymode = MODE_11NA_HT20;
+		else
+			phymode = MODE_11NG_HT20;
+		break;
 	default:
 		phymode = MODE_UNKNOWN;
 		break;
@@ -25841,7 +26016,137 @@ qwx_peer_assoc_h_phymode(struct qwx_softc *sc, struct 
 	arg->peer_phymode = phymode;
 }
 
+/*
+ * 802.11n D2.0 defined values for "Minimum MPDU Start Spacing":
+ *   0 for no restriction
+ *   1 for 1/4 us
+ *   2 for 1/2 us
+ *   3 for 1 us
+ *   4 for 2 us
+ *   5 for 4 us
+ *   6 for 8 us
+ *   7 for 16 us
+ */
+uint8_t
+qwx_parse_mpdudensity(uint8_t mpdudensity)
+{
+	switch (mpdudensity) {
+	case 0:
+		return 0;
+	case 1:
+	case 2:
+	case 3:
+	/* Our lower layer calculations limit our precision to
+	 * 1 microsecond
+	 */
+		return 1;
+	case 4:
+		return 2;
+	case 5:
+		return 4;
+	case 6:
+		return 8;
+	case 7:
+		return 16;
+	default:
+		return 0;
+	}
+}
+
 void
+qwx_peer_assoc_h_ht(struct qwx_softc *sc, struct qwx_vif *arvif,
+    struct ieee80211_node *ni, struct peer_assoc_params *arg)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	int i, n;
+	uint8_t max_nss;
+	uint32_t stbc, aggsize, mpdu_density;
+#ifdef notyet
+	lockdep_assert_held(&ar->conf_mutex);
+#endif
+	if ((ni->ni_flags & IEEE80211_NODE_HT) == 0)
+		return;
+
+	arg->ht_flag = true;
+
+	aggsize = (ni->ni_ampdu_param & IEEE80211_AMPDU_PARAM_LE);
+	arg->peer_max_mpdu = (1 << (13 + aggsize)) - 1;
+
+	mpdu_density = (ni->ni_ampdu_param & IEEE80211_AMPDU_PARAM_SS) >> 2;
+	arg->peer_mpdu_density = qwx_parse_mpdudensity(mpdu_density);
+
+	arg->peer_ht_caps = ni->ni_htcaps;
+	arg->peer_rate_caps |= WMI_HOST_RC_HT_FLAG;
+
+	if (ni->ni_htcaps & IEEE80211_HTCAP_LDPC)
+		arg->ldpc_flag = true;
+#if 0
+	if (sta->deflink.bandwidth >= IEEE80211_STA_RX_BW_40) {
+		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;
+
+	if (ni->ni_htcaps & IEEE80211_HTCAP_TXSTBC) {
+		arg->peer_rate_caps |= WMI_HOST_RC_TX_STBC_FLAG;
+		arg->stbc_flag = true;
+	}
+
+	if (ni->ni_htcaps & IEEE80211_HTCAP_TXSTBC) {
+		stbc = ni->ni_htcaps & IEEE80211_HTCAP_RXSTBC_MASK;
+		stbc = stbc >> IEEE80211_HTCAP_RXSTBC_SHIFT;
+		stbc = stbc << WMI_HOST_RC_RX_STBC_FLAG_S;
+		arg->peer_rate_caps |= stbc;
+		arg->stbc_flag = true;
+	}
+
+	if (ni->ni_rxmcs[1] && ni->ni_rxmcs[2])
+		arg->peer_rate_caps |= WMI_HOST_RC_TS_FLAG;
+	else if (ni->ni_rxmcs[1])
+		arg->peer_rate_caps |= WMI_HOST_RC_DS_FLAG;
+
+	for (i = 0, n = 0, max_nss = 0; i < nitems(ni->ni_rxmcs) * 8; i++)
+		if ((ic->ic_sup_mcs[i / 8] & BIT(i % 8)) &&
+		    (ni->ni_rxmcs[i / 8] & BIT(i % 8))) {
+			max_nss = (i / 8) + 1;
+			arg->peer_ht_rates.rates[n++] = i;
+		}
+
+	/* This is a workaround for HT-enabled STAs which break the spec
+	 * and have no HT capabilities RX mask (no HT RX MCS map).
+	 *
+	 * As per spec, in section 20.3.5 Modulation and coding scheme (MCS),
+	 * MCS 0 through 7 are mandatory in 20MHz with 800 ns GI at all STAs.
+	 *
+	 * Firmware asserts if such situation occurs.
+	 */
+	if (n == 0) {
+		arg->peer_ht_rates.num_rates = 8;
+		for (i = 0; i < arg->peer_ht_rates.num_rates; i++)
+			arg->peer_ht_rates.rates[i] = i;
+	} else {
+		arg->peer_ht_rates.num_rates = n;
+		arg->peer_nss = max_nss;
+	}
+
+	DNPRINTF(QWX_D_MAC, "%s: ht peer %pM mcs cnt %d nss %d\n", __func__,
+	    arg->peer_mac, arg->peer_ht_rates.num_rates, arg->peer_nss);
+}
+
+void
+qwx_peer_assoc_h_qos(struct qwx_softc *sc, struct qwx_vif *vif,
+    struct ieee80211_node *ni, struct peer_assoc_params *arg)
+{
+	if (ni->ni_flags & IEEE80211_NODE_QOS) {
+		arg->is_wme_set = 1;
+		arg->qos_flag = 1;
+	}
+}
+
+void
 qwx_peer_assoc_prepare(struct qwx_softc *sc, struct qwx_vif *arvif,
     struct ieee80211_node *ni, struct peer_assoc_params *arg, int reassoc)
 {
@@ -25852,12 +26157,14 @@ qwx_peer_assoc_prepare(struct qwx_softc *sc, struct qw
 	qwx_peer_assoc_h_crypto(sc, arvif, ni, arg);
 	qwx_peer_assoc_h_rates(ni, arg);
 	qwx_peer_assoc_h_phymode(sc, ni, arg);
-#if 0
 	qwx_peer_assoc_h_ht(sc, arvif, ni, arg);
+#if 0
 	qwx_peer_assoc_h_vht(sc, arvif, ni, arg);
 	qwx_peer_assoc_h_he(sc, arvif, ni, arg);
 	qwx_peer_assoc_h_he_6ghz(sc, arvif, ni, arg);
+#endif
 	qwx_peer_assoc_h_qos(sc, arvif, ni, arg);
+#if 0
 	qwx_peer_assoc_h_smps(ni, arg);
 #endif
 #if 0
@@ -25866,7 +26173,119 @@ qwx_peer_assoc_prepare(struct qwx_softc *sc, struct qw
 	/* TODO: amsdu_disable req? */
 }
 
+void
+qwx_rx_agg_start(struct qwx_softc *sc, struct ieee80211_node *ni, uint8_t tid,
+    uint16_t ssn, uint16_t winsize)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
+	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
+	enum hal_pn_type pn_type;
+
+	if (!test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) &&
+	    (ic->ic_flags & IEEE80211_F_RSNON))
+		pn_type = HAL_PN_TYPE_WPA;
+	else
+		pn_type = HAL_PN_TYPE_NONE;
+
+	if (qwx_peer_rx_tid_setup(sc, ni, arvif->vdev_id, pdev_id, tid,
+	    winsize, ssn, pn_type))
+		ieee80211_addba_req_refuse(ic, ni, tid);
+	else 
+		ieee80211_addba_req_accept(ic, ni, tid);
+}
+
+void
+qwx_rx_agg_stop(struct qwx_softc *sc, struct ieee80211_node *ni, uint8_t tid,
+    uint16_t ssn, uint16_t winsize, int timeout_val, int start)
+{
+	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
+	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
+	struct qwx_node *nq = (struct qwx_node *)ni;
+	struct ath11k_peer *peer;
+	uint64_t paddr;
+	int ret;
+
+	peer = qwx_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return;
+
+	if (!peer->rx_tid[tid].active)
+		return;
+
+	ret = qwx_peer_rx_tid_reo_update(sc, peer,
+	    peer->rx_tid, 1, 0, false);
+	if (ret) {
+		printf("%s: failed to update reo for rx tid %d: %d\n",
+		    sc->sc_dev.dv_xname, tid, ret);
+	}
+
+	paddr = peer->rx_tid[tid].paddr;
+	ret = qwx_wmi_peer_rx_reorder_queue_setup(sc, arvif->vdev_id, pdev_id,
+	    ni->ni_macaddr, paddr, tid, 1, 1);
+	if (ret) {
+		printf("%s: failed to send wmi to delete rx tid %d\n",
+		    sc->sc_dev.dv_xname, ret);
+	}
+}
+
+void
+qwx_ba_task(void *arg)
+{
+	struct qwx_softc *sc = arg;
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ieee80211_node *ni = ic->ic_bss;
+	int s = splnet();
+	int tid;
+
+	for (tid = 0; tid < IEEE80211_NUM_TID; tid++) {
+		if (test_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags))
+			break;
+		if (sc->ba_rx.start_tidmask & (1 << tid)) {
+			struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid];
+			qwx_rx_agg_start(sc, ni, tid, ba->ba_winstart,
+			    ba->ba_winsize);
+			sc->ba_rx.start_tidmask &= ~(1 << tid);
+		} else if (sc->ba_rx.stop_tidmask & (1 << tid)) {
+			qwx_rx_agg_stop(sc, ni, tid, 0, 0, 0, 0);
+			sc->ba_rx.stop_tidmask &= ~(1 << tid);
+		}
+	}
+
+	refcnt_rele_wake(&sc->task_refs);
+	splx(s);
+}
+
 int
+qwx_ampdu_rx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
+    uint8_t tid)
+{
+	struct qwx_softc *sc = ic->ic_softc;
+
+	sc->ba_rx.start_tidmask |= (1 << tid);
+	qwx_add_task(sc, systq, &sc->ba_task);
+	return EBUSY;
+}
+
+void
+qwx_ampdu_rx_stop(struct ieee80211com *ic, struct ieee80211_node *ni,
+    uint8_t tid)
+{
+	struct qwx_softc *sc = ic->ic_softc;
+
+	sc->ba_rx.stop_tidmask |= (1 << tid);
+	qwx_add_task(sc, systq, &sc->ba_task);
+}
+
+int
+qwx_ampdu_tx_start(struct ieee80211com *ic, struct ieee80211_node *ni,
+    uint8_t tid)
+{
+	/* Firmware handles Tx aggregation internally. */
+	return 0;
+}
+
+int
 qwx_run(struct qwx_softc *sc)
 {
 	struct ieee80211com *ic = &sc->sc_ic;
@@ -26011,6 +26430,7 @@ qwx_attach(struct qwx_softc *sc)
 	task_set(&sc->init_task, qwx_init_task, sc);
 	task_set(&sc->newstate_task, qwx_newstate_task, sc);
 	task_set(&sc->setkey_task, qwx_setkey_task, sc);
+	task_set(&sc->ba_task, qwx_ba_task, sc);
 	task_set(&sc->bgscan_task, qwx_bgscan_task, sc);
 	timeout_set_proc(&sc->scan.timeout, qwx_scan_timeout, sc);
 #if NBPFILTER > 0
blob - f6537fe003dfd380a70ac0298558ec267d0f5966
blob + 670f7f6d9acb993ade256155b02e4360dd982487
--- sys/dev/ic/qwxreg.h
+++ sys/dev/ic/qwxreg.h
@@ -13253,3 +13253,45 @@ struct ath11k_htt_extd_stats_msg {
 #define	HTT_MAC_ADDR_L32_3	GENMASK(31, 24)
 #define	HTT_MAC_ADDR_H16_0	GENMASK(7, 0)
 #define	HTT_MAC_ADDR_H16_1	GENMASK(15, 8)
+
+#define WMI_HOST_RC_DS_FLAG			0x01
+#define WMI_HOST_RC_CW40_FLAG			0x02
+#define WMI_HOST_RC_SGI_FLAG			0x04
+#define WMI_HOST_RC_HT_FLAG			0x08
+#define WMI_HOST_RC_RTSCTS_FLAG			0x10
+#define WMI_HOST_RC_TX_STBC_FLAG		0x20
+#define WMI_HOST_RC_RX_STBC_FLAG		0xC0
+#define WMI_HOST_RC_RX_STBC_FLAG_S		6
+#define WMI_HOST_RC_WEP_TKIP_FLAG		0x100
+#define WMI_HOST_RC_TS_FLAG			0x200
+#define WMI_HOST_RC_UAPSD_FLAG			0x400
+
+#define WMI_HT_CAP_ENABLED			0x0001
+#define WMI_HT_CAP_HT20_SGI			0x0002
+#define WMI_HT_CAP_DYNAMIC_SMPS			0x0004
+#define WMI_HT_CAP_TX_STBC			0x0008
+#define WMI_HT_CAP_TX_STBC_MASK_SHIFT		3
+#define WMI_HT_CAP_RX_STBC			0x0030
+#define WMI_HT_CAP_RX_STBC_MASK_SHIFT		4
+#define WMI_HT_CAP_LDPC				0x0040
+#define WMI_HT_CAP_L_SIG_TXOP_PROT		0x0080
+#define WMI_HT_CAP_MPDU_DENSITY			0x0700
+#define WMI_HT_CAP_MPDU_DENSITY_MASK_SHIFT	8
+#define WMI_HT_CAP_HT40_SGI			0x0800
+#define WMI_HT_CAP_RX_LDPC			0x1000
+#define WMI_HT_CAP_TX_LDPC			0x2000
+#define WMI_HT_CAP_IBF_BFER			0x4000
+
+/* These macros should be used when we wish to advertise STBC support for
+ * only 1SS or 2SS or 3SS.
+ */
+#define WMI_HT_CAP_RX_STBC_1SS			0x0010
+#define WMI_HT_CAP_RX_STBC_2SS			0x0020
+#define WMI_HT_CAP_RX_STBC_3SS			0x0030
+
+#define WMI_HT_CAP_DEFAULT_ALL (WMI_HT_CAP_ENABLED    | \
+				WMI_HT_CAP_HT20_SGI   | \
+				WMI_HT_CAP_HT40_SGI   | \
+				WMI_HT_CAP_TX_STBC    | \
+				WMI_HT_CAP_RX_STBC    | \
+				WMI_HT_CAP_LDPC)
blob - d55417ec859a20edd2732ec41c617a572ee8b0a1
blob + 5899768a5e6989bf2b68cd39a07030104d89f4fc
--- sys/dev/ic/qwxvar.h
+++ sys/dev/ic/qwxvar.h
@@ -1791,6 +1791,11 @@ struct ath11k_peer {
 };
 TAILQ_HEAD(qwx_peer_list, ath11k_peer);
 
+struct qwx_ba_task_data {
+	uint32_t		start_tidmask;
+	uint32_t		stop_tidmask;
+};
+
 struct qwx_softc {
 	struct device			sc_dev;
 	struct ieee80211com		sc_ic;
@@ -1825,6 +1830,10 @@ struct qwx_softc {
 	int install_key_done;
 	int install_key_status;
 
+	/* Task for firmware BlockAck setup/teardown and its arguments. */
+	struct task		ba_task;
+	struct qwx_ba_task_data	ba_rx;
+
 	enum ath11k_11d_state	state_11d;
 	int			completed_11d_scan;
 	uint32_t		vdev_id_11d_scan;
@@ -2002,6 +2011,12 @@ int	qwx_set_key(struct ieee80211com *, struct ieee8021
     struct ieee80211_key *);
 void	qwx_delete_key(struct ieee80211com *, struct ieee80211_node *,
     struct ieee80211_key *);
+int	qwx_ampdu_rx_start(struct ieee80211com *, struct ieee80211_node *,
+	    uint8_t);
+void	qwx_ampdu_rx_stop(struct ieee80211com *, struct ieee80211_node *,
+	    uint8_t);
+int	qwx_ampdu_tx_start(struct ieee80211com *, struct ieee80211_node *,
+	    uint8_t);
 
 void	qwx_qrtr_recv_msg(struct qwx_softc *, struct mbuf *);
 
blob - 3f41e96ffcb1b7f74d74b55f18e06ecfd251d255
blob + f7994af8385c151efff75666859b0ca58ed84245
--- sys/dev/pci/if_qwx_pci.c
+++ sys/dev/pci/if_qwx_pci.c
@@ -1074,9 +1074,7 @@ unsupported_wcn6855_soc:
 
 	/* Set device capabilities. */
 	ic->ic_caps =
-#if 0
 	    IEEE80211_C_QOS | IEEE80211_C_TX_AMPDU | /* A-MPDU */
-#endif
 	    IEEE80211_C_ADDBA_OFFLOAD | /* device sends ADDBA/DELBA frames */
 	    IEEE80211_C_WEP |		/* WEP */
 	    IEEE80211_C_RSN |		/* WPA/RSN */
@@ -1092,6 +1090,9 @@ 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;
 
+	memset(ic->ic_sup_mcs, 0, sizeof(ic->ic_sup_mcs));
+	ic->ic_sup_mcs[0] = 0xff;		/* MCS 0-7 */
+
 	/* IBSS channel undefined for now. */
 	ic->ic_ibss_chan = &ic->ic_channels[1];
 
@@ -1119,6 +1120,10 @@ unsupported_wcn6855_soc:
 	ic->ic_updateedca = qwx_updateedca;
 	ic->ic_updatedtim = qwx_updatedtim;
 #endif
+	ic->ic_ampdu_rx_start = qwx_ampdu_rx_start;
+	ic->ic_ampdu_rx_stop = qwx_ampdu_rx_stop;
+	ic->ic_ampdu_tx_start = qwx_ampdu_tx_start;
+	ic->ic_ampdu_tx_stop = NULL;
 	ic->ic_bgscan_start = qwx_bgscan;
 
 	/*