Download raw body.
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(¶ms, 0, sizeof(params));
> -
> - entry_size = qwz_hal_srng_get_entrysize(sc, HAL_TCL_DATA);
> - qwz_hal_srng_get_params(sc, srng, ¶ms);
> - 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], ð->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);
>
qwz: enable WPA2 association on WCN7850