Skip to content

wifi: mt7932: carry out a requested roam - #320

Merged
maralcbr merged 1 commit into
omacom:integration/aurorafrom
scottjones:pr/mt7932-roam-move
Nov 1, 2026
Merged

maralcbr merged 1 commit into
omacom:integration/aurorafrom
scottjones:pr/mt7932-roam-move

Conversation

@scottjones

@scottjones scottjones commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Roaming on the MacBook Neo can leave Wi-Fi offline. iwd asks to reassociate, the driver refuses, and iwd treats that as disconnected while cfg80211 and the firmware stay connected, so every later connect fails until the firmware loses the AP or iwd restarts.

The driver now does the move: it leaves the current AP and joins the requested one. Carrier stays up, so NetworkManager has no reason to restart DHCP. A move that fails still ends the connection cleanly.

 mt_connect(prev_bssid set, connected)
-  return -EBUSY
+  leave the current AP, carrier stays up
+  join the requested AP
+  report through the connect result
+  on failure: drop carrier, report the disconnect

Evidence

J700, iwd 3.12, WPA2-PSK, one SSID across three APs. omacom/omarchy-mac-pkgs#85's NetworkManager setting is noted per run.

Known remaining

  • Joins that time out on 2.4 GHz: across the 10 joins to two BSSes on the three builds tested with this roaming path work, all 5 tries 5.5 minutes or more after the last join to that BSS (or the first to it) timed out with -110, and all 5 within 43 s of a previous try landed. Four of the failures were moves and one a plain connect. On 5 GHz, 2 of 111 failed. These counts are logged joins only: the log line isn't in the journal for joins at boot. On this commit, the first move to one 2.4 GHz BSS timed out and a retry 18 s later landed, but iwd's boot join to the other landed in 0.21 s, over 2 hours after its last join. We don't know yet whether the AP or the firmware is responsible.
  • A move during a scan: from reading the code, a scan still running when a move starts makes the move wait up to about 7 s with carrier up. If the scan hasn't ended by then, the move becomes a full disconnect. If the firmware never ends a scan the driver has already retired, the driver marks the link failed and connects fail until the driver reloads, as any disconnect does on the base. iwd runs scans and connects one at a time, so this needs a scan from outside iwd or firmware that stops answering. Not seen in 358 logged scan requests over four boots, and not exercised.
  • Deferred regulatory change: from reading the code, one applied during a move ends that move in a disconnect and reconnect. Not exercised.
  • Reporting: on a connection made by a move, local disconnects are reported as from the AP, because iwd otherwise drops them. iwd is the client that matters here, but other nl80211 listeners see that too.
  • Moves can come late: in one room iwd moved only at about -89 dBm.
  • Not tested: radar-channel targets and NetworkManager without j414s (MacBook Pro 14" M2 Pro): hci_bcm4377 hangs after long s2idle resume, all HCI commands time out (-110), no recovery #85. SAE and OWE moves can't happen on this stack: the driver doesn't advertise BIP yet, so iwd treats the Neo as WPA2-only, and it refuses a WPA3-only network before the driver sees it. wpa_supplicant wasn't tested.
  • iwd's signal for the AP it was connected to can go stale (on an interim build, -56 on every reconnect during the drops, at a real -80). This doesn't fix that, and it only matters when something reconnects before iwd rescans.

Merge Danger

Door: two-way. One commit, in a driver only J700 binds.

Blast Radius: Neo Wi-Fi.

Roaming now tears down and rejoins for real, so a target AP that doesn't answer costs a longer outage than refusing did.

🤖 Generated with Claude Code

@RyanTheTide RyanTheTide left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. This fixes a real wedge, and our tree has the same refusal today (aurora-silicon/linux aurora-wip still returns -EBUSY from mt_connect() for any connect while connected). I read it against cfg80211 and iwd 3.12 and the state handling holds up. mt_disconnect() flushes the disconnect worker before the join starts, the disconnect report is held back only while roam_moving is set, and a move ends in one connect result, plus a disconnect when it fails. cfg80211 leaves wdev->connected set after a failed connect result, so that trailing disconnect is needed, and the two events are processed in order.

Follow ups, none blocking:

  1. The from-AP reporting. This is an iwd bug, not the driver. netdev_reassociate() sets in_reassoc, and the only code that clears it is in netdev_associate_event(), which handles NL80211_CMD_ASSOCIATE. cfg80211 never sends that for a driver that reports through cfg80211_connect_done(), so on a CMD_CONNECT driver in_reassoc stays set until the connection is freed, and netdev_disconnect_event() drops every disconnect not from the AP. The proper fix is clearing in_reassoc in netdev_connect_event() on success. I'd send that to iwd and drop roam_landed once it ships. Until then, wpa_supplicant also reassociates with prev_bssid through CMD_CONNECT, so its users get these as AP deauths too, and it handles those differently from local ones.

  2. A cancelled move reports WLAN_REASON_DEAUTH_LEAVING after the failed connect result. The cancel already saved its reason in disconnect_reason, so passing reason there keeps what the caller asked for.

  3. roam_moving is read with READ_ONCE() in mt_retire_connection() outside response_lock but written plainly. WRITE_ONCE() at the writes would match roam_landed.

  4. Deferred regulatory change. mt_disconnect_work() still calls mt_reg_apply_deferred_locked() during a move, which sets reg_pending, so mt_rf_allowed() fails in mt_connect() and the move becomes a disconnect. Holding the deferral while roam_moving is set and applying it from mt_connect_work() once the move has a result would keep the move.

  5. KUnit. Which event goes out for a move that lands, fails or is cancelled is small enough to pull into a helper and test, the way the connect result logging is tested in our tree (aurora-silicon/linux 4db3a5a).

On the gaps listed, from our side.

BIP and WPA3 with iwd. You're right, integration/aurora advertises only CCMP. iwd's wiphy_can_connect_sae() won't use SAE without BIP-CMAC-128, and station.c refuses any BSS that sets MFPR when it can't pick a group management cipher, and refuses 6 GHz without MFP. So on this base iwd can't join WPA3-only, WPA2 with PMF required, OWE networks that require PMF, or anything on 6 GHz. That's a gap we left. The SAE commit said iwd stays WPA2-only, and we should have closed it before iwd was the default. Our tree has since added WPA2 PMF and advertises BIP-CMAC-128, with the firmware doing the IGTK install and BIP (aurora-silicon/linux#659, 6156b0c), on top of 6 GHz station support (12010e0, SAE-H2E or OWE with required MFP). Both are on aurora-silicon/linux aurora-wip, not integration/aurora yet. Honest status, that PR passed KUnit and build gates but has not been qualified on hardware with iwd. We're running your matrix on a Neo with iwd and again with NetworkManager and wpa_supplicant: WPA2 with PMF off, optional and required, WPA2/WPA3 transition, WPA3-only, OWE, 6 GHz, and a move between two PMF-required APs. Every row that allows it has to join with PMF on, and the results go in the PR that brings it here. Once BIP is advertised, SAE and OWE moves go through this same path, so this PR is the right base for that.

2.4 GHz -110. Our tree has no fix for this. We've seen the same signature at boot, a -110 after the request reached the AP with the firmware still healthy and the next attempt connecting, which is why we log those at info level in 4db3a5a. Your 5.5 minute pattern looks more like the AP ageing the station out than the firmware, but I haven't proven that either way.

Roaming config. omacom/omarchy-mac-pkgs#84 sets the iwd roam thresholds to -100 to leave roaming to the firmware. This driver never reports a firmware roam (no cfg80211_roamed() anywhere), and your runs show the firmware holding a far AP down to -97 dBm. With this PR in I'd keep iwd roaming on for the MT7932. omacom/omarchy-mac-pkgs#85's autoconnect setting still makes sense as the fallback for joins the firmware rejects.

@maralcbr maralcbr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I checked it against cfg80211's sme.c. A failed connect result releases the old AP's BSS but leaves wdev->connected set, so the trailing disconnect is needed. Both events are handled in the same event loop, so nothing can slip in between them. A landed move correctly takes over the old connection. I agree with Ryan's five follow-ups. A few more, none blocking:

  1. Three disconnect reports skip mt_report_disconnected(). The refused-move path at unlock: (link.c:902-907), the trailing disconnect after a failed move (link.c:750-752) and the existing cancel path (link.c:746) always report the disconnect as local. It's harmless for iwd, since a failed reassociation already ends the connection there, but routing these through the helper (and passing reason, per Ryan's point 2) would keep the from-AP decision in one place.

  2. A narrow race can send two disconnects. It sits between mt_connect() dropping response_lock (link.c:842) and mt_disconnect() taking it again (link.c:950). An RF failure in that window makes mt_rf_fail_locked() report a disconnect right away, because roam_moving only holds back the report in mt_disconnect_work(). mt_connect() then sees link_failed, returns -EIO, and the unlock: path reports a second one. __cfg80211_disconnected() has no guard, so userspace gets a duplicate NL80211_CMD_DISCONNECT and the keys are deleted twice. It's unlikely; checking roam_moving in mt_rf_fail_locked(), or skipping the unlock: report when one has already gone out, would close it.

  3. A comment no longer matches the code. The connect worker's comment (link.c:680-684) says cfg80211 calls are made only after response_lock is dropped. The new unlock: path calls cfg80211_disconnected() and netif_carrier_off() under it. mt_disconnect_work() and mt_rf_fail_locked() already do the same, so this adds no new risk, but the comment should be reworded.

  4. A move during a scan isn't in "Known remaining". mt_disconnect_work() → mt_scan_quiesce() can wait up to about 7 s while carrier stays up. If the scan is still running after that, the admission check refuses on scan_request and the move becomes a full disconnect. Worth one line in the PR.

  5. The harness doesn't exercise moves. It stubs netif_carrier_off() and mt_report_disconnected() as no-ops and never sets roam, so the new branches compile but don't run. This backs up Ryan's point 5.

Process nits:

  • b2f328c repairs the harness build that 079aa63 broke. Squashing them would let every commit pass CI on its own.
  • Both commits carry Co-Authored-By: alongside Assisted-by:, which already covers it. Kernel convention is Assisted-by: alone.

On roam_landed: marking local disconnects as from the AP is visible to every nl80211 listener, and wpa_supplicant handles those differently from local ones. I'd note the iwd in_reassoc bug next to roam_landed, so it gets removed once the iwd fix ships.

@RyanTheTide RyanTheTide left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still approving on b2f328c. The new commit only adds stubs to the connect result harness so it builds with the move state, and the driver code is the same as what I reviewed. The follow ups from my earlier review still apply, including a harness case that actually exercises a move.

iwd roams a fullmac device by sending CMD_CONNECT with the previous BSSID.
mt_connect() refused that with -EBUSY, and iwd takes a failed reassociation
as the end of the connection and sends no disconnect, so cfg80211 and the
firmware stayed connected. Every later connect then failed with -EALREADY,
and the device stayed offline until the firmware lost the AP or iwd exited.

Do what the request asks. Leave the current AP and join the one requested,
keeping the carrier up so NetworkManager doesn't restart DHCP. Report the
end of the move through the connect result, which is what iwd's
reassociation waits for: cfg80211_roamed() would leave its connect callback
installed. A move that can't start, fails or is cancelled still ends the
connection, so all three agree.

iwd keeps its reassociation mark after a move, and drops disconnects not
reported as from the AP, so report them that way on a connection a move
made. Also refuse a reassociation that arrives after the link was lost, and
log moves and connects refused at admission.

Teach the connect result harness about the move state; it doesn't exercise
moves.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Scott Jones <scottajones@gmail.com>
@scottjones

Copy link
Copy Markdown
Collaborator Author

Squashed and force-pushed as 747e84f, one commit on 1630ee1. From the reviews: the failed or cancelled move now passes the saved reason, WRITE_ONCE on roam_moving, the three disconnect reports go through mt_report_disconnected() (with roam_landed cleared first so their local flag doesn't change), the response_lock comment is reworded, the iwd in_reassoc note sits beside roam_landed, and Co-Authored-By is dropped. The harness fix is in the same commit.

Not in this push: the double-disconnect race, the deferred regulatory change and KUnit for moves.

Retested on a J700 with this tree. The results and a line on a move during a scan are in the description.

@maralcbr maralcbr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving 747e84f. The review fixes check out: all three reports go through mt_report_disconnected() with roam_landed cleared first, a failed or cancelled move passes the saved reason, the roam_moving writes use WRITE_ONCE, and the lock comment now matches the code.

One nit for a follow-up: the early refusals of a move (RF not allowed at link.c:811-813, and the -EINVAL validation failure) go through mt_disconnect() without clearing roam_landed. On a connection a move made, those are still reported as from the AP, while a refusal at admission is reported as local. It's harmless for iwd. Clearing roam_landed there too would make them match.

The double-disconnect race, the deferred regulatory change and KUnit coverage for moves remain follow-ups.

@maralcbr
maralcbr added this pull request to the merge queue Nov 1, 2026
Merged via the queue into omacom:integration/aurora with commit 4fdd0fc Nov 1, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants