Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
fix qwx roaming (Re: sys/qwx,sys/qwz: report RSSI as percentage)
To:
marcus@nazgul.ch, tech@openbsd.org
Date:
Sun, 31 May 2026 18:58:12 +0200

Download raw body.

Thread
On Fri, May 22, 2026 at 12:58:40AM +0200, Kirill A. Korinsky wrote:
> Next, roaming. It doesn't work. It never jumped to AP in direct view of
> laptop when I saw -90 dBm. And iwx laptop jumped to it.

This fixes roaming on qwx for me. ok?

M  sys/dev/ic/qwx.c          |  210+  31-
M  sys/dev/ic/qwxvar.h       |    7+   0-
M  sys/dev/pci/if_qwx_pci.c  |    1+   0-

3 files changed, 218 insertions(+), 31 deletions(-)

commit - ca362689551c3ca46673929394339d3a1d8b4983
commit + f605e0317ada28d37acd9064fd79920e6b9ca1cb
blob - e10ad8ce38428ef4a0dd316e271561c1b91c5f7d
blob + 023f95a3cada9608773b35112d54f81e489ad8c2
--- sys/dev/ic/qwx.c
+++ sys/dev/ic/qwx.c
@@ -323,6 +323,7 @@ qwx_init(struct ifnet *ifp)
 			return error;
 
 		ifp->if_flags |= IFF_RUNNING;
+		sc->ops.irq_enable(sc);
 		ieee80211_begin_scan(ifp);
 	}
 
@@ -396,6 +397,18 @@ qwx_mfp_leave(struct qwx_softc *sc)
 }
 
 void
+qwx_del_task_all(struct qwx_softc *sc)
+{
+	qwx_del_task(sc, sc->sc_nswq, &sc->newstate_task);
+	qwx_del_task(sc, systq, &sc->setkey_task);
+	qwx_setkey_clear(sc);
+	qwx_del_task(sc, systq, &sc->ba_task);
+	qwx_del_task(sc, systq, &sc->bgscan_task);
+	qwx_del_task(sc, systq, &sc->bgscan_done_task);
+	qwx_del_task(sc, systq, &sc->set_cc_task);
+}
+
+void
 qwx_stop(struct ifnet *ifp)
 {
 	struct qwx_softc *sc = ifp->if_softc;
@@ -419,20 +432,18 @@ qwx_stop(struct ifnet *ifp)
 
 	/* Cancel scheduled tasks and let any stale tasks finish up. */
 	task_del(systq, &sc->init_task);
-	qwx_del_task(sc, sc->sc_nswq, &sc->newstate_task);
-	qwx_del_task(sc, systq, &sc->setkey_task);
-	qwx_del_task(sc, systq, &sc->ba_task);
-	qwx_del_task(sc, systq, &sc->bgscan_task);
-	qwx_del_task(sc, systq, &sc->set_cc_task);
+	qwx_del_task_all(sc);
 	refcnt_finalize(&sc->task_refs, "qwxstop");
 
-	qwx_setkey_clear(sc);
-
 	ifp->if_timer = sc->sc_tx_timer = 0;
 
 	ifp->if_flags &= ~IFF_RUNNING;
 	ifq_clr_oactive(&ifp->if_snd);
 
+	free(sc->bgscan_unref_arg, M_DEVBUF, sc->bgscan_unref_arg_size);
+	sc->bgscan_unref_arg = NULL;
+	sc->bgscan_unref_arg_size = 0;
+
 	clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags);
 
 	/*
@@ -455,6 +466,8 @@ qwx_stop(struct ifnet *ifp)
 		sc->bss_peer_id = HAL_INVALID_PEERID;
 	}
 
+	sc->sc_flags &= ~QWX_FLAG_ROAMING;
+
 	sc->scan.state = ATH11K_SCAN_IDLE;
 	sc->vdev_id_11d_scan = QWX_11D_INVALID_VDEV_ID;
 	sc->pdevs_active = 0;
@@ -1098,9 +1111,7 @@ qwx_newstate(struct ieee80211com *ic, enum ieee80211_s
 		qwx_setkey_clear(sc);
 
 		qwx_del_task(sc, systq, &sc->bgscan_task);
-#if 0
 		qwx_del_task(sc, systq, &sc->bgscan_done_task);
-#endif
 		qwx_del_task(sc, systq, &sc->set_cc_task);
 	}
 
@@ -1144,6 +1155,15 @@ qwx_newstate_task(void *arg)
 	if (nstate <= ostate) {
 		switch (ostate) {
 		case IEEE80211_S_RUN:
+			if (sc->sc_flags & QWX_FLAG_ROAMING) {
+				/*
+				 * Previous association state has already
+				 * been torn down. Don't try do to it again.
+				 */
+				sc->sc_flags &= ~QWX_FLAG_ROAMING;
+				break;
+			}
+
 			if (ic->ic_opmode == IEEE80211_M_STA &&
 			    (ifp->if_flags & IFF_RUNNING) &&
 			    (ic->ic_bss->ni_flags & IEEE80211_NODE_MFP) &&
@@ -1195,14 +1215,8 @@ next_scan:
 			printf("%s: %s -> %s\n", ifp->if_xname,
 			    ieee80211_state_name[ic->ic_state],
 			    ieee80211_state_name[IEEE80211_S_SCAN]);
-#if 0
-		if ((sc->sc_flags & QWX_FLAG_BGSCAN) == 0) {
-#endif
-			ieee80211_set_link_state(ic, LINK_STATE_DOWN);
-			ieee80211_node_cleanup(ic, ic->ic_bss);
-#if 0
-		}
-#endif
+		ieee80211_set_link_state(ic, LINK_STATE_DOWN);
+		ieee80211_node_cleanup(ic, ic->ic_bss);
 		ic->ic_state = IEEE80211_S_SCAN;
 		refcnt_rele_wake(&sc->task_refs);
 		splx(s);
@@ -13707,8 +13721,11 @@ qwx_wmi_process_mgmt_tx_comp(struct qwx_softc *sc,
 	ieee80211_release_node(ic, tx_data->ni);
 	tx_data->ni = NULL;
 
-	if (arvif->txmgmt.queued > 0)
+	if (arvif->txmgmt.queued > 0) {
 		arvif->txmgmt.queued--;
+		if (arvif->txmgmt.queued == 0)
+			wakeup(&arvif->txmgmt.queued);
+	}
 
 	if (tx_compl_param->status != 0)
 		ifp->if_oerrors++;
@@ -15980,8 +15997,11 @@ qwx_dp_tx_free_txbuf(struct qwx_softc *sc, int msdu_id
 		m_freem(tx_data->m);
 		tx_data->m = NULL;
 
-		if (tx_ring->queued > 0)
+		if (tx_ring->queued > 0) {
 			tx_ring->queued--;
+			if (tx_ring->queued == 0)
+				wakeup(&tx_ring->queued);
+		}
 	}
 
 	if (tx_data->ni) {
@@ -16133,8 +16153,11 @@ qwx_dp_tx_complete_msdu(struct qwx_softc *sc, struct d
 		m_freem(tx_data->m);
 		tx_data->m = NULL;
 	
-		if (tx_ring->queued > 0)
+		if (tx_ring->queued > 0) {
 			tx_ring->queued--;
+			if (tx_ring->queued == 0)
+				wakeup(&tx_ring->queued);
+		}
 	}
 
 	if (tx_data->ni == NULL)
@@ -20926,7 +20949,6 @@ qwx_core_qmi_firmware_ready(struct qwx_softc *sc)
 		goto err_core_stop;
 	}
 
-	sc->ops.irq_enable(sc);
 #if 0
 	mutex_unlock(&ab->core_lock);
 #endif
@@ -26304,6 +26326,171 @@ qwx_bgscan(struct ieee80211com *ic)
 	return 0;
 }
 
+
+void
+qwx_bgscan_done_task(void *arg)
+{
+	struct qwx_softc *sc = arg;
+	struct qwx_dp *dp = &sc->dp;
+	struct qwx_vif *arvif = TAILQ_FIRST(&sc->vif_list); /* XXX */
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ifnet *ifp = &ic->ic_if;
+	struct ieee80211_node *ni = ic->ic_bss;
+	struct qwx_node *nq = (struct qwx_node *)ni;
+	int err = 0, s, i;
+
+	s = splnet();
+
+	/* Prevent races with ifconfig commands. */
+	if (rw_enter(&sc->ioctl_rwl, RW_WRITE | RW_NOSLEEP) != 0) {
+		refcnt_rele_wake(&sc->task_refs);
+		splx(s);
+		return;
+	}
+
+	/* Ensure that we start in expected state. */
+	if (test_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags) ||
+	    (ifp->if_flags & IFF_RUNNING) == 0 ||
+	    (ic->ic_flags & IEEE80211_F_BGSCAN) == 0 ||
+	    (ic->ic_xflags & IEEE80211_F_TX_MGMT_ONLY) == 0 ||
+	    (sc->sc_flags & QWX_FLAG_ROAMING) ||
+	    ic->ic_state != IEEE80211_S_RUN) {
+		/* Don't touch the device, just return. */
+		rw_exit(&sc->ioctl_rwl);
+		refcnt_rele_wake(&sc->task_refs);
+		splx(s);
+		return;
+	}
+
+	/* Send a DEAUTH frame to our old AP. */
+	err = IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
+	    IEEE80211_REASON_AUTH_LEAVE);
+	if (err)
+		goto done;
+
+	/* Prevent state changes due to received frames. */
+	ifp->if_flags &= ~IFF_RUNNING;
+
+	/* Disallow new tasks. */
+	set_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags);
+
+	qwx_del_task_all(sc);
+
+	/* Wait for Tx queues to drain. */
+	for (i = 0; i < sc->hw_params.max_tx_ring; i++) {
+		struct dp_tx_ring *tx_ring = &dp->tx_ring[i];
+
+		while (tx_ring->queued > 0) {
+			err = tsleep_nsec(&tx_ring->queued, 0, "qwxtxdr",
+			    SEC_TO_NSEC(1));
+			if (err) {
+				if (tx_ring->queued == 0) {
+					err = 0;
+					break;
+				}
+				printf("%s: Tx ring %d has %d frames queued\n",
+				    __func__, i, tx_ring->queued);
+				goto done;
+			}
+		}
+	}
+	while (arvif->txmgmt.queued > 0) {
+		err = tsleep_nsec(&arvif->txmgmt.queued, 0, "qwxtxdr",
+		    MSEC_TO_NSEC(500));
+		if (err) {
+			if (arvif->txmgmt.queued == 0) {
+				err = 0;
+				break;
+			}
+			printf("%s: %d management frames still queued\n",
+			    __func__, arvif->txmgmt.queued);
+			goto done;
+		}
+	}
+
+	/*
+	 * Remove installed crypto keys while we still have access to them.
+	 * Once qwx_newstate() is entered ic_bss will already contain
+	 * information about our next AP.
+	 */
+	if (ic->ic_flags & IEEE80211_F_RSNON) {
+		struct ieee80211_key *k;
+
+	
+		if (nq->flags & QWX_NODE_FLAG_HAVE_PAIRWISE_KEY)
+			(*ic->ic_delete_key)(ic, ni, &ni->ni_pairwise_key);
+
+		if ((nq->flags & QWX_NODE_FLAG_HAVE_GROUP_KEY) &&
+		    (ic->ic_def_txkey == 1 || ic->ic_def_txkey == 2)) {
+			k = &ic->ic_nw_keys[ic->ic_def_txkey];
+			if ((k->k_flags & IEEE80211_KEY_GROUP) &&
+			    k->k_cipher == IEEE80211_CIPHER_CCMP)
+				(*ic->ic_delete_key)(ic, ni, k);
+		}
+
+		if (ic->ic_igtk_kid == 4 || ic->ic_igtk_kid == 5) {
+			k = &ic->ic_nw_keys[ic->ic_igtk_kid];
+			if (k->k_flags & IEEE80211_KEY_IGTK)
+				(*ic->ic_delete_key)(ic, ni, k);
+		}
+
+		ni->ni_port_valid = 0;
+		ni->ni_flags &= ~IEEE80211_NODE_TXRXPROT;
+		ni->ni_flags &= ~IEEE80211_NODE_TXMGMTPROT;
+		ni->ni_flags &= ~IEEE80211_NODE_RXMGMTPROT;
+		ni->ni_rsn_supp_state = RSNA_SUPP_INITIALIZE;
+	}
+
+	/*
+	 * XXX: This needs to be unset for vdev shutdown to work.
+	 * Perhaps we need a separate flag?
+	 */
+	clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags);
+
+	/* Clear association to our old AP in firmware. */
+	err = qwx_run_stop(sc);
+	if (err)
+		goto done;
+
+	err = qwx_deauth(sc);
+	if (err)
+		goto done;
+
+	/* Allow roaming to proceed. */
+	sc->sc_flags |= QWX_FLAG_ROAMING;
+	ifp->if_flags |= IFF_RUNNING;
+	ni->ni_unref_arg = sc->bgscan_unref_arg;
+	ni->ni_unref_arg_size = sc->bgscan_unref_arg_size;
+	sc->bgscan_unref_arg = NULL;
+	sc->bgscan_unref_arg_size = 0;
+	ieee80211_node_switch_bss(ic, ni);
+done:
+	if (err) {
+		free(sc->bgscan_unref_arg, M_DEVBUF, sc->bgscan_unref_arg_size);
+		sc->bgscan_unref_arg = NULL;
+		sc->bgscan_unref_arg_size = 0;
+
+		ifp->if_flags |= IFF_RUNNING;
+		task_add(systq, &sc->init_task);
+	}
+
+	rw_exit(&sc->ioctl_rwl);
+	refcnt_rele_wake(&sc->task_refs);
+	splx(s);
+}
+
+void
+qwx_bgscan_done(struct ieee80211com *ic,
+    struct ieee80211_node_switch_bss_arg *arg, size_t arg_size)
+{
+	struct qwx_softc *sc = ic->ic_softc;
+
+	free(sc->bgscan_unref_arg, M_DEVBUF, sc->bgscan_unref_arg_size);
+	sc->bgscan_unref_arg = arg;
+	sc->bgscan_unref_arg_size = arg_size;
+	qwx_add_task(sc, systq, &sc->bgscan_done_task);
+}
+
 /*
  * Find a pdev which corresponds to a given channel.
  * This doesn't exactly match the semantics of the Linux driver
@@ -26407,7 +26594,7 @@ qwx_auth(struct qwx_softc *sc)
 
 	qwx_recalculate_mgmt_rate(sc, ni, arvif->vdev_id, pdev->pdev_id);
 	ni->ni_txrate = 0;
-	
+
 	/* Start vdev. */
 	ret = qwx_mac_vdev_start(sc, arvif, pdev->pdev_id);
 	if (ret) {
@@ -27013,13 +27200,6 @@ qwx_assoc(struct qwx_softc *sc)
 	IEEE80211_ADDR_COPY(arvif->bssid, ni->ni_bssid);
 	sc->bss_peer_id = nq->peer_id;
 
-	/*
-	 * Enable reception of data frames now, if not already enabled.
-	 * We may need to receive EAPOL data frames very soon after the
-	 * AP sends a response to our assoc request.
-	 */
-	sc->ops.irq_enable(sc);
-
 	return 0;
 }
 
@@ -27112,8 +27292,6 @@ qwx_run_stop(struct qwx_softc *sc)
 	struct qwx_node *nq = (void *)ni;
 	int ret;
 
-	sc->ops.irq_disable(sc);
-
 	ret = qwx_wmi_set_peer_param(sc, ni->ni_macaddr, arvif->vdev_id,
 	    pdev_id, WMI_PEER_AUTHORIZE, 0);
 	if (ret) {
@@ -27167,6 +27345,7 @@ qwx_attach(struct qwx_softc *sc)
 	task_set(&sc->setkey_task, qwx_setkey_task, sc);
 	task_set(&sc->ba_task, qwx_ba_task, sc);
 	task_set(&sc->bgscan_task, qwx_bgscan_task, sc);
+	task_set(&sc->bgscan_done_task, qwx_bgscan_done_task, sc);
 	task_set(&sc->set_cc_task, qwx_set_cc_task, sc);
 	timeout_set_proc(&sc->scan.timeout, qwx_scan_timeout, sc);
 #if NBPFILTER > 0
blob - 9635f26dbbea069ee04706a17f5119161eb3ed27
blob + a4e9ac3ccc564f7cb0fc1d6e23b61155e59f3c93
--- sys/dev/ic/qwxvar.h
+++ sys/dev/ic/qwxvar.h
@@ -412,6 +412,8 @@ enum ath11k_dev_flags {
 	ATH11K_FLAG_FIXED_MEM_RGN,
 	ATH11K_FLAG_DEVICE_INIT_DONE,
 	ATH11K_FLAG_MULTI_MSI_VECTORS,
+
+	QWX_FLAG_ROAMING,
 };
 
 enum ath11k_scan_state {
@@ -1886,6 +1888,9 @@ struct qwx_softc {
 	u_int			scan_channel;
 	struct qwx_survey_info	survey[IEEE80211_CHAN_MAX];
 	struct task		bgscan_task;
+	struct task		bgscan_done_task;
+	struct ieee80211_node_switch_bss_arg *bgscan_unref_arg;
+	size_t bgscan_unref_arg_size;
 
 	int			attached;
 	struct {
@@ -2032,6 +2037,8 @@ void	qwx_init_task(void *);
 int	qwx_newstate(struct ieee80211com *, enum ieee80211_state, int);
 void	qwx_newstate_task(void *);
 int	qwx_bgscan(struct ieee80211com *);
+void	qwx_bgscan_done(struct ieee80211com *,
+	    struct ieee80211_node_switch_bss_arg *, size_t);
 void	qwx_updatechan(struct ieee80211com *);
 
 struct qwx_node {
blob - 2c3164ff3d02a115c326d8d2c8868cd71ab67386
blob + f23e4bd271d5fe321ba30b9c2da3c3a60e10064b
--- sys/dev/pci/if_qwx_pci.c
+++ sys/dev/pci/if_qwx_pci.c
@@ -1134,6 +1134,7 @@ unsupported_wcn6855_soc:
 	ic->ic_ampdu_tx_start = qwx_ampdu_tx_start;
 	ic->ic_ampdu_tx_stop = NULL;
 	ic->ic_bgscan_start = qwx_bgscan;
+	ic->ic_bgscan_done = qwx_bgscan_done;
 
 	/*
 	 * We cannot read the MAC address without loading the