fix qwx roaming (Re: sys/qwx,sys/qwz: report RSSI as percentage)

Stefan Sperling <[email protected]>
Newsgroups gmane.os.openbsd.tech
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.