From: Mark Kettenis Subject: Re: qwz: enable WPA2 association on WCN7850 To: Marcus Glocker Cc: tech@openbsd.org, kirill@korins.ky, stsp@stsp.name, mail@patrick-wildt.de Date: Sun, 26 Apr 2026 13:45:10 +0200 > Date: Sat, 25 Apr 2026 23:56:07 +0200 > From: Marcus Glocker > > 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); >