Index | Thread | Search

From:
Mark Kettenis <mark.kettenis@xs4all.nl>
Subject:
Re: qwz: enable WPA2 association on WCN7850
To:
Marcus Glocker <marcus@nazgul.ch>
Cc:
tech@openbsd.org, kirill@korins.ky, stsp@stsp.name, mail@patrick-wildt.de
Date:
Sun, 26 Apr 2026 13:45:10 +0200

Download raw body.

Thread
  • Mark Kettenis:

    qwz: enable WPA2 association on WCN7850

  • > Date: Sat, 25 Apr 2026 23:56:07 +0200
    > From: Marcus Glocker <marcus@nazgul.ch>
    > 
    > Bring the qwz driver up to a working WPA2 client connection on the
    > Qualcomm WCN7850 chip.  Tested on the Samsung Galaxy Book4 Edge.
    > 
    > Major changes:
    > 
    > 1. Fix the RX path.
    >    Wire up the WCN7850 descriptor accesses that were unset; override
    >    the descriptor size to match what the FW actually writes (512 bytes
    >    instead of struct sizeof 472); add the first-line filters that drop
    >    FW-injected garbage frames before net80211 mistakes them for fake
    >    auth/deauth.
    > 
    > 2. Fix the TX path.
    >    Port Linux's WiFi7 "TX bank" infrastructure: a per-VDEV register
    >    that holds encap/encrypt/search settings the descriptor used to
    >    carry inline.  Rewrite the TX descriptor builder for the WiFi7 wire
    >    format.  Fix an encrypt_type default that was making the FW try to
    >    WEP-encrypt plain-text EAPOL frames.
    > 
    > 3. Fix MSI interrupt routing.
    >    Correct the DP IRQ group's MSI vector calculation, and free the
    >    vector DP group 0 needs (was being held by an unused pktlog
    >    interrupt).  Without these, RX completions never fired regardless
    >    of how correct the rest of the path was.
    
    That code still looks a bit dodgy to me.  The equivalent code in
    qwx(4) looks a bit dodgy too though.  I'll have a look over there
    first to see if I can make it a bit less dodgy.  No reason not to move
    forward with qwz(4).
    
    > 4. Make the WPA2 4-way handshake complete.
    >    Move WMI_PEER_AUTHORIZE to fire after key install, not before; the
    >    old order told the FW crypto was up while plain-text EAPOL was still
    >    in flight, crashing the FW.  Mask the AID to its 14-bit value before
    >    handing it to the FW.  Add the missing REO queue setup for non-QoS
    >    frames, which is where EAPOL lives.
    > 
    > 5. Add non-coherent DMA cache sync on RX and TX.
    >    Without explicit flushes the CPU and FW see different bytes for
    >    the same buffer.  This was the root cause of "garbage RX frames":
    >    they were always real EAPOL Msg 1 frames torn by stale CPU cache
    >    lines.
    
    DMA on the galaxybook should be coherent.  But bus_dmamap_sync() still
    issues a barrier instruction which might be needed to make sure reads
    by the CPU aren't issued in the wrong order.  So those extra
    bus_dmamap_sync() calls are needed.
    
    > 
    > 6. Update register/descriptor defines from ath11k to ath12k WiFi7.
    >    The TX descriptor wire format changed completely between
    >    generations: bit positions, field set, even the number of 32-bit
    >    words.  Partial updates wouldn't have worked.
    > 
    > 7. Cleanup.
    >    Remove some debug printfs and the diagnostic counters added during
    >    the bring-up to verify the path was working.
    > 
    > Known limitations:
    > 
    >   - Firmware occasionally crashes after sustained traffic; driver
    >     recovers via the existing RDDM path in if_qwz_pci.c without a
    >     system reboot.  Root-causing this is the next follow-up.
    >   - One PN-replay loop in qwz_dp_peer_rx_pn_replay_config doesn't
    >     iterate the non-QoS TID slot.  Cosmetic for normal use; will
    >     land as a separate small commit.
    > 
    > Further testing, feedback, OKs, welcome.
    
    Doesn't seem to get me much further on the vivobook.  Tried it first
    with a FritzBox.  After an "ifconfig qwz0 up" I see these messages:
    
    qwz_pull_reg_chan_list_ext_update_ev: not implemented
    qwz0: failed to extract regulatory info from received event
    qwz_pull_reg_chan_list_ext_update_ev: not implemented
    qwz0: failed to extract regulatory info from received event
    
    This isn't different from before.  I suppose this is something we can
    ignore for now.
    
    After dong an "ifconfig qwz0 nwid xxx wpakey yyy" I get a few more of
    these and then:
    
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    
    I think that is where it resets and tries again, where it becomes:
    
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    qwz0: failed to send wlan mode request, err = 1
    qwz0: qmi failed to send wlan mode off: 1
    
    And after a few more resets it becomes:
    
    qwz0: tx credits timeout
    qwz0: failed to send WMI_PDEV_SET_PARAM cmd
    qwz0: failed to enable MESH MCAST ENABLE for pdev 0: 35
    
    After that the device appears to be dead.  An attempt to revive with
    "ifconfig qwz0 down; ifconfig qwz0 up" results in:
    
    ifconfig: qwz0: SIOCSIFFLAGS: Resource temporarily unavailable
    
    and more:
    
    qwz0: tx credits timeout
    qwz0: failed to send WMI_PDEV_SET_PARAM cmd
    qwz0: failed to enable MESH MCAST ENABLE for pdev 0: 35
    
    I did see it reach the "status: active" state at some point, but that
    didn't last for more than a few seconds.
    
    I also tried with my athn(4) OpenBSD access point.  There it does
    reach the "status: active" state and stays in that state.  If I then
    do "ifconfig qwz0 autoconf", I get a few firmware errors:
    
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    
    But it recovers and remains associated.  After a while I also see:
    
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    qwz0: fatal firmware error
    qwz0: peer delete unmap timeout
    qwz0: unable to delete BSS peer: 35
    qwz0: failed to send wlan mode request, err = 1
    qwz0: qmi failed to send wlan mode off: 1
    
    but it stays associated.  My DHCP server sees the DHCP request and
    offers a lease, but the offered address never gets configured.
    
    So a little bit of progress.  I don't see a reason not to commit this.
    
    > Index: sys/dev/ic/qwz.c
    > ===================================================================
    > RCS file: /cvs/src/sys/dev/ic/qwz.c,v
    > diff -u -p -u -p -r1.25 qwz.c
    > --- sys/dev/ic/qwz.c	21 Apr 2026 08:56:22 -0000	1.25
    > +++ sys/dev/ic/qwz.c	25 Apr 2026 21:29:57 -0000
    > @@ -730,8 +730,17 @@ qwz_add_sta_key(struct qwz_softc *sc, st
    >  		nq->flags |= QWZ_NODE_FLAG_HAVE_PAIRWISE_KEY;
    >  
    >  	if ((nq->flags & want_keymask) == want_keymask) {
    > -		DPRINTF("marking port %s valid\n",
    > -		    ether_sprintf(ni->ni_macaddr));
    > +		uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
    > +
    > +		/* 4-way handshake done; authorize the BSS peer now. */
    > +		ret = qwz_wmi_set_peer_param(sc, ni->ni_macaddr, arvif->vdev_id,
    > +		    pdev_id, WMI_PEER_AUTHORIZE, 1);
    > +		if (ret) {
    > +			printf("%s: unable to authorize BSS peer: %d\n",
    > +			    sc->sc_dev.dv_xname, ret);
    > +			return ret;
    > +		}
    > +
    >  		ni->ni_port_valid = 1;
    >  		ieee80211_set_link_state(ic, LINK_STATE_UP);
    >  	}
    > @@ -1116,7 +1125,7 @@ qwz_hw_wcn7850_rx_desc_get_mpdu_seq_ctl_
    >  	      le32toh(desc->u.wcn7850.mpdu_start.info4));
    >  }
    >  
    > -int
    > +bool
    >  qwz_hw_wcn7850_rx_desc_get_mpdu_fc_valid(struct hal_rx_desc *desc)
    >  {
    >  	return !!FIELD_GET(RX_MPDU_START_INFO4_MPDU_FCTRL_VALID,
    > @@ -1230,6 +1239,13 @@ qwz_hw_wcn7850_rx_desc_is_da_mcbc(struct
    >  	    le32toh(desc->u.wcn7850.msdu_end.info13));
    >  }
    >  
    > +bool
    > +qwz_hw_wcn7850_dp_rx_h_msdu_done(struct hal_rx_desc *desc)
    > +{
    > +	return !!(le32toh(desc->u.wcn7850.msdu_end.info14) &
    > +	    RX_MSDU_END_INFO14_MSDU_DONE);
    > +}
    > +
    >  int
    >  qwz_hw_wcn7850_dp_rx_h_is_decrypted(struct hal_rx_desc *desc)
    >  {
    > @@ -1270,7 +1286,21 @@ qwz_hw_wcn7850_dp_rx_h_mpdu_err(struct h
    >  
    >  uint32_t qwz_hw_wcn7850_get_rx_desc_size(void)
    >  {
    > -	return sizeof(struct hal_rx_desc_wcn7850);
    > +	/*
    > +	 * Empirically observed on WCN7850 hw2.0 fw 0x110cffff: the FW
    > +	 * places the MSDU payload at offset 512 of the buffer (with the
    > +	 * mpdu_start_tag at 216 and mpdu_start data at 224, matching our
    > +	 * 80-byte rx_padding0). The struct sizeof works out to only 472
    > +	 * bytes, so override the descriptor size getter to return the
    > +	 * actual 512 bytes for m_adj to strip the right amount.
    > +	 *
    > +	 * NOTE: keeping struct sizeof at 472 is intentional; bumping
    > +	 * pkt_hdr_tlv to 168 to make sizeof = 512 caused spontaneous
    > +	 * machine reboots, suggesting a downstream code path (likely the
    > +	 * EAPOL TX response) was crashing the FW once real frames started
    > +	 * arriving. We isolate that here by only changing what m_adj sees.
    > +	 */
    > +	return 512;
    >  }
    >  
    >  uint8_t
    > @@ -1583,9 +1613,9 @@ const struct ce_attr qwz_host_ce_config_
    >  		.dest_nentries = 0,
    >  	},
    >  
    > -	/* CE5: target->host pktlog */
    > +	/* CE5: target->host pktlog (DIS_INTR: frees MSI vector 8 for DP group 0) */
    >  	{
    > -		.flags = CE_ATTR_FLAGS,
    > +		.flags = CE_ATTR_FLAGS | CE_ATTR_DIS_INTR,
    >  		.src_nentries = 0,
    >  		.src_sz_max = 0,
    >  		.dest_nentries = 0,
    > @@ -1652,9 +1682,9 @@ const struct hal_rx_ops hal_rx_wcn7850_o
    >  #ifdef notyet
    >  	.rx_desc_get_mesh_ctl = qwz_hw_wcn7850_rx_desc_get_mesh_ctl,
    >  	.rx_desc_get_mpdu_seq_ctl_vld = qwz_hw_wcn7850_rx_desc_get_mpdu_seq_ctl_vld,
    > -	.rx_desc_get_mpdu_fc_valid = qwz_hw_wcn7850_rx_desc_get_mpdu_fc_valid,
    >  	.rx_desc_get_mpdu_start_seq_no = qwz_hw_wcn7850_rx_desc_get_mpdu_start_seq_no,
    >  #endif
    > +	.rx_desc_get_mpdu_fc_valid = qwz_hw_wcn7850_rx_desc_get_mpdu_fc_valid,
    >  	.rx_desc_get_msdu_len = qwz_hw_wcn7850_rx_desc_get_msdu_len,
    >  #ifdef notyet
    >  	.rx_desc_get_msdu_sgi = qwz_hw_wcn7850_rx_desc_get_msdu_sgi,
    > @@ -1682,10 +1712,10 @@ const struct hal_rx_ops hal_rx_wcn7850_o
    >  	.rx_desc_get_dot11_hdr = qwz_hw_wcn7850_rx_desc_get_dot11_hdr,
    >  	.rx_desc_get_crypto_header = qwz_hw_wcn7850_rx_desc_get_crypto_hdr,
    >  	.rx_desc_get_mpdu_frame_ctl = qwz_hw_wcn7850_rx_desc_get_mpdu_frame_ctl,
    > -	.dp_rx_h_msdu_done = qwz_hw_wcn7850_dp_rx_h_msdu_done,
    >  	.dp_rx_h_l4_cksum_fail = qwz_hw_wcn7850_dp_rx_h_l4_cksum_fail,
    >  	.dp_rx_h_ip_cksum_fail = qwz_hw_wcn7850_dp_rx_h_ip_cksum_fail,
    >  #endif
    > +	.dp_rx_h_msdu_done = qwz_hw_wcn7850_dp_rx_h_msdu_done,
    >  	.dp_rx_h_is_decrypted = qwz_hw_wcn7850_dp_rx_h_is_decrypted,
    >  	.dp_rx_h_mpdu_err = qwz_hw_wcn7850_dp_rx_h_mpdu_err,
    >  	.rx_desc_get_desc_size = qwz_hw_wcn7850_get_rx_desc_size,
    > @@ -7613,29 +7643,6 @@ qwz_hal_srng_get_params(struct qwz_softc
    >  	params->flags = srng->flags;
    >  }
    >  
    > -void
    > -qwz_hal_tx_init_data_ring(struct qwz_softc *sc, struct hal_srng *srng)
    > -{
    > -	struct hal_srng_params params;
    > -	struct hal_tlv_hdr *tlv;
    > -	int i, entry_size;
    > -	uint8_t *desc;
    > -
    > -	memset(&params, 0, sizeof(params));
    > -
    > -	entry_size = qwz_hal_srng_get_entrysize(sc, HAL_TCL_DATA);
    > -	qwz_hal_srng_get_params(sc, srng, &params);
    > -	desc = (uint8_t *)params.ring_base_vaddr;
    > -
    > -	for (i = 0; i < params.num_entries; i++) {
    > -		tlv = (struct hal_tlv_hdr *)desc;
    > -		tlv->tl = FIELD_PREP(HAL_TLV_HDR_TAG, HAL_TCL_DATA_CMD) |
    > -		    FIELD_PREP(HAL_TLV_HDR_LEN,
    > -		    sizeof(struct hal_tcl_data_cmd));
    > -		desc += entry_size;
    > -	}
    > -}
    > -
    >  #define DSCP_TID_MAP_TBL_ENTRY_SIZE 64
    >  
    >  /* dscp_tid_map - Default DSCP-TID mapping
    > @@ -8021,9 +8028,6 @@ qwz_dp_srng_common_setup(struct qwz_soft
    >  			    sc->sc_dev.dv_xname, i, ret);
    >  			goto err;
    >  		}
    > -
    > -		srng = &sc->hal.srng_list[dp->tx_ring[i].tcl_data_ring.ring_id];
    > -		qwz_hal_tx_init_data_ring(sc, srng);
    >  	}
    >  
    >  	ret = qwz_dp_srng_setup(sc, &dp->reo_reinject_ring, HAL_REO_REINJECT,
    > @@ -8241,7 +8245,29 @@ void *ath12k_dp_cc_get_desc_addr_ptr(str
    >  {
    >  	struct qwz_dp *dp = &sc->dp;
    >  
    > -	return QWZ_DMA_KVA(dp->spt_info[ppt_idx].mem) + spt_idx;
    > +	/*
    > +	 * Cast to void** before adding spt_idx so arithmetic steps by
    > +	 * sizeof(void*), not by 1 byte.
    > +	 */
    > +	return (void **)QWZ_DMA_KVA(dp->spt_info[ppt_idx].mem) + spt_idx;
    > +}
    > +
    > +struct ath12k_rx_desc_info *
    > +qwz_dp_get_rx_desc(struct qwz_softc *sc, uint32_t cookie)
    > +{
    > +	struct ath12k_rx_desc_info **desc_addr_ptr;
    > +	uint16_t ppt_idx, spt_idx;
    > +
    > +	ppt_idx = FIELD_GET(ATH12K_DP_CC_COOKIE_PPT, cookie);
    > +	spt_idx = FIELD_GET(ATH12K_DP_CC_COOKIE_SPT, cookie);
    > +
    > +	if (ppt_idx < ATH12K_RX_SPT_PAGE_OFFSET ||
    > +	    ppt_idx >= ATH12K_RX_SPT_PAGE_OFFSET + ATH12K_NUM_RX_SPT_PAGES ||
    > +	    spt_idx >= ATH12K_MAX_SPT_ENTRIES)
    > +		return NULL;
    > +
    > +	desc_addr_ptr = ath12k_dp_cc_get_desc_addr_ptr(sc, ppt_idx, spt_idx);
    > +	return *desc_addr_ptr;
    >  }
    >  
    >  int
    > @@ -10042,10 +10068,6 @@ qwz_peer_assoc_conf_event(struct qwz_sof
    >  		return;
    >  	}
    >  
    > -	DNPRINTF(QWZ_D_WMI, "%s: event peer assoc conf ev vdev id %d "
    > -	    "macaddr %s\n", __func__, peer_assoc_conf.vdev_id,
    > -	    ether_sprintf((u_char *)peer_assoc_conf.macaddr));
    > -
    >  	sc->peer_assoc_done = 1;
    >  	wakeup(&sc->peer_assoc_done);
    >  }
    > @@ -12497,8 +12519,11 @@ qwz_peer_map_event(struct qwz_softc *sc,
    >  	spin_lock_bh(&ab->base_lock);
    >  #endif
    >  	ni = ieee80211_find_node(ic, mac_addr);
    > -	if (ni == NULL)
    > +	if (ni == NULL) {
    > +		printf("%s: peer_map: no node for %s\n", sc->sc_dev.dv_xname,
    > +		    ether_sprintf(mac_addr));
    >  		return;
    > +	}
    >  	nq = (struct qwz_node *)ni;
    >  	peer = &nq->peer;
    >  
    > @@ -12639,6 +12664,9 @@ qwz_dp_htt_htc_t2h_msg_handler(struct qw
    >  		ath12k_htt_backpressure_event_handler(ab, skb);
    >  		break;
    >  #endif
    > +	case HTT_T2H_MSG_TYPE_PRIMARY_LINK_PEER_MIGRATE_IND:
    > +		/* MLO peer migration; no action needed for non-MLO ops. */
    > +		break;
    >  	default:
    >  		printf("%s: htt event %d not handled\n", __func__, type);
    >  		break;
    > @@ -13040,6 +13068,13 @@ qwz_dp_rxbufs_replenish(struct qwz_softc
    >  			goto fail_free_mbuf;
    >  		}
    >  
    > +		/*
    > +		 * Invalidate any stale cache lines covering this buffer
    > +		 * before the FW writes RX data into it.
    > +		 */
    > +		bus_dmamap_sync(sc->sc_dmat, rx_desc->map, 0,
    > +		    rx_desc->map->dm_mapsize, BUS_DMASYNC_PREREAD);
    > +
    >  		cookie = rx_desc->cookie;
    >  
    >  		desc = qwz_hal_srng_src_get_next_entry(sc, srng);
    > @@ -13049,6 +13084,7 @@ qwz_dp_rxbufs_replenish(struct qwz_softc
    >  		TAILQ_REMOVE(used_list, rx_desc, entry);
    >  
    >  		rx_desc->m = m;
    > +		rx_desc->in_use = 1;
    >  		m = NULL;
    >  
    >  		num_remain--;
    > @@ -13636,6 +13672,97 @@ qwz_dp_update_vdev_search(struct qwz_sof
    >  	}
    >  }
    >  
    > +/*
    > + * Compute the 32-bit TX bank config for this VDEV. Mirror of Linux
    > + * ath12k_wifi7_dp_tx_get_vdev_bank_config().
    > + */
    > +uint32_t
    > +qwz_dp_tx_get_vdev_bank_config(struct qwz_softc *sc, struct qwz_vif *arvif)
    > +{
    > +	uint32_t bank_config = 0;
    > +	enum hal_tcl_encap_type encap_type;
    > +
    > +	if (test_bit(ATH12K_FLAG_RAW_MODE, sc->sc_flags))
    > +		encap_type = HAL_TCL_ENCAP_TYPE_RAW;
    > +	else
    > +		encap_type = HAL_TCL_ENCAP_TYPE_NATIVE_WIFI;
    > +
    > +	bank_config |= FIELD_PREP(HAL_TX_BANK_CONFIG_ENCAP_TYPE, encap_type);
    > +	bank_config |= FIELD_PREP(HAL_TX_BANK_CONFIG_SRC_BUFFER_SWAP, 0) |
    > +	    FIELD_PREP(HAL_TX_BANK_CONFIG_LINK_META_SWAP, 0) |
    > +	    FIELD_PREP(HAL_TX_BANK_CONFIG_EPD, 0);
    > +
    > +	if (arvif->vdev_type == WMI_VDEV_TYPE_STA)
    > +		bank_config |= FIELD_PREP(HAL_TX_BANK_CONFIG_INDEX_LOOKUP_EN, 1);
    > +
    > +	bank_config |= FIELD_PREP(HAL_TX_BANK_CONFIG_ADDRX_EN,
    > +	    !!(arvif->hal_addr_search_flags & HAL_TX_ADDRX_EN)) |
    > +	    FIELD_PREP(HAL_TX_BANK_CONFIG_ADDRY_EN,
    > +	    !!(arvif->hal_addr_search_flags & HAL_TX_ADDRY_EN));
    > +
    > +	bank_config |= FIELD_PREP(HAL_TX_BANK_CONFIG_MESH_EN, 0) |
    > +	    FIELD_PREP(HAL_TX_BANK_CONFIG_VDEV_ID_CHECK_EN, 1);
    > +	bank_config |= FIELD_PREP(HAL_TX_BANK_CONFIG_DSCP_TIP_MAP_ID, 0);
    > +
    > +	return bank_config;
    > +}
    > +
    > +/*
    > + * Write the per-bank TX config register so the FW picks up the encap/
    > + * encrypt/search settings when it processes a tcl_data_cmd with this
    > + * bank_id. Mirror of Linux ath12k_wifi7_hal_tx_configure_bank_register().
    > + */
    > +void
    > +qwz_hal_tx_configure_bank_register(struct qwz_softc *sc, uint32_t bank_config,
    > +    uint8_t bank_id)
    > +{
    > +	sc->ops.write32(sc, HAL_TCL_SW_CONFIG_BANK_ADDR + 4 * bank_id,
    > +	    bank_config);
    > +}
    > +
    > +/*
    > + * Allocate (or reuse) a TX bank profile that matches the requested
    > + * bank_config. Configures the FW register on first use of a bank.
    > + * Returns -1 if no bank slot is available.
    > + */
    > +int
    > +qwz_dp_tx_get_bank_profile(struct qwz_softc *sc, struct qwz_vif *arvif)
    > +{
    > +	struct qwz_dp *dp = &sc->dp;
    > +	uint32_t bank_config = qwz_dp_tx_get_vdev_bank_config(sc, arvif);
    > +	int i, bank_id = -1, configure_register = 0;
    > +
    > +	for (i = 0; i < dp->num_bank_profiles; i++) {
    > +		if (dp->bank_profiles[i].is_configured &&
    > +		    dp->bank_profiles[i].bank_config == bank_config) {
    > +			bank_id = i;
    > +			goto inc_ref_and_return;
    > +		}
    > +		if (!dp->bank_profiles[i].is_configured ||
    > +		    dp->bank_profiles[i].num_users == 0) {
    > +			bank_id = i;
    > +			goto configure_and_return;
    > +		}
    > +	}
    > +
    > +	if (bank_id == -1) {
    > +		printf("%s: out of TX bank slots\n", sc->sc_dev.dv_xname);
    > +		return -1;
    > +	}
    > +
    > +configure_and_return:
    > +	dp->bank_profiles[bank_id].is_configured = 1;
    > +	dp->bank_profiles[bank_id].bank_config = bank_config;
    > +	configure_register = 1;
    > +inc_ref_and_return:
    > +	dp->bank_profiles[bank_id].num_users++;
    > +
    > +	if (configure_register)
    > +		qwz_hal_tx_configure_bank_register(sc, bank_config, bank_id);
    > +
    > +	return bank_id;
    > +}
    > +
    >  void
    >  qwz_dp_vdev_tx_attach(struct qwz_softc *sc, struct qwz_pdev *pdev,
    >      struct qwz_vif *arvif)
    > @@ -13648,6 +13775,15 @@ qwz_dp_vdev_tx_attach(struct qwz_softc *
    >  	arvif->tcl_metadata &= ~HTT_TCL_META_DATA_VALID_HTT;
    >  
    >  	qwz_dp_update_vdev_search(sc, arvif);
    > +
    > +	/*
    > +	 * Allocate and configure a TX bank for this VDEV. The bank
    > +	 * register holds encap_type, encrypt_type, addrx/y_en, etc., and
    > +	 * the per-frame tcl_data_cmd descriptor only references it by
    > +	 * bank_id. WCN7850 (WiFi7) requires this; the older inline
    > +	 * encoding crashes the FW.
    > +	 */
    > +	arvif->bank_id = qwz_dp_tx_get_bank_profile(sc, arvif);
    >  }
    >  
    >  void
    > @@ -13838,6 +13974,8 @@ qwz_dp_tx_complete_msdu(struct qwz_softc
    >  		return;
    >  	}
    >  
    > +	bus_dmamap_sync(sc->sc_dmat, tx_data->map, 0,
    > +	    tx_data->map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
    >  	bus_dmamap_unload(sc->sc_dmat, tx_data->map);
    >  	m_freem(tx_data->m);
    >  	tx_data->m = NULL;
    > @@ -14096,7 +14234,7 @@ qwz_dp_process_rx_err_buf(struct qwz_sof
    >  	struct qwz_rx_data *rx_data;
    >  	struct hal_rx_desc *rx_desc;
    >  	uint16_t msdu_len;
    > -	uint32_t hal_rx_desc_sz = sc->hw_params.hal_desc_sz;
    > +	uint32_t hal_rx_desc_sz = sc->hal.hal_desc_sz;
    >  
    >  	if (buf_id >= rx_ring->bufs_max || isset(rx_ring->freemap, buf_id))
    >  		return;
    > @@ -14244,7 +14382,6 @@ qwz_dp_process_rx_err(struct qwz_softc *
    >  
    >  	return tot_n_bufs_reaped;
    >  #endif
    > -	printf("%s:%d\n", __func__, __LINE__);
    >  	return 0;
    >  }
    >  
    > @@ -14493,7 +14630,6 @@ done:
    >  
    >  	return total_num_buffs_reaped;
    >  #endif
    > -	printf("%s:%d\n", __func__, __LINE__);
    >  	return 0;
    >  }
    >  
    > @@ -14659,11 +14795,6 @@ qwz_dp_rx_h_undecap_raw(struct qwz_softc
    >  #endif
    >  }
    >  
    > -static inline uint8_t *
    > -qwz_dp_rx_h_80211_hdr(struct qwz_softc *sc, struct hal_rx_desc *desc)
    > -{
    > -	return sc->hal_rx_ops->rx_desc_get_hdr_status(desc);
    > -}
    >  
    >  static inline enum hal_encrypt_type
    >  qwz_dp_rx_h_enctype(struct qwz_softc *sc, struct hal_rx_desc *desc)
    > @@ -14681,46 +14812,95 @@ qwz_dp_rx_h_msdu_start_decap_type(struct
    >  }
    >  
    >  void
    > +qwz_dp_rx_h_undecap_eth(struct qwz_softc *sc, struct qwz_rx_msdu *msdu,
    > +    struct hal_rx_desc *rx_desc)
    > +{
    > +	struct rx_mpdu_start_qcn9274 *mpdu = &rx_desc->u.wcn7850.mpdu_start;
    > +	struct ether_header *eth;
    > +	uint8_t da[IEEE80211_ADDR_LEN], sa[IEEE80211_ADDR_LEN];
    > +	uint8_t llc[8] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 };
    > +	struct ieee80211_frame tmpwh;
    > +	int hdrlen;
    > +	uint8_t *p;
    > +	struct mbuf *m = msdu->m;
    > +
    > +	if (m->m_pkthdr.len < ETHER_HDR_LEN)
    > +		return;
    > +
    > +	eth = mtod(m, struct ether_header *);
    > +	memcpy(da, eth->ether_dhost, IEEE80211_ADDR_LEN);
    > +	memcpy(sa, eth->ether_shost, IEEE80211_ADDR_LEN);
    > +	memcpy(&llc[6], &eth->ether_type, 2); /* SNAP type = ethertype (BE) */
    > +
    > +	/* Determine 802.11 header length from mpdu_start frame_ctrl. */
    > +	tmpwh.i_fc[0] = le16toh(mpdu->frame_ctrl) & 0xff;
    > +	tmpwh.i_fc[1] = (le16toh(mpdu->frame_ctrl) >> 8) & 0xff;
    > +	hdrlen = sizeof(struct ieee80211_frame);
    > +	if (ieee80211_has_addr4(&tmpwh))
    > +		hdrlen += IEEE80211_ADDR_LEN;
    > +	if (ieee80211_has_qos(&tmpwh))
    > +		hdrlen += 2;
    > +
    > +	/* Strip Ethernet header. */
    > +	m_adj(m, ETHER_HDR_LEN);
    > +
    > +	/* Prepend LLC/SNAP header. */
    > +	M_PREPEND(m, 8, M_DONTWAIT);
    > +	if (m == NULL) {
    > +		msdu->m = NULL;
    > +		return;
    > +	}
    > +	msdu->m = m;
    > +	memcpy(mtod(m, void *), llc, 8);
    > +
    > +	/* Prepend 802.11 header. */
    > +	M_PREPEND(m, hdrlen, M_DONTWAIT);
    > +	if (m == NULL) {
    > +		msdu->m = NULL;
    > +		return;
    > +	}
    > +	msdu->m = m;
    > +	p = mtod(m, uint8_t *);
    > +	memset(p, 0, hdrlen);
    > +	p[0] = tmpwh.i_fc[0];
    > +	p[1] = tmpwh.i_fc[1];
    > +	memcpy(p + 2, &mpdu->duration, 2);
    > +	memcpy(p + 4, mpdu->addr1, IEEE80211_ADDR_LEN);
    > +	memcpy(p + 10, mpdu->addr2, IEEE80211_ADDR_LEN);
    > +	memcpy(p + 16, mpdu->addr3, IEEE80211_ADDR_LEN);
    > +	memcpy(p + 22, &mpdu->seq_ctrl, 2);
    > +	if (ieee80211_has_addr4(&tmpwh)) {
    > +		memcpy(p + 24, mpdu->addr4, IEEE80211_ADDR_LEN);
    > +		if (ieee80211_has_qos(&tmpwh))
    > +			memcpy(p + 30, &mpdu->qos_ctrl, 2);
    > +	} else if (ieee80211_has_qos(&tmpwh)) {
    > +		memcpy(p + 24, &mpdu->qos_ctrl, 2);
    > +	}
    > +
    > +	/* Override addr1/addr3 with actual DA/SA from Ethernet header. */
    > +	memcpy(p + 4, da, IEEE80211_ADDR_LEN);
    > +	memcpy(p + 16, sa, IEEE80211_ADDR_LEN);
    > +}
    > +
    > +void
    >  qwz_dp_rx_h_undecap(struct qwz_softc *sc, struct qwz_rx_msdu *msdu,
    >      struct hal_rx_desc *rx_desc, enum hal_encrypt_type enctype,
    >      int decrypted)
    >  {
    > -	uint8_t *first_hdr;
    >  	uint8_t decap;
    >  
    > -	first_hdr = qwz_dp_rx_h_80211_hdr(sc, rx_desc);
    >  	decap = qwz_dp_rx_h_msdu_start_decap_type(sc, rx_desc);
    >  
    >  	switch (decap) {
    >  	case DP_RX_DECAP_TYPE_NATIVE_WIFI:
    > -		qwz_dp_rx_h_undecap_nwifi(sc, msdu, first_hdr, enctype);
    > +		qwz_dp_rx_h_undecap_nwifi(sc, msdu, NULL, enctype);
    >  		break;
    >  	case DP_RX_DECAP_TYPE_RAW:
    >  		qwz_dp_rx_h_undecap_raw(sc, msdu, enctype, decrypted);
    >  		break;
    > -#if 0
    >  	case DP_RX_DECAP_TYPE_ETHERNET2_DIX:
    > -		ehdr = (struct ethhdr *)msdu->data;
    > -
    > -		/* mac80211 allows fast path only for authorized STA */
    > -		if (ehdr->h_proto == cpu_to_be16(ETH_P_PAE)) {
    > -			ATH12K_SKB_RXCB(msdu)->is_eapol = true;
    > -			ath12k_dp_rx_h_undecap_eth(ar, msdu, first_hdr,
    > -						   enctype, status);
    > -			break;
    > -		}
    > -
    > -		/* PN for mcast packets will be validated in mac80211;
    > -		 * remove eth header and add 802.11 header.
    > -		 */
    > -		if (ATH12K_SKB_RXCB(msdu)->is_mcbc && decrypted)
    > -			ath12k_dp_rx_h_undecap_eth(ar, msdu, first_hdr,
    > -						   enctype, status);
    > -		break;
    > -	case DP_RX_DECAP_TYPE_8023:
    > -		/* TODO: Handle undecap for these formats */
    > +		qwz_dp_rx_h_undecap_eth(sc, msdu, rx_desc);
    >  		break;
    > -#endif
    >  	}
    >  }
    >  
    > @@ -14828,7 +15008,7 @@ qwz_dp_rx_process_msdu(struct qwz_softc 
    >  	uint8_t l3_pad_bytes;
    >  	uint16_t msdu_len;
    >  	int ret;
    > -	uint32_t hal_rx_desc_sz = sc->hw_params.hal_desc_sz;
    > +	uint32_t hal_rx_desc_sz = sc->hal.hal_desc_sz;
    >  
    >  	last_buf = qwz_dp_rx_get_msdu_last_buf(msdu_list, msdu);
    >  	if (!last_buf) {
    > @@ -14845,6 +15025,25 @@ qwz_dp_rx_process_msdu(struct qwz_softc 
    >  		return EIO;
    >  	}
    >  
    > +	/* Drop non-802.11 frames (e.g. WCN7850 FW internal messages). */
    > +	if (!sc->hal_rx_ops->rx_desc_get_mpdu_fc_valid(rx_desc))
    > +		return EIO;
    > +
    > +	/*
    > +	 * WCN7850 FW injects internal messages into the REO ring with
    > +	 * fc_valid=1 but garbage 802.11 contents. Their synthetic addr1
    > +	 * always ends in 84:e1 (regardless of the multicast bit). Drop
    > +	 * those, then drop any remaining unicast frames not addressed
    > +	 * to our own MAC.
    > +	 */
    > +	struct rx_mpdu_start_qcn9274 *mpdu = &rx_desc->u.wcn7850.mpdu_start;
    > +
    > +	if (mpdu->addr1[4] == 0x84 && mpdu->addr1[5] == 0xe1)
    > +		return EIO;
    > +	if (!(mpdu->addr1[0] & 0x01) &&
    > +	    !IEEE80211_ADDR_EQ(mpdu->addr1, sc->sc_ic.ic_myaddr))
    > +		return EIO;
    > +
    >  	msdu->rx_desc = rx_desc;
    >  	msdu_len = qwz_dp_rx_h_msdu_start_msdu_len(sc, rx_desc);
    >  	l3_pad_bytes = qwz_dp_rx_h_msdu_end_l3pad(sc, lrx_desc);
    > @@ -14935,27 +15134,25 @@ qwz_dp_rx_process_received_packets(struc
    >  int
    >  qwz_dp_process_rx(struct qwz_softc *sc, int ring_id)
    >  {
    > -#if 0
    >  	struct qwz_dp *dp = &sc->dp;
    > -	struct qwz_pdev_dp *pdev_dp = &sc->pdev_dp;
    > -	struct dp_rxdma_ring *rx_ring;
    >  	int num_buffs_reaped[MAX_RADIOS] = {0};
    >  	struct qwz_rx_msdu_list msdu_list[MAX_RADIOS];
    > +	TAILQ_HEAD(, ath12k_rx_desc_info) rx_desc_used_list;
    > +	struct ath12k_rx_desc_info *desc_info;
    >  	struct qwz_rx_msdu *msdu;
    >  	struct mbuf *m;
    > -	struct qwz_rx_data *rx_data;
    >  	int total_msdu_reaped = 0;
    >  	struct hal_srng *srng;
    >  	int done = 0;
    > -	int idx;
    > +	int budget = 512;
    >  	unsigned int mac_id;
    >  	struct hal_reo_dest_ring *desc;
    >  	enum hal_reo_dest_ring_push_reason push_reason;
    > -	uint32_t cookie;
    >  	int i;
    >  
    >  	for (i = 0; i < MAX_RADIOS; i++)
    >  		TAILQ_INIT(&msdu_list[i]);
    > +	TAILQ_INIT(&rx_desc_used_list);
    >  
    >  	srng = &sc->hal.srng_list[dp->reo_dst_ring[ring_id].ring_id];
    >  #ifdef notyet
    > @@ -14964,41 +15161,60 @@ qwz_dp_process_rx(struct qwz_softc *sc, 
    >  try_again:
    >  	qwz_hal_srng_access_begin(sc, srng);
    >  
    > -	while ((desc = (struct hal_reo_dest_ring *)
    > +	while (budget > 0 && (desc = (struct hal_reo_dest_ring *)
    >  	    qwz_hal_srng_dst_get_next_entry(sc, srng))) {
    > +		budget--;
    > +		uint64_t desc_va;
    > +		uint32_t cookie;
    > +
    >  		cookie = FIELD_GET(BUFFER_ADDR_INFO1_SW_COOKIE,
    >  		    desc->buf_addr_info.info1);
    > -		idx = FIELD_GET(DP_RXDMA_BUF_COOKIE_BUF_ID, cookie);
    > -		mac_id = FIELD_GET(DP_RXDMA_BUF_COOKIE_PDEV_ID, cookie);
    > +		desc_va = (uint64_t)desc->buf_va_hi << 32 | desc->buf_va_lo;
    >  
    > -		if (mac_id >= MAX_RADIOS)
    > -			continue;
    > +		/* Validate VA looks like a kernel address before dereferencing */
    > +		if (desc_va >= 0xffff800000000000ULL)
    > +			desc_info = (struct ath12k_rx_desc_info *)desc_va;
    > +		else
    > +			desc_info = NULL;
    >  
    > -		rx_ring = &pdev_dp->rx_refill_buf_ring;
    > -		if (idx >= rx_ring->bufs_max || isset(rx_ring->freemap, idx))
    > +		/* Fall back to cookie-based lookup if HW CC gave invalid VA */
    > +		if (desc_info == NULL ||
    > +		    desc_info->magic != ATH12K_DP_RX_DESC_MAGIC) {
    > +			desc_info = qwz_dp_get_rx_desc(sc, cookie);
    > +			if (desc_info == NULL ||
    > +			    desc_info->magic != ATH12K_DP_RX_DESC_MAGIC)
    > +				continue;
    > +		}
    > +
    > +		if (!desc_info->in_use)
    >  			continue;
    >  
    > -		rx_data = &rx_ring->rx_data[idx];
    > -		bus_dmamap_unload(sc->sc_dmat, rx_data->map);
    > -		m = rx_data->m;
    > -		rx_data->m = NULL;
    > -		setbit(rx_ring->freemap, idx);
    > +		/*
    > +		 * Sync the DMA buffer for CPU read before reading the
    > +		 * descriptor / MSDU data.
    > +		 */
    > +		bus_dmamap_sync(sc->sc_dmat, desc_info->map, 0,
    > +		    desc_info->map->dm_mapsize, BUS_DMASYNC_POSTREAD);
    > +		bus_dmamap_unload(sc->sc_dmat, desc_info->map);
    > +		m = desc_info->m;
    > +		desc_info->m = NULL;
    > +		desc_info->in_use = 0;
    >  
    > +		/* WCN7850 is single-radio */
    > +		mac_id = 0;
    >  		num_buffs_reaped[mac_id]++;
    >  
    > +		TAILQ_INSERT_TAIL(&rx_desc_used_list, desc_info, entry);
    > +
    >  		push_reason = FIELD_GET(HAL_REO_DEST_RING_INFO0_PUSH_REASON,
    >  		    desc->info0);
    >  		if (push_reason !=
    >  		    HAL_REO_DEST_RING_PUSH_REASON_ROUTING_INSTRUCTION) {
    >  			m_freem(m);
    > -#if 0
    > -			sc->soc_stats.hal_reo_error[
    > -			    dp->reo_dst_ring[ring_id].ring_id]++;
    > -#endif
    >  			continue;
    >  		}
    >  
    > -		msdu = &rx_data->rx_msdu;
    > +		msdu = &desc_info->rx_msdu;
    >  		msdu->m = m;
    >  		msdu->is_first_msdu = !!(desc->rx_msdu_info.info0 &
    >  		    RX_MSDU_DESC_INFO0_FIRST_MSDU_IN_MPDU);
    > @@ -15010,8 +15226,7 @@ try_again:
    >  		    desc->rx_mpdu_info.meta_data);
    >  		msdu->seq_no = FIELD_GET(RX_MPDU_DESC_INFO0_SEQ_NUM,
    >  		    desc->rx_mpdu_info.info0);
    > -		msdu->tid = FIELD_GET(HAL_REO_DEST_RING_INFO0_RX_QUEUE_NUM,
    > -		    desc->info0);
    > +		msdu->tid = 0; /* no RX_QUEUE_NUM in wifi7 */
    >  
    >  		msdu->mac_id = mac_id;
    >  		TAILQ_INSERT_TAIL(&msdu_list[mac_id], msdu, entry);
    > @@ -15030,7 +15245,7 @@ try_again:
    >  	 * head pointer so that we can reap complete MPDU in the current
    >  	 * rx processing.
    >  	 */
    > -	if (!done && qwz_hal_srng_dst_num_free(sc, srng, 1)) {
    > +	if (!done && budget > 0 && qwz_hal_srng_dst_num_free(sc, srng, 1)) {
    >  		qwz_hal_srng_access_end(sc, srng);
    >  		goto try_again;
    >  	}
    > @@ -15048,16 +15263,11 @@ try_again:
    >  
    >  		qwz_dp_rx_process_received_packets(sc, &msdu_list[i], i);
    >  
    > -		rx_ring = &sc->pdev_dp.rx_refill_buf_ring;
    > -
    > -		qwz_dp_rxbufs_replenish(sc, i, rx_ring, num_buffs_reaped[i],
    > -		    sc->hw_params.hal_params->rx_buf_rbm);
    > +		qwz_dp_rxbufs_replenish(sc, &dp->rx_refill_buf_ring,
    > +		    &rx_desc_used_list, num_buffs_reaped[i]);
    >  	}
    >  exit:
    >  	return total_msdu_reaped;
    > -#endif
    > -	printf("%s:%d\n", __func__, __LINE__);
    > -	return 0;
    >  }
    >  
    >  #if 0
    > @@ -15361,6 +15571,7 @@ qwz_dp_wmask_compaction_rx_tlv_supported
    >  void
    >  qwz_dp_hal_rx_desc_init(struct qwz_softc *sc)
    >  {
    > +	sc->hal_rx_ops = &hal_rx_wcn7850_ops;
    >  	if (qwz_dp_wmask_compaction_rx_tlv_supported(sc)) {
    >  		/* RX TLVS compaction is supported, hence change the hal_rx_ops
    >  		 * to compact hal_rx_ops.
    > @@ -21045,6 +21256,7 @@ qwz_vif_alloc(struct qwz_softc *sc)
    >  	arvif = malloc(sizeof(*arvif), M_DEVBUF, M_NOWAIT | M_ZERO);
    >  	if (arvif == NULL)
    >  		return NULL;
    > +	arvif->bank_id = -1;
    >  
    >  	txmgmt = &arvif->txmgmt;
    >  	for (i = 0; i < nitems(txmgmt->data); i++) {
    > @@ -22061,7 +22273,7 @@ qwz_peer_frags_flush(struct qwz_softc *s
    >  #ifdef notyet
    >  	lockdep_assert_held(&ar->ab->base_lock);
    >  #endif
    > -	for (i = 0; i < IEEE80211_NUM_TID; i++) {
    > +	for (i = 0; i <= HAL_DESC_REO_NON_QOS_TID; i++) {
    >  		rx_tid = &peer->rx_tid[i];
    >  
    >  		qwz_dp_rx_frags_cleanup(sc, rx_tid, 1);
    > @@ -22081,7 +22293,7 @@ qwz_peer_rx_tid_cleanup(struct qwz_softc
    >  #ifdef notyet
    >  	lockdep_assert_held(&ar->ab->base_lock);
    >  #endif
    > -	for (i = 0; i < IEEE80211_NUM_TID; i++) {
    > +	for (i = 0; i <= HAL_DESC_REO_NON_QOS_TID; i++) {
    >  		rx_tid = &peer->rx_tid[i];
    >  
    >  		qwz_peer_rx_tid_delete(sc, peer, i);
    > @@ -22285,7 +22497,7 @@ qwz_dp_peer_setup(struct qwz_softc *sc, 
    >  		return ret;
    >  	}
    >  
    > -	for (tid = 0; tid < IEEE80211_NUM_TID; tid++) {
    > +	for (tid = 0; tid <= HAL_DESC_REO_NON_QOS_TID; tid++) {
    >  		ret = qwz_peer_rx_tid_setup(sc, ni, vdev_id, pdev_id,
    >  		    tid, 1, 0, HAL_PN_TYPE_NONE);
    >  		if (ret) {
    > @@ -22413,6 +22625,14 @@ qwz_dp_tx_get_tid(struct mbuf *m)
    >  	return tid;
    >  }
    >  
    > +/*
    > + * Build a WCN7850 / ath12k WiFi7 TCL data descriptor. Encap/encrypt/
    > + * search settings live in the bank addressed by ti->bank_id; the
    > + * descriptor only carries DESC_TYPE | BANK_ID + per-frame fields.
    > + * Each TCL_DATA ring slot is just a hal_tcl_data_cmd (no per-entry
    > + * TLV header, unlike ath11k); FW reads info0 from offset 0.
    > + * Mirror of Linux ath12k_wifi7_hal_tx_cmd_desc_setup().
    > + */
    >  void
    >  qwz_hal_tx_cmd_desc_setup(struct qwz_softc *sc, void *cmd,
    >      struct hal_tx_info *ti)
    > @@ -22429,29 +22649,24 @@ qwz_hal_tx_cmd_desc_setup(struct qwz_sof
    >  
    >  	tcl_cmd->info0 =
    >  	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_DESC_TYPE, ti->type) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_ENCAP_TYPE, ti->encap_type) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_ENCRYPT_TYPE, ti->encrypt_type) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_SEARCH_TYPE, ti->search_type) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_ADDR_EN, ti->addr_search_flags) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_CMD_NUM, ti->meta_data_flags);
    > -
    > -	tcl_cmd->info1 = ti->flags0 |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO1_DATA_LEN, ti->data_len) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO1_PKT_OFFSET, ti->pkt_offset);
    > -
    > -	tcl_cmd->info2 = ti->flags1 |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO2_TID, ti->tid) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO2_LMAC_ID, ti->lmac_id);
    > -
    > -	tcl_cmd->info3 = FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_DSCP_TID_TABLE_IDX,
    > -	    ti->dscp_tid_tbl_idx) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_SEARCH_INDEX, ti->bss_ast_idx) |
    > -	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_CACHE_SET_NUM, ti->bss_ast_hash);
    > -	tcl_cmd->info4 = 0;
    > -#ifdef notyet
    > -	if (ti->enable_mesh)
    > -		ab->hw_params.hw_ops->tx_mesh_enable(ab, tcl_cmd);
    > -#endif
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO0_BANK_ID, ti->bank_id);
    > +
    > +	tcl_cmd->info1 = FIELD_PREP(HAL_TCL_DATA_CMD_INFO1_CMD_NUM,
    > +	    ti->meta_data_flags);
    > +
    > +	tcl_cmd->info2 = ti->flags0 |
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO2_DATA_LEN, ti->data_len) |
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO2_PKT_OFFSET, ti->pkt_offset);
    > +
    > +	tcl_cmd->info3 = ti->flags1 |
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_TID, ti->tid) |
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_PMAC_ID, ti->lmac_id) |
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_VDEV_ID, ti->vdev_id);
    > +
    > +	tcl_cmd->info4 =
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO4_SEARCH_INDEX, ti->bss_ast_idx) |
    > +	    FIELD_PREP(HAL_TCL_DATA_CMD_INFO4_CACHE_SET_NUM, ti->bss_ast_hash);
    > +	tcl_cmd->info5 = 0;
    >  }
    >  
    >  int
    > @@ -22477,6 +22692,23 @@ qwz_dp_tx(struct qwz_softc *sc, struct q
    >  		m_freem(m);
    >  		return ESHUTDOWN;
    >  	}
    > +
    > +	if (arvif->bank_id < 0) {
    > +		printf("%s: TX before TX bank is configured, dropping\n",
    > +		    sc->sc_dev.dv_xname);
    > +		m_freem(m);
    > +		return EIO;
    > +	}
    > +
    > +	/*
    > +	 * Default to no encryption. enum hal_encrypt_type starts with
    > +	 * HAL_ENCRYPT_TYPE_WEP_40 = 0, and the {0} initializer would
    > +	 * otherwise silently select WEP40.
    > +	 */
    > +	ti.encrypt_type = HAL_ENCRYPT_TYPE_OPEN;
    > +	ti.bank_id = arvif->bank_id;
    > +	ti.vdev_id = arvif->vdev_id;
    > +
    >  #if 0
    >  	if (unlikely(!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) &&
    >  		     !ieee80211_is_data(hdr->frame_control)))
    > @@ -22570,7 +22802,7 @@ qwz_dp_tx(struct qwz_softc *sc, struct q
    >  	if (ieee80211_vif_is_mesh(arvif->vif))
    >  		ti.enable_mesh = true;
    >  #endif
    > -	ti.flags1 |= FIELD_PREP(HAL_TCL_DATA_CMD_INFO2_TID_OVERWRITE, 1);
    > +	ti.flags1 |= FIELD_PREP(HAL_TCL_DATA_CMD_INFO3_TID_OVERWRITE, 1);
    >  
    >  	ti.tid = qwz_dp_tx_get_tid(m);
    >  #if 0
    > @@ -22620,6 +22852,9 @@ qwz_dp_tx(struct qwz_softc *sc, struct q
    >  	}
    >  	ti.paddr = tx_data->map->dm_segs[0].ds_addr;
    >  
    > +	bus_dmamap_sync(sc->sc_dmat, tx_data->map, 0,
    > +	    tx_data->map->dm_mapsize, BUS_DMASYNC_PREWRITE);
    > +
    >  	ti.data_len = m->m_pkthdr.len;
    >  
    >  	hal_ring_id = tx_ring->tcl_data_ring.ring_id;
    > @@ -22649,8 +22884,7 @@ qwz_dp_tx(struct qwz_softc *sc, struct q
    >  	tx_data->m = m;
    >  	tx_data->ni = ni;
    >  
    > -	qwz_hal_tx_cmd_desc_setup(sc,
    > -	    hal_tcl_desc + sizeof(struct hal_tlv_hdr), &ti);
    > +	qwz_hal_tx_cmd_desc_setup(sc, hal_tcl_desc, &ti);
    >  
    >  	qwz_hal_srng_access_end(sc, tcl_ring);
    >  
    > @@ -23404,7 +23638,7 @@ qwz_peer_assoc_h_basic(struct qwz_softc 
    >  
    >  	IEEE80211_ADDR_COPY(arg->peer_mac, ni->ni_macaddr);
    >  	arg->vdev_id = arvif->vdev_id;
    > -	arg->peer_associd = ni->ni_associd;
    > +	arg->peer_associd = IEEE80211_AID(ni->ni_associd);
    >  	arg->auth_flag = 1;
    >  	arg->peer_listen_intval = ni->ni_intval;
    >  	arg->peer_nss = 1;
    > @@ -23564,7 +23798,12 @@ qwz_run(struct qwz_softc *sc)
    >  	WARN_ON(arvif->is_up);
    >  #endif
    >  
    > -	arvif->aid = ni->ni_associd;
    > +	/*
    > +	 * ni->ni_associd carries the raw on-wire AID field including the
    > +	 * top two reserved bits (which are always 11 on the wire). The FW
    > +	 * wants the 14-bit AID value only.
    > +	 */
    > +	arvif->aid = IEEE80211_AID(ni->ni_associd);
    >  	IEEE80211_ADDR_COPY(arvif->bssid, ni->ni_bssid);
    >  
    >  	ret = qwz_wmi_vdev_up(sc, arvif->vdev_id, pdev_id, arvif->aid,
    > @@ -23582,14 +23821,6 @@ qwz_run(struct qwz_softc *sc)
    >  
    >  	DNPRINTF(QWZ_D_MAC, "%s: vdev %d up (associated) bssid %s aid %d\n",
    >  	    __func__, arvif->vdev_id, ether_sprintf(ni->ni_bssid), arvif->aid);
    > -
    > -	ret = qwz_wmi_set_peer_param(sc, ni->ni_macaddr, arvif->vdev_id,
    > -	    pdev_id, WMI_PEER_AUTHORIZE, 1);
    > -	if (ret) {
    > -		printf("%s: unable to authorize BSS peer: %d\n",
    > -		   sc->sc_dev.dv_xname, ret);
    > -		return ret;
    > -	}
    >  
    >  	/* Enable "ext" IRQs for datapath. */
    >  	sc->ops.irq_enable(sc);
    > Index: sys/dev/ic/qwzreg.h
    > ===================================================================
    > RCS file: /cvs/src/sys/dev/ic/qwzreg.h,v
    > diff -u -p -u -p -r1.12 qwzreg.h
    > --- sys/dev/ic/qwzreg.h	12 Apr 2026 19:52:23 -0000	1.12
    > +++ sys/dev/ic/qwzreg.h	25 Apr 2026 21:29:58 -0000
    > @@ -8236,7 +8236,6 @@ enum hal_rx_msdu_desc_reo_dest_ind {
    >  
    >  struct rx_msdu_desc {
    >  	uint32_t info0;
    > -	uint32_t rsvd0;
    >  } __packed;
    >  
    >  /* rx_msdu_desc
    > @@ -8406,33 +8405,21 @@ enum hal_reo_dest_ring_error_code {
    >  	HAL_REO_DEST_RING_ERROR_CODE_MAX,
    >  };
    >  
    > -#define HAL_REO_DEST_RING_INFO0_QUEUE_ADDR_HI		GENMASK(7, 0)
    > -#define HAL_REO_DEST_RING_INFO0_BUFFER_TYPE		(1 << 8)
    > -#define HAL_REO_DEST_RING_INFO0_PUSH_REASON		GENMASK(10, 9)
    > -#define HAL_REO_DEST_RING_INFO0_ERROR_CODE		GENMASK(15, 11)
    > -#define HAL_REO_DEST_RING_INFO0_RX_QUEUE_NUM		GENMASK(31, 16)
    > -
    > -#define HAL_REO_DEST_RING_INFO1_REORDER_INFO_VALID	(1 << 0)
    > -#define HAL_REO_DEST_RING_INFO1_REORDER_OPCODE		GENMASK(4, 1)
    > -#define HAL_REO_DEST_RING_INFO1_REORDER_SLOT_IDX	GENMASK(12, 5)
    > -
    > -#define HAL_REO_DEST_RING_INFO2_RING_ID			GENMASK(27, 20)
    > -#define HAL_REO_DEST_RING_INFO2_LOOPING_COUNT		GENMASK(31, 28)
    > +/* wifi7 / WCN7850 REO destination ring info0 */
    > +#define HAL_REO_DEST_RING_INFO0_PUSH_REASON		GENMASK(2, 1)
    > +#define HAL_REO_DEST_RING_INFO0_ERROR_CODE		GENMASK(7, 3)
    > +#define HAL_REO_DEST_RING_INFO0_BUFFER_TYPE		BIT(0)
    > +#define HAL_REO_DEST_RING_INFO0_SRC_LINK_ID		GENMASK(15, 13)
    > +#define HAL_REO_DEST_RING_INFO0_RING_ID			GENMASK(27, 20)
    > +#define HAL_REO_DEST_RING_INFO0_LOOPING_COUNT		GENMASK(31, 28)
    >  
    >  struct hal_reo_dest_ring {
    >  	struct ath12k_buffer_addr buf_addr_info;
    >  	struct rx_mpdu_desc rx_mpdu_info;
    >  	struct rx_msdu_desc rx_msdu_info;
    > -	uint32_t queue_addr_lo;
    > +	uint32_t buf_va_lo;
    > +	uint32_t buf_va_hi;
    >  	uint32_t info0; /* %HAL_REO_DEST_RING_INFO0_ */
    > -	uint32_t info1; /* %HAL_REO_DEST_RING_INFO1_ */
    > -	uint32_t rsvd0;
    > -	uint32_t rsvd1;
    > -	uint32_t rsvd2;
    > -	uint32_t rsvd3;
    > -	uint32_t rsvd4;
    > -	uint32_t rsvd5;
    > -	uint32_t info2; /* %HAL_REO_DEST_RING_INFO2_ */
    >  } __packed;
    >  
    >  /* hal_reo_dest_ring
    > @@ -8843,39 +8830,61 @@ struct hal_reo_flush_cache {
    >  	uint32_t rsvd0[6];
    >  } __packed;
    >  
    > -#define HAL_TCL_DATA_CMD_INFO0_DESC_TYPE	BIT(0)
    > -#define HAL_TCL_DATA_CMD_INFO0_EPD		BIT(1)
    > -#define HAL_TCL_DATA_CMD_INFO0_ENCAP_TYPE	GENMASK(3, 2)
    > -#define HAL_TCL_DATA_CMD_INFO0_ENCRYPT_TYPE	GENMASK(7, 4)
    > -#define HAL_TCL_DATA_CMD_INFO0_SRC_BUF_SWAP	BIT(8)
    > -#define HAL_TCL_DATA_CMD_INFO0_LNK_META_SWAP	BIT(9)
    > -#define HAL_TCL_DATA_CMD_INFO0_SEARCH_TYPE	GENMASK(13, 12)
    > -#define HAL_TCL_DATA_CMD_INFO0_ADDR_EN		GENMASK(15, 14)
    > -#define HAL_TCL_DATA_CMD_INFO0_CMD_NUM		GENMASK(31, 16)
    > -
    > -#define HAL_TCL_DATA_CMD_INFO1_DATA_LEN		GENMASK(15, 0)
    > -#define HAL_TCL_DATA_CMD_INFO1_IP4_CKSUM_EN	BIT(16)
    > -#define HAL_TCL_DATA_CMD_INFO1_UDP4_CKSUM_EN	BIT(17)
    > -#define HAL_TCL_DATA_CMD_INFO1_UDP6_CKSUM_EN	BIT(18)
    > -#define HAL_TCL_DATA_CMD_INFO1_TCP4_CKSUM_EN	BIT(19)
    > -#define HAL_TCL_DATA_CMD_INFO1_TCP6_CKSUM_EN	BIT(20)
    > -#define HAL_TCL_DATA_CMD_INFO1_TO_FW		BIT(21)
    > -#define HAL_TCL_DATA_CMD_INFO1_PKT_OFFSET	GENMASK(31, 23)
    > -
    > -#define HAL_TCL_DATA_CMD_INFO2_BUF_TIMESTAMP		GENMASK(18, 0)
    > -#define HAL_TCL_DATA_CMD_INFO2_BUF_T_VALID		BIT(19)
    > -#define HAL_IPQ8074_TCL_DATA_CMD_INFO2_MESH_ENABLE	BIT(20)
    > -#define HAL_TCL_DATA_CMD_INFO2_TID_OVERWRITE		BIT(21)
    > -#define HAL_TCL_DATA_CMD_INFO2_TID			GENMASK(25, 22)
    > -#define HAL_TCL_DATA_CMD_INFO2_LMAC_ID			GENMASK(27, 26)
    > -
    > -#define HAL_TCL_DATA_CMD_INFO3_DSCP_TID_TABLE_IDX	GENMASK(5, 0)
    > -#define HAL_TCL_DATA_CMD_INFO3_SEARCH_INDEX		GENMASK(25, 6)
    > -#define HAL_TCL_DATA_CMD_INFO3_CACHE_SET_NUM		GENMASK(29, 26)
    > -#define HAL_QCN9074_TCL_DATA_CMD_INFO3_MESH_ENABLE	GENMASK(31, 30)
    > -
    > -#define HAL_TCL_DATA_CMD_INFO4_RING_ID			GENMASK(27, 20)
    > -#define HAL_TCL_DATA_CMD_INFO4_LOOPING_COUNT		GENMASK(31, 28)
    > +/*
    > + * WCN7850 / ath12k WiFi7 TCL data descriptor layout. The OLDER
    > + * ath11k/ath12k-non-WiFi7 layout had encap_type/encrypt_type/search_type/
    > + * addr_en encoded inline in info0; the WiFi7 hardware moves these to a
    > + * pre-configured TX bank addressed by bank_id. The descriptor itself
    > + * carries DESC_TYPE | BANK_ID | DATA_LEN | TID | VDEV_ID | search_index.
    > + */
    > +#define HAL_TCL_DATA_CMD_INFO0_CMD_TYPE		BIT(0)
    > +#define HAL_TCL_DATA_CMD_INFO0_DESC_TYPE	BIT(1)
    > +#define HAL_TCL_DATA_CMD_INFO0_BANK_ID		GENMASK(7, 2)
    > +#define HAL_TCL_DATA_CMD_INFO0_TX_NOTIFY_FRAME	GENMASK(10, 8)
    > +#define HAL_TCL_DATA_CMD_INFO0_HDR_LEN_READ_SEL	BIT(11)
    > +#define HAL_TCL_DATA_CMD_INFO0_BUF_TIMESTAMP	GENMASK(30, 12)
    > +#define HAL_TCL_DATA_CMD_INFO0_BUF_TIMESTAMP_VLD	BIT(31)
    > +
    > +#define HAL_TCL_DATA_CMD_INFO1_CMD_NUM		GENMASK(31, 16)
    > +
    > +#define HAL_TCL_DATA_CMD_INFO2_DATA_LEN		GENMASK(15, 0)
    > +#define HAL_TCL_DATA_CMD_INFO2_IP4_CKSUM_EN	BIT(16)
    > +#define HAL_TCL_DATA_CMD_INFO2_UDP4_CKSUM_EN	BIT(17)
    > +#define HAL_TCL_DATA_CMD_INFO2_UDP6_CKSUM_EN	BIT(18)
    > +#define HAL_TCL_DATA_CMD_INFO2_TCP4_CKSUM_EN	BIT(19)
    > +#define HAL_TCL_DATA_CMD_INFO2_TCP6_CKSUM_EN	BIT(20)
    > +#define HAL_TCL_DATA_CMD_INFO2_TO_FW		BIT(21)
    > +#define HAL_TCL_DATA_CMD_INFO2_PKT_OFFSET	GENMASK(31, 23)
    > +
    > +#define HAL_TCL_DATA_CMD_INFO3_TID_OVERWRITE	BIT(0)
    > +#define HAL_TCL_DATA_CMD_INFO3_FLOW_OVERRIDE_EN	BIT(1)
    > +#define HAL_TCL_DATA_CMD_INFO3_CLASSIFY_INFO_SEL	GENMASK(3, 2)
    > +#define HAL_TCL_DATA_CMD_INFO3_TID		GENMASK(7, 4)
    > +#define HAL_TCL_DATA_CMD_INFO3_FLOW_OVERRIDE	BIT(8)
    > +#define HAL_TCL_DATA_CMD_INFO3_PMAC_ID		GENMASK(10, 9)
    > +#define HAL_TCL_DATA_CMD_INFO3_MSDU_COLOR	GENMASK(12, 11)
    > +#define HAL_TCL_DATA_CMD_INFO3_VDEV_ID		GENMASK(31, 24)
    > +
    > +#define HAL_TCL_DATA_CMD_INFO4_SEARCH_INDEX	GENMASK(19, 0)
    > +#define HAL_TCL_DATA_CMD_INFO4_CACHE_SET_NUM	GENMASK(23, 20)
    > +#define HAL_TCL_DATA_CMD_INFO4_IDX_LOOKUP_OVERRIDE	BIT(24)
    > +
    > +#define HAL_TCL_DATA_CMD_INFO5_RING_ID		GENMASK(27, 20)
    > +#define HAL_TCL_DATA_CMD_INFO5_LOOPING_COUNT	GENMASK(31, 28)
    > +
    > +/* Per-bank config for HAL_TCL_SW_CONFIG_BANK_ADDR + 4 * bank_id. */
    > +#define HAL_TX_BANK_CONFIG_EPD			BIT(0)
    > +#define HAL_TX_BANK_CONFIG_ENCAP_TYPE		GENMASK(2, 1)
    > +#define HAL_TX_BANK_CONFIG_ENCRYPT_TYPE		GENMASK(6, 3)
    > +#define HAL_TX_BANK_CONFIG_SRC_BUFFER_SWAP	BIT(7)
    > +#define HAL_TX_BANK_CONFIG_LINK_META_SWAP	BIT(8)
    > +#define HAL_TX_BANK_CONFIG_INDEX_LOOKUP_EN	BIT(9)
    > +#define HAL_TX_BANK_CONFIG_ADDRX_EN		BIT(10)
    > +#define HAL_TX_BANK_CONFIG_ADDRY_EN		BIT(11)
    > +#define HAL_TX_BANK_CONFIG_MESH_EN		GENMASK(13, 12)
    > +#define HAL_TX_BANK_CONFIG_VDEV_ID_CHECK_EN	BIT(14)
    > +#define HAL_TX_BANK_CONFIG_PMAC_ID		GENMASK(16, 15)
    > +#define HAL_TX_BANK_CONFIG_DSCP_TIP_MAP_ID	GENMASK(22, 17)
    >  
    >  enum hal_encrypt_type {
    >  	HAL_ENCRYPT_TYPE_WEP_40,
    > @@ -12381,7 +12390,13 @@ struct hal_rx_desc_qcn9274_compact {
    >  	uint8_t msdu_payload[];
    >  } __packed;
    >  
    > -#define RX_BE_PADDING0_BYTES 8
    > +/*
    > + * Empirically observed on WCN7850 hw2.0 fw 0x110cffff: the FW writes
    > + * mpdu_start_tag at descriptor offset 216 (not 144 as a literal reading
    > + * of upstream Linux/ath12k headers would suggest). The padding between
    > + * msdu_end and mpdu_start_tag is 80 bytes, not 8.
    > + */
    > +#define RX_BE_PADDING0_BYTES 80
    >  #define RX_BE_PADDING1_BYTES 8
    >  
    >  #define HAL_RX_BE_PKT_HDR_TLV_LEN 112
    > @@ -13167,6 +13182,7 @@ enum htt_t2h_msg_type {
    >  	HTT_T2H_MSG_TYPE_PPDU_STATS_IND = 0x1d,
    >  	HTT_T2H_MSG_TYPE_EXT_STATS_CONF = 0x1c,
    >  	HTT_T2H_MSG_TYPE_BKPRESSURE_EVENT_IND = 0x24,
    > +	HTT_T2H_MSG_TYPE_PRIMARY_LINK_PEER_MIGRATE_IND = 0x30,
    >  };
    >  
    >  #define HTT_TARGET_VERSION_MAJOR 3
    > Index: sys/dev/ic/qwzvar.h
    > ===================================================================
    > RCS file: /cvs/src/sys/dev/ic/qwzvar.h,v
    > diff -u -p -u -p -r1.13 qwzvar.h
    > --- sys/dev/ic/qwzvar.h	12 Apr 2026 19:52:23 -0000	1.13
    > +++ sys/dev/ic/qwzvar.h	25 Apr 2026 21:29:58 -0000
    > @@ -141,6 +141,8 @@ struct hal_tx_info {
    >  	uint8_t dscp_tid_tbl_idx;
    >  	bool enable_mesh;
    >  	uint8_t rbm_id;
    > +	uint8_t bank_id;	/* WCN7850/WiFi7: pre-configured TX bank index */
    > +	uint8_t vdev_id;	/* WCN7850/WiFi7: VDEV ID in tcl_data_cmd info3 */
    >  };
    >  
    >  /* TODO: Check if the actual desc macros can be used instead */
    > @@ -281,9 +283,9 @@ struct hal_rx_ops {
    >  #ifdef notyet
    >  	uint8_t (*rx_desc_get_mesh_ctl)(struct hal_rx_desc *desc);
    >  	bool (*rx_desc_get_mpdu_seq_ctl_vld)(struct hal_rx_desc *desc);
    > -	bool (*rx_desc_get_mpdu_fc_valid)(struct hal_rx_desc *desc);
    >  	uint16_t (*rx_desc_get_mpdu_start_seq_no)(struct hal_rx_desc *desc);
    >  #endif
    > +	bool (*rx_desc_get_mpdu_fc_valid)(struct hal_rx_desc *desc);
    >  	uint16_t (*rx_desc_get_msdu_len)(struct hal_rx_desc *desc);
    >  #ifdef notyet
    >  	uint8_t (*rx_desc_get_msdu_sgi)(struct hal_rx_desc *desc);
    > @@ -1103,6 +1105,7 @@ struct ath12k_rx_desc_info {
    >  	uint32_t magic;
    >  	uint8_t in_use		: 1,
    >  	        reserved	: 7;
    > +	struct qwz_rx_msdu rx_msdu;
    >  };
    >  
    >  struct ath12k_tx_desc_info {
    > @@ -1798,6 +1801,7 @@ struct qwz_vif {
    >  	uint16_t tcl_metadata;
    >  	uint8_t hal_addr_search_flags;
    >  	uint8_t search_type;
    > +	int8_t bank_id;	/* WCN7850/WiFi7 TX bank profile id, -1 if unset */
    >  
    >  	struct qwz_softc *sc;
    >  
    > Index: sys/dev/pci/if_qwz_pci.c
    > ===================================================================
    > RCS file: /cvs/src/sys/dev/pci/if_qwz_pci.c,v
    > diff -u -p -u -p -r1.7 if_qwz_pci.c
    > --- sys/dev/pci/if_qwz_pci.c	12 Apr 2026 19:52:23 -0000	1.7
    > +++ sys/dev/pci/if_qwz_pci.c	25 Apr 2026 21:29:58 -0000
    > @@ -1490,9 +1490,10 @@ qwz_pcic_ext_irq_config(struct qwz_softc
    >  
    >  		if (num_irq) {
    >  			int irq_idx = irq_grp->irqs[0];
    > +			int vector = (i % num_vectors) + base_vector;
    >  			pci_intr_handle_t ih;
    >  
    > -			if (pci_intr_map_msivec(pa, irq_idx, &ih) != 0 &&
    > +			if (pci_intr_map_msivec(pa, vector, &ih) != 0 &&
    >  			    pci_intr_map(pa, &ih) != 0) {
    >  				printf("%s: can't map interrupt\n",
    >  				    sc->sc_dev.dv_xname);
    > 
    
    
  • Mark Kettenis:

    qwz: enable WPA2 association on WCN7850