Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
The second batch of qwz backports and fixes
To:
Stefan Sperling <stsp@stsp.name>
Cc:
OpenBSD tech <tech@openbsd.org>
Date:
Mon, 28 Sep 2026 12:06:22 +0200

Download raw body.

Thread
Stefan,

the first batch was commited.

Here the second batch of my fixes.

It contains six backports, one "based on" fix on commit to qwx and a few new
fixes.

In total 11 commits.

Compile tested each independed commits, and after all 11 commits wifi works
on my qwz device.

Ok?

From 3fadc7b97f759a9d04f9affb8c18cf944d5cd8ff Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:55:01 +0200
Subject: [PATCH 01/11] sys/qwz: serialize activation state changes

Backport of sys/dev/ic/qwx.c,v 1.131
---
 sys/dev/ic/qwz.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index 17ef50cc9c0..a8c1e48a1f3 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -24906,13 +24906,12 @@ qwz_activate(struct device *self, int act)
 
 	switch (act) {
 	case DVACT_QUIESCE:
-		if (ifp->if_flags & IFF_RUNNING) {
-			rw_enter_write(&sc->ioctl_rwl);
+		rw_enter_write(&sc->ioctl_rwl);
+		if (ifp->if_flags & IFF_RUNNING)
 			qwz_stop(ifp);
-			rw_exit(&sc->ioctl_rwl);
-		}
 		if (sc->fw_initialized)
 			qwz_core_deinit(sc);
+		rw_exit(&sc->ioctl_rwl);
 		break;
 	case DVACT_RESUME:
 		err = qwz_hal_srng_init(sc);
@@ -24921,12 +24920,14 @@ qwz_activate(struct device *self, int act)
 			    sc->sc_dev.dv_xname);
 		break;
 	case DVACT_WAKEUP:
+		rw_enter_write(&sc->ioctl_rwl);
 		if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP) {
 			err = qwz_init(ifp);
 			if (err)
 				printf("%s: could not initialize hardware\n",
 				    sc->sc_dev.dv_xname);
 		}
+		rw_exit(&sc->ioctl_rwl);
 		break;
 	}
 
-- 
2.55.0


From be717d101afe12e2fe9ac2d0a87e68c1e0ecb733 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:54:58 +0200
Subject: [PATCH 02/11] sys/qwz: protect initialization with splnet

Backport of sys/dev/ic/qwx.c,v 1.137
---
 sys/dev/ic/qwz.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index a8c1e48a1f3..9d735685db9 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -184,6 +184,7 @@ qwz_init(struct ifnet *ifp)
 	int error;
 	struct qwz_softc *sc = ifp->if_softc;
 	struct ieee80211com *ic = &sc->sc_ic;
+	int s = splnet();
 
 	/* Firmware stays running across ifconfig down/up; only re-scan. */
 	if (sc->fw_initialized) {
@@ -196,6 +197,7 @@ qwz_init(struct ifnet *ifp)
 			ifp->if_flags |= IFF_RUNNING;
 			ieee80211_begin_scan(ifp);
 		}
+		splx(s);
 		return 0;
 	}
 
@@ -246,8 +248,10 @@ qwz_init(struct ifnet *ifp)
 	sc->vdev_id_11d_scan = QWZ_11D_INVALID_VDEV_ID;
 
 	error = qwz_core_init(sc);
-	if (error)
+	if (error) {
+		splx(s);
 		return error;
+	}
 
 	memset(&sc->qrtr_server, 0, sizeof(sc->qrtr_server));
 	sc->qrtr_server.node = QRTR_NODE_BCAST;
@@ -258,13 +262,16 @@ qwz_init(struct ifnet *ifp)
 		    SEC_TO_NSEC(5));
 		if (error) {
 			printf("%s: qrtr init timeout\n", sc->sc_dev.dv_xname);
+			splx(s);
 			return error;
 		}
 	}
 
 	error = qwz_qmi_event_server_arrive(sc);
-	if (error)
+	if (error) {
+		splx(s);
 		return error;
+	}
 
 	if (sc->attached) {
 		/* Update MAC in case the upper layers changed it. */
@@ -294,13 +301,16 @@ qwz_init(struct ifnet *ifp)
 		ifp->if_flags |= IFF_RUNNING;
 
 		error = qwz_mac_start(sc);
-		if (error)
+		if (error) {
+			splx(s);
 			return error;
+		}
 
 		ieee80211_begin_scan(ifp);
 	}
 
 	sc->fw_initialized = 1;
+	splx(s);
 	return 0;
 }
 
-- 
2.55.0


From 1a1273c9162f6ae588eed4b9aa74a3e51a12a922 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:55:28 +0200
Subject: [PATCH 03/11] sys/qwz: separate firmware peer lifetime

Backport of sys/dev/ic/qwx.c,v 1.78 and sys/dev/ic/qwxvar.h,v 1.27
---
 sys/dev/ic/qwz.c    | 386 +++++++++++++++++++++-----------------------
 sys/dev/ic/qwzvar.h |  83 +++++-----
 2 files changed, 231 insertions(+), 238 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index 9d735685db9..4682c6634da 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -174,10 +174,42 @@ qwz_node_alloc(struct ieee80211com *ic)
 
 	nq = malloc(sizeof(struct qwz_node), M_DEVBUF, M_NOWAIT | M_ZERO);
 	if (nq != NULL)
-		nq->peer.peer_id = HAL_INVALID_PEERID;
+		nq->peer_id = HAL_INVALID_PEERID;
 	return (struct ieee80211_node *)nq;
 }
 
+void
+qwz_node_clear_peer_id(struct qwz_softc *sc, struct ath12k_peer *peer)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ieee80211_node *ni = ic->ic_bss;
+	struct qwz_node *nq = (struct qwz_node *)ni;
+
+	if (nq != NULL && nq->peer_id == peer->peer_id)
+		nq->peer_id = HAL_INVALID_PEERID;
+	RBT_FOREACH(ni, ieee80211_tree, &ic->ic_tree) {
+		nq = (struct qwz_node *)ni;
+		if (nq->peer_id == peer->peer_id)
+			nq->peer_id = HAL_INVALID_PEERID;
+	}
+	if (sc->bss_peer_id == peer->peer_id)
+		sc->bss_peer_id = HAL_INVALID_PEERID;
+}
+
+void
+qwz_free_peers(struct qwz_softc *sc)
+{
+	struct ath12k_peer *peer;
+
+	while ((peer = TAILQ_FIRST(&sc->peers)) != NULL) {
+		TAILQ_REMOVE(&sc->peers, peer, entry);
+		qwz_node_clear_peer_id(sc, peer);
+		free(peer, M_DEVBUF, sizeof(*peer));
+		sc->num_peers--;
+	}
+	KASSERT(sc->num_peers == 0);
+}
+
 int
 qwz_init(struct ifnet *ifp)
 {
@@ -188,6 +220,13 @@ qwz_init(struct ifnet *ifp)
 
 	/* Firmware stays running across ifconfig down/up; only re-scan. */
 	if (sc->fw_initialized) {
+		if (!TAILQ_EMPTY(&sc->peers) || sc->num_started_vdevs > 0) {
+			error = qwz_deauth(sc);
+			if (error) {
+				splx(s);
+				return error;
+			}
+		}
 		ic->ic_state = IEEE80211_S_INIT;
 		sc->ns_nstate = IEEE80211_S_INIT;
 		sc->scan.state = ATH12K_SCAN_IDLE;
@@ -728,13 +767,17 @@ qwz_add_sta_key(struct qwz_softc *sc, struct ieee80211_node *ni,
 {
 	struct ieee80211com *ic = &sc->sc_ic;
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	struct qwz_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
 	int ret = 0;
 	uint32_t flags = 0;
 	const int want_keymask = (QWZ_NODE_FLAG_HAVE_PAIRWISE_KEY |
 	    QWZ_NODE_FLAG_HAVE_GROUP_KEY);
 
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return EINVAL;
+
 	/*
 	 * Flush the fragments cache during key (re)install to
 	 * ensure all frags in the new frag list belong to the same key.
@@ -10341,6 +10384,7 @@ void
 qwz_peer_delete_resp_event(struct qwz_softc *sc, struct mbuf *m)
 {
 	struct wmi_peer_delete_resp_event peer_del_resp;
+	struct ath12k_peer *peer;
 
 	if (qwz_pull_peer_del_resp_ev(sc, m, &peer_del_resp) != 0) {
 		printf("%s: failed to extract peer delete resp",
@@ -10348,6 +10392,11 @@ qwz_peer_delete_resp_event(struct qwz_softc *sc, struct mbuf *m)
 		return;
 	}
 
+	peer = qwz_peer_find_by_addr(sc, peer_del_resp.peer_macaddr.addr);
+	if (peer == NULL || peer->vdev_id != peer_del_resp.vdev_id ||
+	    !peer->delete_pending)
+		return;
+	peer->delete_done = 1;
 	sc->peer_delete_done = 1;
 	wakeup(&sc->peer_delete_done);
 
@@ -12876,49 +12925,30 @@ qwz_peer_map_event(struct qwz_softc *sc, uint8_t vdev_id, uint16_t peer_id,
 	struct ieee80211_node *ni;
 	struct qwz_node *nq;
 	struct ath12k_peer *peer;
-#ifdef notyet
-	spin_lock_bh(&ab->base_lock);
-#endif
-	/*
-	 * For STA mode the only peer is the AP, and the per-peer state
-	 * we care about (FW-assigned ast_hash / hw_peer_id) is consumed
-	 * later via ic->ic_bss in qwz_peer_create.  ieee80211_find_node
-	 * may return a DIFFERENT node from the RB-tree (a stale scan
-	 * entry for the same BSSID), and updating that wrong node
-	 * leaves ic_bss's qwz_peer with ast_hash=0 / hw_peer_id=0
-	 * forever -- causing the FW to AST-look-up slot 0 on the first
-	 * protected post-AUTHORIZE frame and dlpager-fault.  Prefer
-	 * ic_bss whenever the MAC matches.
-	 */
+	struct qwz_vif *arvif = TAILQ_FIRST(&sc->vif_list);
+
+	peer = qwz_peer_find_by_addr(sc, mac_addr);
+	if (peer == NULL || peer->vdev_id != vdev_id)
+		return;
+
+	peer->peer_id = peer_id;
+	peer->is_mapped = 1;
+	peer->ast_hash = ast_hash;
+	peer->hw_peer_id = hw_peer_id;
 	if (ic->ic_opmode == IEEE80211_M_STA && ic->ic_bss != NULL &&
 	    IEEE80211_ADDR_EQ(ic->ic_bss->ni_macaddr, mac_addr))
 		ni = ic->ic_bss;
 	else
 		ni = ieee80211_find_node(ic, mac_addr);
-	if (ni == NULL) {
-		printf("%s: peer_map: no node for %s\n", sc->sc_dev.dv_xname,
-		    ether_sprintf(mac_addr));
-		return;
+	if (ni != NULL) {
+		nq = (struct qwz_node *)ni;
+		nq->peer_id = peer_id;
 	}
-	nq = (struct qwz_node *)ni;
-	peer = &nq->peer;
-
-	peer->vdev_id = vdev_id;
-	peer->peer_id = peer_id;
-	peer->ast_hash = ast_hash;
-	peer->hw_peer_id = hw_peer_id;
-#if 0
-	ether_addr_copy(peer->addr, mac_addr);
-	list_add(&peer->list, &ab->peers);
-#endif
-	/* Propagate FW-assigned AST values to STA arvif for qwz_dp_tx(). */
-	{
-		struct qwz_vif *arvif = TAILQ_FIRST(&sc->vif_list);
-		if (ic->ic_opmode == IEEE80211_M_STA &&
-		    arvif != NULL && arvif->vdev_id == vdev_id) {
-			arvif->ast_hash = ast_hash;
-			arvif->ast_idx = hw_peer_id;
-		}
+	if (ic->ic_opmode == IEEE80211_M_STA && arvif != NULL &&
+	    arvif->vdev_id == vdev_id) {
+		arvif->ast_hash = ast_hash;
+		arvif->ast_idx = hw_peer_id;
+		sc->bss_peer_id = peer_id;
 	}
 
 	sc->peer_mapped = 1;
@@ -12926,61 +12956,47 @@ qwz_peer_map_event(struct qwz_softc *sc, uint8_t vdev_id, uint16_t peer_id,
 
 	DNPRINTF(QWZ_D_HTT, "%s: peer map vdev %d peer %s id %d\n",
 	    __func__, vdev_id, ether_sprintf(mac_addr), peer_id);
-#ifdef notyet
-	spin_unlock_bh(&ab->base_lock);
-#endif
 }
 
-struct ieee80211_node *
+struct ath12k_peer *
 qwz_peer_find_by_id(struct qwz_softc *sc, uint16_t peer_id)
 {
-	struct ieee80211com *ic = &sc->sc_ic;
-	struct ieee80211_node *ni = NULL;
-	int s;
+	struct ath12k_peer *peer;
 
-	s = splnet();
-	RBT_FOREACH(ni, ieee80211_tree, &ic->ic_tree) {
-		struct qwz_node *nq = (struct qwz_node *)ni;
-		if (nq->peer.peer_id == peer_id)
-			break;
+	TAILQ_FOREACH(peer, &sc->peers, entry) {
+		if (peer->peer_id == peer_id)
+			return peer;
 	}
-	splx(s);
+	return NULL;
+}
 
-	return ni;
+struct ath12k_peer *
+qwz_peer_find_by_addr(struct qwz_softc *sc, const uint8_t *addr)
+{
+	struct ath12k_peer *peer;
+
+	TAILQ_FOREACH(peer, &sc->peers, entry) {
+		if (IEEE80211_ADDR_EQ(peer->addr, addr))
+			return peer;
+	}
+	return NULL;
 }
 
 void
 qwz_peer_unmap_event(struct qwz_softc *sc, uint16_t peer_id)
 {
-	struct ieee80211_node *ni;
-#ifdef notyet
-	spin_lock_bh(&ab->base_lock);
-#endif
-	ni = qwz_peer_find_by_id(sc, peer_id);
-	if (ni) {
-		DNPRINTF(QWZ_D_HTT, "%s: peer unmap peer %s id %d\n",
-		    __func__, ether_sprintf(ni->ni_macaddr), peer_id);
-	} else {
-		/*
-		 * The node may already have been removed from ic_tree
-		 * by ieee80211 cleanup before this event arrived (e.g.
-		 * during a soft ifconfig down/up cycle).  The unmap
-		 * event is FW's confirmation that the peer is gone, so
-		 * still signal the waiter in qwz_peer_delete().
-		 */
-		DNPRINTF(QWZ_D_HTT, "%s: peer unmap for unknown id %d\n",
-		    __func__, peer_id);
-	}
+	struct ath12k_peer *peer;
 
-#if 0
-	list_del(&peer->list);
-	kfree(peer);
-#endif
+	peer = qwz_peer_find_by_id(sc, peer_id);
+	if (peer == NULL)
+		return;
+
+	DNPRINTF(QWZ_D_HTT, "%s: peer unmap peer %s id %d\n",
+	    __func__, ether_sprintf(peer->addr), peer_id);
+
+	peer->is_mapped = 0;
 	sc->peer_mapped = 1;
 	wakeup(&sc->peer_mapped);
-#ifdef notyet
-	spin_unlock_bh(&ab->base_lock);
-#endif
 }
 
 void
@@ -18749,6 +18765,7 @@ qwz_core_deinit(struct qwz_softc *sc)
 	mutex_unlock(&ab->core_lock);
 #endif
 	sc->ops.power_down(sc);
+	qwz_free_peers(sc);
 #if 0
 	ath12k_mac_destroy(ab);
 	ath12k_debugfs_soc_destroy(ab);
@@ -21639,6 +21656,9 @@ qwz_mac_vdev_stop(struct qwz_softc *sc, struct qwz_vif *arvif, int pdev_id)
 #if 0
 	reinit_completion(&ar->vdev_setup_done);
 #endif
+	if (!arvif->is_started)
+		return 0;
+
 	sc->vdev_setup_done = 0;
 	ret = qwz_wmi_vdev_stop(sc, arvif->vdev_id, pdev_id);
 	if (ret) {
@@ -21654,6 +21674,7 @@ qwz_mac_vdev_stop(struct qwz_softc *sc, struct qwz_vif *arvif, int pdev_id)
 		return ret;
 	}
 
+	arvif->is_started = 0;
 	if (sc->num_started_vdevs > 0)
 		sc->num_started_vdevs--;
 
@@ -21778,6 +21799,7 @@ qwz_mac_vdev_start_restart(struct qwz_softc *sc, struct qwz_vif *arvif,
 
 	if (!restart)
 		sc->num_started_vdevs++;
+	arvif->is_started = 1;
 
 	DNPRINTF(QWZ_D_MAC, "%s: vdev %d started\n", __func__, arvif->vdev_id);
 
@@ -22365,21 +22387,24 @@ qwz_mac_get_rate_hw_value(struct ieee80211com *ic,
 
 int
 qwz_peer_delete(struct qwz_softc *sc, uint32_t vdev_id, uint8_t pdev_id,
-    uint8_t *addr)
+    struct ath12k_peer *peer)
 {
 	int ret;
 
-	sc->peer_mapped = 0;
-	sc->peer_delete_done = 0;
-
-	ret = qwz_wmi_send_peer_delete_cmd(sc, addr, vdev_id, pdev_id);
-	if (ret) {
-		printf("%s: failed to delete peer vdev_id %d addr %s ret %d\n",
-		    sc->sc_dev.dv_xname, vdev_id, ether_sprintf(addr), ret);
-		return ret;
+	if (!peer->delete_pending) {
+		peer->delete_pending = 1;
+		ret = qwz_wmi_send_peer_delete_cmd(sc, peer->addr, vdev_id,
+		    pdev_id);
+		if (ret) {
+			peer->delete_pending = 0;
+			printf("%s: failed to delete peer vdev_id %d addr %s "
+			    "ret %d\n", sc->sc_dev.dv_xname, vdev_id,
+			    ether_sprintf(peer->addr), ret);
+			return ret;
+		}
 	}
 
-	while (!sc->peer_mapped) {
+	while (peer->is_mapped) {
 		ret = tsleep_nsec(&sc->peer_mapped, 0, "qwzpeer",
 		    SEC_TO_NSEC(3));
 		if (ret) {
@@ -22389,7 +22414,7 @@ qwz_peer_delete(struct qwz_softc *sc, uint32_t vdev_id, uint8_t pdev_id,
 		}
 	}
 
-	while (!sc->peer_delete_done) {
+	while (!peer->delete_done) {
 		ret = tsleep_nsec(&sc->peer_delete_done, 0, "qwzpeerd",
 		    SEC_TO_NSEC(3));
 		if (ret) {
@@ -22399,6 +22424,9 @@ qwz_peer_delete(struct qwz_softc *sc, uint32_t vdev_id, uint8_t pdev_id,
 		}
 	}
 
+	TAILQ_REMOVE(&sc->peers, peer, entry);
+	qwz_node_clear_peer_id(sc, peer);
+	free(peer, M_DEVBUF, sizeof(*peer));
 	sc->num_peers--;
 	return 0;
 }
@@ -22407,123 +22435,49 @@ int
 qwz_peer_create(struct qwz_softc *sc, struct qwz_vif *arvif, uint8_t pdev_id,
     struct ieee80211_node *ni, struct peer_create_params *param)
 {
-	struct ieee80211com *ic = &sc->sc_ic;
 	struct qwz_node *nq = (struct qwz_node *)ni;
 	struct ath12k_peer *peer;
 	int ret;
-#ifdef notyet
-	lockdep_assert_held(&ar->conf_mutex);
-#endif
-	if (sc->num_peers > (qwz_core_get_max_peers_per_radio(sc) - 1)) {
-		DPRINTF("%s: failed to create peer due to insufficient "
-		    "peer entry resource in firmware\n", __func__);
+
+	if (sc->num_peers >= qwz_core_get_max_peers_per_radio(sc))
 		return ENOBUFS;
-	}
-#ifdef notyet
-	mutex_lock(&ar->ab->tbl_mtx_lock);
-	spin_lock_bh(&ar->ab->base_lock);
-#endif
-	peer = &nq->peer;
-	/*
-	 * Reset stale peer state from any prior attempt.  After a
-	 * fatal_firmware_error the FW peer table is wiped but the
-	 * host-side qwz_node persists with peer->peer_id and
-	 * peer->vdev_id from the last attempt.  Without this reset
-	 * the subsequent peer_create returns EINVAL and we get stuck
-	 * in a recovery loop (peer_create fail -> wlan mode off fail
-	 * -> mhi_start -> repeat).  The stale ast_hash / hw_peer_id
-	 * are also reset because they will be re-populated by the
-	 * next peer_map_event.
-	 */
+	if (!TAILQ_EMPTY(&sc->peers))
+		return EBUSY;
+
+	peer = malloc(sizeof(*peer), M_DEVBUF, M_ZERO | M_NOWAIT);
+	if (peer == NULL)
+		return ENOMEM;
 	peer->peer_id = HAL_INVALID_PEERID;
-	peer->vdev_id = 0;
-	peer->ast_hash = 0;
-	peer->hw_peer_id = 0;
-#ifdef notyet
-	spin_unlock_bh(&ar->ab->base_lock);
-	mutex_unlock(&ar->ab->tbl_mtx_lock);
-#endif
+	peer->vdev_id = param->vdev_id;
+	peer->pdev_id = pdev_id;
+	IEEE80211_ADDR_COPY(peer->addr, param->peer_addr);
+	TAILQ_INSERT_TAIL(&sc->peers, peer, entry);
+	sc->num_peers++;
 	sc->peer_mapped = 0;
 
 	ret = qwz_wmi_send_peer_create_cmd(sc, pdev_id, param);
 	if (ret) {
+		TAILQ_REMOVE(&sc->peers, peer, entry);
+		sc->num_peers--;
+		free(peer, M_DEVBUF, sizeof(*peer));
 		printf("%s: failed to send peer create vdev_id %d ret %d\n",
 		    sc->sc_dev.dv_xname, param->vdev_id, ret);
 		return ret;
 	}
 
-	while (!sc->peer_mapped) {
+	while (peer->peer_id == HAL_INVALID_PEERID) {
 		ret = tsleep_nsec(&sc->peer_mapped, 0, "qwzpeer",
 		    SEC_TO_NSEC(3));
 		if (ret) {
+			/* Firmware may still own this peer after a timeout. */
 			printf("%s: peer create command timeout\n",
 			    sc->sc_dev.dv_xname);
 			return ret;
 		}
 	}
 
-#ifdef notyet
-	mutex_lock(&ar->ab->tbl_mtx_lock);
-	spin_lock_bh(&ar->ab->base_lock);
-#endif
-#if 0
-	peer = ath12k_peer_find(ar->ab, param->vdev_id, param->peer_addr);
-	if (!peer) {
-		spin_unlock_bh(&ar->ab->base_lock);
-		mutex_unlock(&ar->ab->tbl_mtx_lock);
-		ath12k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n",
-			    param->peer_addr, param->vdev_id);
-
-		ret = -ENOENT;
-		goto cleanup;
-	}
-
-	ret = ath12k_peer_rhash_add(ar->ab, peer);
-	if (ret) {
-		spin_unlock_bh(&ar->ab->base_lock);
-		mutex_unlock(&ar->ab->tbl_mtx_lock);
-		goto cleanup;
-	}
-#endif
-	peer->pdev_id = pdev_id;
-#if 0
-	peer->sta = sta;
-#endif
-	if (ic->ic_opmode == IEEE80211_M_STA) {
-		arvif->ast_hash = peer->ast_hash;
-		arvif->ast_idx = peer->hw_peer_id;
-	}
-#if 0
-	peer->sec_type = HAL_ENCRYPT_TYPE_OPEN;
-	peer->sec_type_grp = HAL_ENCRYPT_TYPE_OPEN;
-
-	if (sta) {
-		struct ath12k_sta *arsta = (struct ath12k_sta *)sta->drv_priv;
-		arsta->tcl_metadata |= FIELD_PREP(HTT_TCL_META_DATA_TYPE, 0) |
-				       FIELD_PREP(HTT_TCL_META_DATA_PEER_ID,
-						  peer->peer_id);
-
-		/* set HTT extension valid bit to 0 by default */
-		arsta->tcl_metadata &= ~HTT_TCL_META_DATA_VALID_HTT;
-	}
-#endif
-	sc->num_peers++;
-#ifdef notyet
-	spin_unlock_bh(&ar->ab->base_lock);
-	mutex_unlock(&ar->ab->tbl_mtx_lock);
-#endif
+	nq->peer_id = peer->peer_id;
 	return 0;
-#if 0
-cleanup:
-	int fbret = qwz_peer_delete(sc, param->vdev_id, param->peer_addr);
-	if (fbret) {
-		printf("%s: failed peer %s delete vdev_id %d fallback ret %d\n",
-		    sc->sc_dev.dv_xname, ether_sprintf(ni->ni_macaddr),
-		    param->vdev_id, fbret);
-	}
-
-	return ret;
-#endif
 }
 
 int
@@ -22923,8 +22877,13 @@ qwz_dp_rx_tid_mem_free(struct qwz_softc *sc, struct ieee80211_node *ni,
     int vdev_id, uint8_t tid)
 {
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	struct dp_rx_tid *rx_tid;
+
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return;
+
 #ifdef notyet
 	spin_lock_bh(&ab->base_lock);
 #endif
@@ -22949,12 +22908,17 @@ qwz_peer_rx_tid_setup(struct qwz_softc *sc, struct ieee80211_node *ni,
 {
 	struct qwz_dp *dp = &sc->dp;
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	struct dp_rx_tid *rx_tid;
 	uint32_t hw_desc_sz;
 	void *vaddr;
 	uint64_t paddr;
 	int ret;
+
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return ENOENT;
+
 #ifdef notyet
 	spin_lock_bh(&ab->base_lock);
 #endif
@@ -23041,13 +23005,18 @@ qwz_peer_rx_frag_setup(struct qwz_softc *sc, struct ieee80211_node *ni,
     int vdev_id)
 {
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	struct dp_rx_tid *rx_tid;
 	int i;
+
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return ENOENT;
+
 #ifdef notyet
 	spin_lock_bh(&ab->base_lock);
 #endif
-	for (i = 0; i <= nitems(peer->rx_tid); i++) {
+	for (i = 0; i < nitems(peer->rx_tid); i++) {
 		rx_tid = &peer->rx_tid[i];
 #if 0
 		rx_tid->ab = ab;
@@ -23068,10 +23037,14 @@ qwz_dp_peer_setup(struct qwz_softc *sc, int vdev_id, int pdev_id,
     struct ieee80211_node *ni)
 {
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	uint32_t reo_dest;
 	int ret = 0, tid;
 
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return ENOENT;
+
 	/* reo_dest ring id starts from 1 unlike mac_id which starts from 0 */
 	reo_dest = sc->pdev_dp.mac_id + 1;
 	ret = qwz_wmi_set_peer_param(sc, ni->ni_macaddr, vdev_id, pdev_id,
@@ -23131,11 +23104,15 @@ qwz_dp_peer_rx_pn_replay_config(struct qwz_softc *sc, struct qwz_vif *arvif,
 {
 	struct ath12k_hal_reo_cmd cmd = {0};
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	struct dp_rx_tid *rx_tid;
 	uint8_t tid;
 	int ret = 0;
 
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return ENOENT;
+
 	/*
 	 * NOTE: Enable PN/TSC replay check offload only for unicast frames.
 	 * We use net80211 PN/TSC replay check functionality for bcast/mcast
@@ -23544,15 +23521,13 @@ qwz_dp_tx(struct qwz_softc *sc, struct qwz_vif *arvif, uint8_t pdev_id,
 
 int
 qwz_mac_station_remove(struct qwz_softc *sc, struct qwz_vif *arvif,
-    uint8_t pdev_id, struct ieee80211_node *ni)
+    uint8_t pdev_id, struct ath12k_peer *peer)
 {
-	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
 	int ret;
 
 	qwz_peer_rx_tid_cleanup(sc, peer);
 
-	ret = qwz_peer_delete(sc, arvif->vdev_id, pdev_id, ni->ni_macaddr);
+	ret = qwz_peer_delete(sc, arvif->vdev_id, pdev_id, peer);
 	if (ret) {
 		printf("%s: unable to delete BSS peer: %d\n",
 		   sc->sc_dev.dv_xname, ret);
@@ -23567,6 +23542,7 @@ qwz_mac_station_add(struct qwz_softc *sc, struct qwz_vif *arvif,
     uint8_t pdev_id, struct ieee80211_node *ni)
 {
 	struct peer_create_params peer_param;
+	struct ath12k_peer *peer;
 	int ret;
 #ifdef notyet
 	lockdep_assert_held(&ar->conf_mutex);
@@ -23597,7 +23573,9 @@ qwz_mac_station_add(struct qwz_softc *sc, struct qwz_vif *arvif,
 	return 0;
 
 free_peer:
-	qwz_peer_delete(sc, arvif->vdev_id, pdev_id, ni->ni_macaddr);
+	peer = qwz_peer_find_by_addr(sc, ni->ni_macaddr);
+	if (peer != NULL)
+		qwz_peer_delete(sc, arvif->vdev_id, pdev_id, peer);
 	return ret;
 }
 
@@ -24246,10 +24224,9 @@ qwz_auth(struct qwz_softc *sc)
 int
 qwz_deauth(struct qwz_softc *sc)
 {
-	struct ieee80211com *ic = &sc->sc_ic;
-	struct ieee80211_node *ni = ic->ic_bss;
 	struct qwz_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
 	uint8_t pdev_id = 0; /* TODO: derive pdev ID somehow? */
+	struct ath12k_peer *peer = TAILQ_FIRST(&sc->peers);
 	int ret;
 
 	ret = qwz_mac_vdev_stop(sc, arvif, pdev_id);
@@ -24259,7 +24236,10 @@ qwz_deauth(struct qwz_softc *sc)
 		return ret;
 	}
 
-	ret = qwz_wmi_set_peer_param(sc, ni->ni_macaddr, arvif->vdev_id,
+	if (peer == NULL)
+		return 0;
+
+	ret = qwz_wmi_set_peer_param(sc, peer->addr, arvif->vdev_id,
 	    pdev_id, WMI_PEER_AUTHORIZE, 0);
 	if (ret) {
 		printf("%s: unable to deauthorize BSS peer: %d\n",
@@ -24267,12 +24247,12 @@ qwz_deauth(struct qwz_softc *sc)
 		return ret;
 	}
 
-	ret = qwz_mac_station_remove(sc, arvif, pdev_id, ni);
+	ret = qwz_mac_station_remove(sc, arvif, pdev_id, peer);
 	if (ret)
 		return ret;
 
 	DNPRINTF(QWZ_D_MAC, "%s: disassociated from bssid %s aid %d\n",
-	    __func__, ether_sprintf(ni->ni_bssid), arvif->aid);
+	    __func__, ether_sprintf(arvif->bssid), arvif->aid);
 
 	return 0;
 }
@@ -24562,10 +24542,14 @@ qwz_rx_agg_stop(struct qwz_softc *sc, struct ieee80211_node *ni, uint8_t tid)
 	struct qwz_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
 	uint8_t pdev_id = 0; /* XXX derive pdev ID somehow */
 	struct qwz_node *nq = (struct qwz_node *)ni;
-	struct ath12k_peer *peer = &nq->peer;
+	struct ath12k_peer *peer;
 	uint64_t paddr;
 	int ret;
 
+	peer = qwz_peer_find_by_id(sc, nq->peer_id);
+	if (peer == NULL)
+		return;
+
 	if (peer->peer_id == HAL_INVALID_PEERID)
 		return;
 
@@ -24829,6 +24813,8 @@ qwz_attach(struct qwz_softc *sc)
 		sc->pdevs[i].sc = sc;
 
 	TAILQ_INIT(&sc->vif_list);
+	TAILQ_INIT(&sc->peers);
+	sc->bss_peer_id = HAL_INVALID_PEERID;
 
 	error = qwz_init(ifp);
 	if (error)
diff --git a/sys/dev/ic/qwzvar.h b/sys/dev/ic/qwzvar.h
index 1a9056bf9cb..0ee994b842f 100644
--- a/sys/dev/ic/qwzvar.h
+++ b/sys/dev/ic/qwzvar.h
@@ -1915,6 +1915,45 @@ struct qwz_ba_task_data {
 	uint32_t		stop_tidmask;
 };
 
+struct ath12k_peer {
+	TAILQ_ENTRY(ath12k_peer) entry;
+#if 0
+	struct ieee80211_sta *sta;
+#endif
+	int vdev_id;
+	uint8_t addr[IEEE80211_ADDR_LEN];
+	int peer_id;
+	uint16_t ast_hash;
+	uint8_t pdev_id;
+	uint16_t hw_peer_id;
+	int is_mapped;
+	int delete_pending;
+	int delete_done;
+#if 0
+	/* protected by ab->data_lock */
+	struct ieee80211_key_conf *keys[WMI_MAX_KEY_INDEX + 1];
+#endif
+	struct dp_rx_tid rx_tid[IEEE80211_NUM_TID + 1];
+#if 0
+	/* peer id based rhashtable list pointer */
+	struct rhash_head rhash_id;
+	/* peer addr based rhashtable list pointer */
+	struct rhash_head rhash_addr;
+
+	/* Info used in MMIC verification of
+	 * RX fragments
+	 */
+	struct crypto_shash *tfm_mmic;
+	u8 mcast_keyidx;
+	u8 ucast_keyidx;
+	u16 sec_type;
+	u16 sec_type_grp;
+	bool is_authorized;
+	bool dp_setup_done;
+#endif
+};
+TAILQ_HEAD(qwz_peer_list, ath12k_peer);
+
 struct qwz_softc {
 	struct device			sc_dev;
 	struct ieee80211com		sc_ic;
@@ -2020,7 +2059,9 @@ struct qwz_softc {
 	int				num_started_vdevs;
 	uint32_t			allocated_vdev_map;
 	uint32_t			free_vdev_map;
+	struct qwz_peer_list		peers;
 	int				num_peers;
+	int				bss_peer_id;
 	int				peer_mapped;
 	int				peer_delete_done;
 	int				vdev_setup_done;
@@ -2120,51 +2161,17 @@ void	qwz_init_task(void *);
 int	qwz_newstate(struct ieee80211com *, enum ieee80211_state, int);
 void	qwz_newstate_task(void *);
 
-struct ath12k_peer {
-#if 0
-	struct list_head list;
-	struct ieee80211_sta *sta;
-#endif
-	int vdev_id;
-#if 0
-	u8 addr[ETH_ALEN];
-#endif
-	int peer_id;
-	uint16_t ast_hash;
-	uint8_t pdev_id;
-	uint16_t hw_peer_id;
-#if 0
-	/* protected by ab->data_lock */
-	struct ieee80211_key_conf *keys[WMI_MAX_KEY_INDEX + 1];
-#endif
-	struct dp_rx_tid rx_tid[IEEE80211_NUM_TID + 1];
-#if 0
-	/* peer id based rhashtable list pointer */
-	struct rhash_head rhash_id;
-	/* peer addr based rhashtable list pointer */
-	struct rhash_head rhash_addr;
-
-	/* Info used in MMIC verification of
-	 * RX fragments
-	 */
-	struct crypto_shash *tfm_mmic;
-	u8 mcast_keyidx;
-	u8 ucast_keyidx;
-	u16 sec_type;
-	u16 sec_type_grp;
-	bool is_authorized;
-	bool dp_setup_done;
-#endif
-};
-
 struct qwz_node {
 	struct ieee80211_node ni;
-	struct ath12k_peer peer;
+	uint16_t peer_id;
 	unsigned int flags;
 #define QWZ_NODE_FLAG_HAVE_PAIRWISE_KEY	0x01
 #define QWZ_NODE_FLAG_HAVE_GROUP_KEY	0x02
 };
 
+struct ath12k_peer *qwz_peer_find_by_id(struct qwz_softc *, uint16_t);
+struct ath12k_peer *qwz_peer_find_by_addr(struct qwz_softc *, const uint8_t *);
+
 struct ieee80211_node *qwz_node_alloc(struct ieee80211com *);
 int	qwz_set_key(struct ieee80211com *, struct ieee80211_node *,
     struct ieee80211_key *);
-- 
2.55.0


From aeaba2d3e2a123be56d583165aa8308f1da7a6b1 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:56:27 +0200
Subject: [PATCH 04/11] sys/qwz: reject invalid firmware peer IDs

Reject invalid peer IDs so an unassociated node cannot match a
pending firmware peer. Peer-map events resolve pending peers by
address.
---
 sys/dev/ic/qwz.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index 4682c6634da..4209e58ab5b 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -12963,6 +12963,9 @@ qwz_peer_find_by_id(struct qwz_softc *sc, uint16_t peer_id)
 {
 	struct ath12k_peer *peer;
 
+	if (peer_id == HAL_INVALID_PEERID)
+		return NULL;
+
 	TAILQ_FOREACH(peer, &sc->peers, entry) {
 		if (peer->peer_id == peer_id)
 			return peer;
-- 
2.55.0


From 8636d1b7d6412861f72d8c9ebfe9064b3337e5c8 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:57:24 +0200
Subject: [PATCH 05/11] sys/qwz: wait for confirmed peer release

Wait for both an explicit peer-unmap event and the delete response
before releasing a peer. This also covers a creation that timed out
before its mapping event arrived.
---
 sys/dev/ic/qwz.c    | 24 +++++++-----------------
 sys/dev/ic/qwzvar.h |  3 +--
 2 files changed, 8 insertions(+), 19 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index 4209e58ab5b..ca25046f6e3 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -10397,8 +10397,7 @@ qwz_peer_delete_resp_event(struct qwz_softc *sc, struct mbuf *m)
 	    !peer->delete_pending)
 		return;
 	peer->delete_done = 1;
-	sc->peer_delete_done = 1;
-	wakeup(&sc->peer_delete_done);
+	wakeup(&peer->delete_done);
 
 	DNPRINTF(QWZ_D_WMI, "%s: peer delete resp for vdev id %d addr %s\n",
 	    __func__, peer_del_resp.vdev_id,
@@ -12932,7 +12931,7 @@ qwz_peer_map_event(struct qwz_softc *sc, uint8_t vdev_id, uint16_t peer_id,
 		return;
 
 	peer->peer_id = peer_id;
-	peer->is_mapped = 1;
+	peer->unmapped = 0;
 	peer->ast_hash = ast_hash;
 	peer->hw_peer_id = hw_peer_id;
 	if (ic->ic_opmode == IEEE80211_M_STA && ic->ic_bss != NULL &&
@@ -12997,7 +12996,8 @@ qwz_peer_unmap_event(struct qwz_softc *sc, uint16_t peer_id)
 	DNPRINTF(QWZ_D_HTT, "%s: peer unmap peer %s id %d\n",
 	    __func__, ether_sprintf(peer->addr), peer_id);
 
-	peer->is_mapped = 0;
+	peer->unmapped = 1;
+	wakeup(&peer->delete_done);
 	sc->peer_mapped = 1;
 	wakeup(&sc->peer_mapped);
 }
@@ -22407,21 +22407,11 @@ qwz_peer_delete(struct qwz_softc *sc, uint32_t vdev_id, uint8_t pdev_id,
 		}
 	}
 
-	while (peer->is_mapped) {
-		ret = tsleep_nsec(&sc->peer_mapped, 0, "qwzpeer",
-		    SEC_TO_NSEC(3));
-		if (ret) {
-			printf("%s: peer delete unmap timeout\n",
-			    sc->sc_dev.dv_xname);
-			return ret;
-		}
-	}
-
-	while (!peer->delete_done) {
-		ret = tsleep_nsec(&sc->peer_delete_done, 0, "qwzpeerd",
+	while (!peer->unmapped || !peer->delete_done) {
+		ret = tsleep_nsec(&peer->delete_done, 0, "qwzpeerd",
 		    SEC_TO_NSEC(3));
 		if (ret) {
-			printf("%s: peer delete command timeout\n",
+			printf("%s: peer delete confirmation timeout\n",
 			    sc->sc_dev.dv_xname);
 			return ret;
 		}
diff --git a/sys/dev/ic/qwzvar.h b/sys/dev/ic/qwzvar.h
index 0ee994b842f..d60edfedaca 100644
--- a/sys/dev/ic/qwzvar.h
+++ b/sys/dev/ic/qwzvar.h
@@ -1926,7 +1926,7 @@ struct ath12k_peer {
 	uint16_t ast_hash;
 	uint8_t pdev_id;
 	uint16_t hw_peer_id;
-	int is_mapped;
+	int unmapped;
 	int delete_pending;
 	int delete_done;
 #if 0
@@ -2063,7 +2063,6 @@ struct qwz_softc {
 	int				num_peers;
 	int				bss_peer_id;
 	int				peer_mapped;
-	int				peer_delete_done;
 	int				vdev_setup_done;
 	int				peer_assoc_done;
 
-- 
2.55.0


From 8153d25b8216a48318229d388e5af1b7ac35816c Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:55:43 +0200
Subject: [PATCH 06/11] sys/qwz: reuse RDDM buffers

Based on sys/dev/pci/if_qwx_pci.c,v 1.31

Reuse the RDDM data and vector buffers on repeated MHI starts to avoid
leaking their allocations. Reprogram the BHIE RX vector after startup
clears its registers; returning early would leave them unset.
---
 sys/dev/pci/if_qwz_pci.c | 28 ++++++++++++++++------------
 1 file changed, 16 insertions(+), 12 deletions(-)

diff --git a/sys/dev/pci/if_qwz_pci.c b/sys/dev/pci/if_qwz_pci.c
index 73b73b07a0e..8b62d533f71 100644
--- a/sys/dev/pci/if_qwz_pci.c
+++ b/sys/dev/pci/if_qwz_pci.c
@@ -3388,20 +3388,24 @@ qwz_rddm_prepare(struct qwz_pci_softc *psc)
 		return;
 	}
 
-	data_adm = qwz_dmamem_alloc(sc->sc_dmat, len, 0);
+	vec_size = nseg * sizeof(*vec);
+	data_adm = psc->rddm_data;
+	vec_adm = psc->rddm_vec;
 	if (data_adm == NULL) {
-		printf("%s: could not allocate BHIE DMA data buffer\n",
-		    sc->sc_dev.dv_xname);
-		return;
-	}
+		data_adm = qwz_dmamem_alloc(sc->sc_dmat, len, 0);
+		if (data_adm == NULL) {
+			printf("%s: could not allocate BHIE DMA data buffer\n",
+			    sc->sc_dev.dv_xname);
+			return;
+		}
 
-	vec_size = nseg * sizeof(*vec);
-	vec_adm = qwz_dmamem_alloc(sc->sc_dmat, vec_size, 0);
-	if (vec_adm == NULL) {
-		printf("%s: could not allocate BHIE DMA vector buffer\n",
-		    sc->sc_dev.dv_xname);
-		qwz_dmamem_free(sc->sc_dmat, data_adm);
-		return;
+		vec_adm = qwz_dmamem_alloc(sc->sc_dmat, vec_size, 0);
+		if (vec_adm == NULL) {
+			printf("%s: could not allocate BHIE DMA vector buffer\n",
+			    sc->sc_dev.dv_xname);
+			qwz_dmamem_free(sc->sc_dmat, data_adm);
+			return;
+		}
 	}
 
 	/* Create vector which controls chunk-wise DMA copy from hardware. */
-- 
2.55.0


From 22276741e32fb28db5cbe234356cf3e5d6926aa5 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:56:57 +0200
Subject: [PATCH 07/11] sys/qwz: reset MHI ring accounting

Backport of sys/dev/pci/if_qwx_pci.c,v 1.34 and sys/dev/pci/if_qwx_pci.c,v 1.38
---
 sys/dev/pci/if_qwz_pci.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/sys/dev/pci/if_qwz_pci.c b/sys/dev/pci/if_qwz_pci.c
index 8b62d533f71..2bd79df6cf6 100644
--- a/sys/dev/pci/if_qwz_pci.c
+++ b/sys/dev/pci/if_qwz_pci.c
@@ -2445,6 +2445,7 @@ qwz_mhi_init_cmd_ring(struct qwz_pci_softc *psc)
 	len = ring->size;
 
 	ring->rp = ring->wp = paddr;
+	ring->queued = 0;
 
 	c = (struct qwz_mhi_cmd_ctxt *)QWZ_DMA_KVA(psc->cmd_ctxt);
 	c->rbase = htole64(paddr);
@@ -2668,6 +2669,7 @@ qwz_mhi_start_channel(struct qwz_pci_softc *psc,
 
 	paddr = QWZ_DMA_DVA(ring->dmamem);
 	ring->rp = ring->wp = paddr;
+	ring->queued = 0;
 	c->rbase = htole64(paddr);
 	c->rp = htole64(ring->rp);
 	c->wp = htole64(ring->wp);
-- 
2.55.0


From 973bbb41a59891a42a92b965bc9d8e84e7367fd6 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:57:03 +0200
Subject: [PATCH 08/11] sys/qwz: stop MHI on full power-down

Backport of sys/dev/pci/if_qwx_pci.c,v 1.39
---
 sys/dev/pci/if_qwz_pci.c | 157 +++++++++++++++++++++++++++++++--------
 1 file changed, 126 insertions(+), 31 deletions(-)

diff --git a/sys/dev/pci/if_qwz_pci.c b/sys/dev/pci/if_qwz_pci.c
index 2bd79df6cf6..4decda11fe3 100644
--- a/sys/dev/pci/if_qwz_pci.c
+++ b/sys/dev/pci/if_qwz_pci.c
@@ -462,11 +462,11 @@ void	qwz_pci_intr_ctrl_event_ee(struct qwz_pci_softc *, uint32_t);
 void	qwz_pci_intr_ctrl_event_cmd_complete(struct qwz_pci_softc *,
 	    uint64_t, uint32_t);
 int	qwz_pci_intr_ctrl_event(struct qwz_pci_softc *,
-	    struct qwz_pci_event_ring *);
+	    struct qwz_pci_event_ring *, int);
 void	qwz_pci_intr_data_event_tx(struct qwz_pci_softc *,
 	    struct qwz_mhi_ring_element *);
 int	qwz_pci_intr_data_event(struct qwz_pci_softc *,
-	    struct qwz_pci_event_ring *);
+	    struct qwz_pci_event_ring *, int);
 int	qwz_pci_intr_mhi_ctrl(void *);
 int	qwz_pci_intr_mhi_data(void *);
 int	qwz_pci_intr(void *);
@@ -2279,9 +2279,77 @@ struct qwz_dma_vec_entry {
 	uint64_t size;
 };
 
+int
+qwz_mhi_stop_channel(struct qwz_pci_softc *psc, struct qwz_pci_xfer_ring *ring)
+{
+	struct qwz_softc *sc = &psc->sc_sc;
+	int ret = 0;
+
+	if (ring->mhi_chan_state != MHI_CH_STATE_ENABLED)
+		return 0;
+
+	DNPRINTF(QWZ_D_MHI, "%s: stop MHI channel %d in state %d\n", __func__,
+	    ring->mhi_chan_id, ring->mhi_chan_state);
+
+	bus_dmamap_sync(sc->sc_dmat, QWZ_DMA_MAP(psc->chan_ctxt), 0,
+	    QWZ_DMA_LEN(psc->chan_ctxt), BUS_DMASYNC_PREWRITE);
+
+	ring->cmd_status = MHI_EV_CC_INVALID;
+	if (qwz_mhi_send_cmd(psc, MHI_CMD_STOP_CHAN, ring->mhi_chan_id))
+		return 1;
+
+	while (ring->cmd_status != MHI_EV_CC_SUCCESS) {
+		ret = tsleep_nsec(&ring->cmd_status, 0, "qwzcmd",
+		    SEC_TO_NSEC(5));
+		if (ret)
+			break;
+	}
+
+	if (ret) {
+		printf("%s: could not stop MHI channel %d in state %d: status 0x%x\n",
+		    sc->sc_dev.dv_xname, ring->mhi_chan_id,
+		    ring->mhi_chan_state, ring->cmd_status);
+		return 1;
+	}
+
+	ring->mhi_chan_state = MHI_CH_STATE_DISABLED;
+	return 0;
+}
+
+void
+qwz_mhi_stop_channels(struct qwz_pci_softc *psc)
+{
+	struct qwz_pci_xfer_ring *ring;
+
+	if (psc->xfer_rings[QWZ_PCI_XFER_RING_IPCR_OUTBOUND].mhi_chan_state
+	    != MHI_CH_STATE_ENABLED &&
+	    psc->xfer_rings[QWZ_PCI_XFER_RING_IPCR_INBOUND].mhi_chan_state
+	    != MHI_CH_STATE_ENABLED)
+		return;
+
+	qwz_mhi_device_wake(&psc->sc_sc);
+
+	ring = &psc->xfer_rings[QWZ_PCI_XFER_RING_IPCR_OUTBOUND];
+	qwz_mhi_stop_channel(psc, ring);
+
+	ring = &psc->xfer_rings[QWZ_PCI_XFER_RING_IPCR_INBOUND];
+	qwz_mhi_stop_channel(psc, ring);
+
+	qwz_mhi_device_zzz(&psc->sc_sc);
+}
+
+void
+qwz_mhi_flush_mhi_event_rings(struct qwz_pci_softc *psc)
+{
+	qwz_pci_intr_ctrl_event(psc, &psc->event_rings[0], 1);
+	qwz_pci_intr_data_event(psc, &psc->event_rings[1], 1);
+}
+
 void
 qwz_pci_power_down(struct qwz_softc *sc)
 {
+	struct qwz_pci_softc *psc = (struct qwz_pci_softc *)sc;
+	struct qwz_pci_xfer_ring *ring;
 	uint32_t state;
 	int i;
 
@@ -2290,6 +2358,9 @@ qwz_pci_power_down(struct qwz_softc *sc)
 
 	qwz_pci_force_wake(sc);
 
+	qwz_mhi_stop_channels(psc);
+	qwz_mhi_flush_mhi_event_rings(psc);
+
 	/*
 	 * Ask firmware to transition to M3 before resetting the device
 	 * so it can flush state cleanly.  Otherwise stale chip RAM from
@@ -2318,6 +2389,23 @@ qwz_pci_power_down(struct qwz_softc *sc)
 	qwz_mhi_stop(sc);
 	clear_bit(ATH12K_FLAG_DEVICE_INIT_DONE, sc->sc_flags);
 	qwz_pci_sw_reset(sc, false);
+
+	for (i = 0; i < nitems(psc->xfer_rings); i++)
+		psc->xfer_rings[i].mhi_chan_state = MHI_CH_STATE_DISABLED;
+
+	ring = &psc->xfer_rings[QWZ_PCI_XFER_RING_IPCR_OUTBOUND];
+	for (i = 0; i < ring->num_elements; i++) {
+		struct qwz_xfer_data *xfer = &ring->data[i];
+
+		if (xfer->m == NULL)
+			continue;
+		bus_dmamap_sync(sc->sc_dmat, xfer->map, 0,
+		    xfer->map->dm_mapsize, BUS_DMASYNC_POSTWRITE);
+		bus_dmamap_unload(sc->sc_dmat, xfer->map);
+		m_freem(xfer->m);
+		xfer->m = NULL;
+	}
+	ring->queued = 0;
 }
 
 void
@@ -2729,6 +2817,7 @@ qwz_mhi_start_channel(struct qwz_pci_softc *psc,
 		qwz_mhi_ring_doorbell(sc, ring->db_addr, ring->wp);
 	}
 
+	ring->mhi_chan_state = MHI_CH_STATE_ENABLED;
 	return 0;
 }
 
@@ -3708,7 +3797,8 @@ qwz_pci_intr_ctrl_event_cmd_complete(struct qwz_pci_softc *psc,
 }
 
 int
-qwz_pci_intr_ctrl_event(struct qwz_pci_softc *psc, struct qwz_pci_event_ring *ring)
+qwz_pci_intr_ctrl_event(struct qwz_pci_softc *psc,
+    struct qwz_pci_event_ring *ring, int flush)
 {
 	struct qwz_softc *sc = &psc->sc_sc;
 	struct qwz_mhi_event_ctxt *c;
@@ -3760,21 +3850,23 @@ qwz_pci_intr_ctrl_event(struct qwz_pci_softc *psc, struct qwz_pci_event_ring *ri
 		DNPRINTF(QWZ_D_MHI, "%s: len=%u code=0x%x type=0x%x chid=%d\n",
 		    __func__, len, code, type, chid);
 
-		switch (type) {
-		case MHI_PKT_TYPE_STATE_CHANGE_EVENT:
-			qwz_pci_intr_ctrl_event_mhi(psc, code);
-			break;
-		case MHI_PKT_TYPE_EE_EVENT:
-			qwz_pci_intr_ctrl_event_ee(psc, code);
-			break;
-		case MHI_PKT_TYPE_CMD_COMPLETION_EVENT:
-			qwz_pci_intr_ctrl_event_cmd_complete(psc,
-			    le64toh(e->ptr), code);
-			break;
-		default:
-			printf("%s: unhandled event type 0x%x\n",
-			    __func__, type);
-			break;
+		if (!flush) {
+			switch (type) {
+			case MHI_PKT_TYPE_STATE_CHANGE_EVENT:
+				qwz_pci_intr_ctrl_event_mhi(psc, code);
+				break;
+			case MHI_PKT_TYPE_EE_EVENT:
+				qwz_pci_intr_ctrl_event_ee(psc, code);
+				break;
+			case MHI_PKT_TYPE_CMD_COMPLETION_EVENT:
+				qwz_pci_intr_ctrl_event_cmd_complete(psc,
+				    le64toh(e->ptr), code);
+				break;
+			default:
+				printf("%s: unhandled event type 0x%x\n",
+				    __func__, type);
+				break;
+			}
 		}
 
 		if (ring->rp + sizeof(*e) >= base + ring->size)
@@ -3931,7 +4023,8 @@ qwz_pci_intr_data_event_tx(struct qwz_pci_softc *psc, struct qwz_mhi_ring_elemen
 }
 
 int
-qwz_pci_intr_data_event(struct qwz_pci_softc *psc, struct qwz_pci_event_ring *ring)
+qwz_pci_intr_data_event(struct qwz_pci_softc *psc,
+    struct qwz_pci_event_ring *ring, int flush)
 {
 	struct qwz_softc *sc = &psc->sc_sc;
 	struct qwz_mhi_event_ctxt *c;
@@ -3981,14 +4074,16 @@ qwz_pci_intr_data_event(struct qwz_pci_softc *psc, struct qwz_pci_event_ring *ri
 		DNPRINTF(QWZ_D_MHI, "%s: len=%u code=0x%x type=0x%x chid=%d\n",
 		    __func__, len, code, type, chid);
 
-		switch (type) {
-		case MHI_PKT_TYPE_TX_EVENT:
-			qwz_pci_intr_data_event_tx(psc, e);
-			break;
-		default:
-			printf("%s: unhandled event type 0x%x\n",
-			    __func__, type);
-			break;
+		if (!flush) {
+			switch (type) {
+			case MHI_PKT_TYPE_TX_EVENT:
+				qwz_pci_intr_data_event_tx(psc, e);
+				break;
+			default:
+				printf("%s: unhandled event type 0x%x\n",
+				    __func__, type);
+				break;
+			}
 		}
 
 		if (ring->rp + sizeof(*e) >= base + ring->size)
@@ -4016,7 +4111,7 @@ qwz_pci_intr_mhi_ctrl(void *arg)
 {
 	struct qwz_pci_softc *psc = arg;
 
-	if (qwz_pci_intr_ctrl_event(psc, &psc->event_rings[0]))
+	if (qwz_pci_intr_ctrl_event(psc, &psc->event_rings[0], 0))
 		return 1;
 
 	return 0;
@@ -4027,7 +4122,7 @@ qwz_pci_intr_mhi_data(void *arg)
 {
 	struct qwz_pci_softc *psc = arg;
 
-	if (qwz_pci_intr_data_event(psc, &psc->event_rings[1]))
+	if (qwz_pci_intr_data_event(psc, &psc->event_rings[1], 0))
 		return 1;
 
 	return 0;
@@ -4096,9 +4191,9 @@ qwz_pci_intr(void *arg)
 	if (!test_bit(ATH12K_FLAG_MULTI_MSI_VECTORS, sc->sc_flags)) {
 		int i;
 
-		if (qwz_pci_intr_ctrl_event(psc, &psc->event_rings[0]))
+		if (qwz_pci_intr_ctrl_event(psc, &psc->event_rings[0], 0))
 			ret = 1;
-		if (qwz_pci_intr_data_event(psc, &psc->event_rings[1]))
+		if (qwz_pci_intr_data_event(psc, &psc->event_rings[1], 0))
 			ret = 1;
 
 		for (i = 0; i < sc->hw_params.ce_count; i++) {
-- 
2.55.0


From 26219f509976cfd97552c7488b333a4fe91b52e0 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:57:30 +0200
Subject: [PATCH 09/11] sys/qwz: retain unused RX descriptors

Draw RX descriptors from the free list only when the caller supplies
none. Return unused descriptors on every refill exit, including
allocation failures and a full ring. This fixes qwz-specific ownership.
---
 sys/dev/ic/qwz.c | 39 +++++++++++++++++----------------------
 1 file changed, 17 insertions(+), 22 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index ca25046f6e3..093accee2e2 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -13405,7 +13405,7 @@ qwz_dp_rxbufs_replenish(struct qwz_softc *sc,
 	int num_free;
 	int num_remain;
 	int num_cut;
-	int ret, i;
+	int ret = 0;
 	uint32_t cookie;
 	uint64_t paddr;
 	struct ath12k_rx_desc_info *rx_desc;
@@ -13426,21 +13426,18 @@ qwz_dp_rxbufs_replenish(struct qwz_softc *sc,
 	req_entries = MIN(num_free, req_entries);
 	num_remain = req_entries;
 
-	if (!num_remain) {
-		qwz_hal_srng_access_end(sc, srng);
-#ifdef notyet
-		spin_unlock_bh(&srng->lock);
-#endif
-		return 0;
-	}
+	if (!num_remain)
+		goto done;
 
-	for (i = 0, num_cut = 0; i < num_remain; i++) {
-		if (TAILQ_EMPTY(&dp->rx_desc_free_list))
-			break;
-		rx_desc = TAILQ_FIRST(&dp->rx_desc_free_list);
-		TAILQ_REMOVE(&dp->rx_desc_free_list, rx_desc, entry);
-		TAILQ_INSERT_TAIL(used_list, rx_desc, entry);
-		num_cut++;
+	if (TAILQ_EMPTY(used_list)) {
+		for (num_cut = 0; num_cut < num_remain; num_cut++) {
+			rx_desc = TAILQ_FIRST(&dp->rx_desc_free_list);
+			if (rx_desc == NULL)
+				break;
+			TAILQ_REMOVE(&dp->rx_desc_free_list, rx_desc, entry);
+			TAILQ_INSERT_TAIL(used_list, rx_desc, entry);
+		}
+		num_remain = num_cut;
 	}
 
 	while (num_remain > 0) {
@@ -13496,22 +13493,20 @@ qwz_dp_rxbufs_replenish(struct qwz_softc *sc,
 		qwz_hal_rx_buf_addr_info_set(desc, paddr, cookie, mgr);
 	}
 
-	qwz_hal_srng_access_end(sc, srng);
-#ifdef notyet
-	spin_unlock_bh(&srng->lock);
-#endif
-	return 0;
+	goto done;
 
 fail_dma_unmap:
 	bus_dmamap_unload(sc->sc_dmat, rx_desc->map);
 fail_free_mbuf:
 	m_free(m);
-
+	ret = ENOBUFS;
+done:
 	qwz_hal_srng_access_end(sc, srng);
+	TAILQ_CONCAT(&dp->rx_desc_free_list, used_list, entry);
 #ifdef notyet
 	spin_unlock_bh(&srng->lock);
 #endif
-	return ENOBUFS;
+	return ret;
 }
 
 int
-- 
2.55.0


From f684221872dfa9a5e53fb37e62bc5c7efcd59871 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:55:16 +0200
Subject: [PATCH 10/11] sys/qwz: check RX slots before allocating

Backport of sys/dev/ic/qwx.c,v 1.117
---
 sys/dev/ic/qwz.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index 093accee2e2..639df12177d 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -13443,6 +13443,10 @@ qwz_dp_rxbufs_replenish(struct qwz_softc *sc,
 	while (num_remain > 0) {
 		const size_t size = DP_RX_BUFFER_SIZE;
 
+		rx_desc = TAILQ_FIRST(used_list);
+		if (rx_desc == NULL)
+			break;
+
 		m = m_gethdr(M_DONTWAIT, MT_DATA);
 		if (m == NULL)
 			goto fail_free_mbuf;
@@ -13456,10 +13460,6 @@ qwz_dp_rxbufs_replenish(struct qwz_softc *sc,
 
 		m->m_len = m->m_pkthdr.len = size;
 
-		rx_desc = TAILQ_FIRST(used_list);
-		if (rx_desc == NULL)
-			goto fail_free_mbuf;
-
 		ret = bus_dmamap_load_mbuf(sc->sc_dmat, rx_desc->map, m,
 		    BUS_DMA_READ | BUS_DMA_NOWAIT);
 		if (ret) {
-- 
2.55.0


From ff55ff61dad921d676a340047d70412f766f4876 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill@korins.ky>
Date: Sun, 27 Sep 2026 12:57:33 +0200
Subject: [PATCH 11/11] sys/qwz: reclaim incomplete RX batches

Process and replenish reaped buffers even when the batch contains no
complete MSDU. Error only and incomplete bacthes must not leave buffers
or descriptors on temporary lists.

---
 sys/dev/ic/qwz.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/sys/dev/ic/qwz.c b/sys/dev/ic/qwz.c
index 639df12177d..69a0cd71e42 100644
--- a/sys/dev/ic/qwz.c
+++ b/sys/dev/ic/qwz.c
@@ -15775,9 +15775,6 @@ try_again:
 #ifdef notyet
 	spin_unlock_bh(&srng->lock);
 #endif
-	if (!total_msdu_reaped)
-		goto exit;
-
 	for (i = 0; i < sc->num_radios; i++) {
 		if (!num_buffs_reaped[i])
 			continue;
@@ -15787,7 +15784,6 @@ try_again:
 		qwz_dp_rxbufs_replenish(sc, &dp->rx_refill_buf_ring,
 		    &rx_desc_used_list, num_buffs_reaped[i]);
 	}
-exit:
 	return total_msdu_reaped;
 }
 
-- 
2.55.0



-- 
wbr, Kirill