Index | Thread | Search

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

Download raw body.

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

  $ netstat -W qwx0 | grep block
	2 new input block ack agreements
	2 new output block ack agreements

Without an additional fix which I will mail out shortly, this will only work
with APs that use 'wpagroupcipher ccmp' (check ifconfig qwx0 output).

M  sys/dev/ic/qwx.c          |  438+  34-
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, 502 insertions(+), 36 deletions(-)

commit - da6f6cc687226b802da41be1bfbe018d5012e976
commit + 9346445d75793bfa730fc90d726f024ac0f7b74f
blob - 462b3aeb2427871eef114f05b612c82d3aa7b0c0
blob + 936d4522e0d77b745d22edb71c628d3a2f1ff7f9
--- 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);
@@ -24736,6 +24882,28 @@ 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 *);
+	qwh =(struct ieee80211_qosframe *)wh;
+	qwh->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 +25023,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 +25039,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 +25995,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 +26012,127 @@ 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_prepare(struct qwx_softc *sc, struct qwx_vif *arvif,
     struct ieee80211_node *ni, struct peer_assoc_params *arg, int reassoc)
 {
@@ -25852,8 +26143,8 @@ 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);
@@ -25866,7 +26157,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 +26414,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;
 
 	/*