Skip to content

J813: N1 Wi-Fi and Bluetooth on the MacBook Air M5, with real MSIs - #998

Merged
Acelogic merged 22 commits into
aurora-silicon:aurora-wipfrom
sithglan:j813/rb/wireless
Nov 6, 2026
Merged

Acelogic merged 22 commits into
aurora-silicon:aurora-wipfrom
sithglan:j813/rb/wireless

Conversation

@sithglan

@sithglan sithglan commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

The MacBook Air M5's N1 Wi-Fi and Bluetooth: Acelogic's native-PCIe N1 driver with Joshua Warren's fixes (authors kept; the driver part of each commit from the J813 line of linux-aurora), btcentauri, the s2idle fixes, and real MSIs: a small MSI controller for the bootloader-trained apcie port 0 (pcie-apple-msi, with its binding) and an interrupt-driven data path (65 -> 580 Mbit/s).

Integration's drivers/net/wireless/apple/centauri only has a Makefile and a header for the T6050 APIF driver, whose Kconfig depends on PCIE_APPLE_APIF, which integration does not have; this fills the directory with the native driver. The T6050 APIF variants are not part of this.

Acelogic's commits carry no Signed-off-by of their own; I have added mine as the one forwarding them. Acelogic, please add yours if you agree.

Tested on the MacBook Air 13-inch M5 (J813): the nine J813 branches (nvme, platform, dts, soc, wireless, media, sound, display, gpu-m5) merged onto integration/aurora 11eccbe, built with the J813 config (7.3.0) and booted through aura's aura-try trial boot. systemd reaches running: NVMe root, the N1 Wi-Fi (association; Bluetooth still needs the N1 firmware started first, as on the current kernel), the AVD and ISP drivers, PSCI cpuidle, the E/P states, CLPC/PMC and the PMP come up, the M5 GPU backend starts firmware 25G76, and drm/apple drives the panel.

Two sound findings from that boot, neither yet resolved: the installed aura predates the amp-level injection now in aura main, so macaudio disables the speakers (the card binds, but the DT this boot used carries no apple,speaker-config); and opening the microphone fails with LEAP leaf 2 did not power up: Error(110), while the same T8152 driver on the J813's current 7.1 kernel records.

Stability caveat: a second boot of the same image hung about 38 s in, just after the NTP step set off the daily fstrim/apt timers, with no panic record (no pstore). The first boot went through the same timer burst and stayed up for minutes. Until the hang is understood, the combined tree is not proven stable across reboots.

cc @Acelogic @DjDeveloperr @RyanTheTide @LiamRay10

AP-switching fix and combined hardware qualification

The disconnect command previously sent TLV 328 with a zero timeout. In the N1 firmware, zero bypasses the deauthentication/disassociation transmit path and immediately tears down the link. This can leave the old AP retaining the station while Linux associates with another BSSID, breaking the return path despite successful WPA and DHCP. The appended fix restores the firmware's 500 ms disconnect budget so the management frame can be sent before teardown. It does not add a userspace delay or disable power saving.

Validation of the combined public-base + #998 + #1013 source tree on physical J813 and J873, using fresh guarded RAM boots:

  • Kernel, 190 modules and both board DTBs built; 185/185 existing host-suite jobs passed locally. No hosted check run was available for this revision, so this is not a hosted-CI result.
  • 16 AP changes on a TP-Link Deco mesh, including four cross-AP cases per board and shared-AP controls: 240/240 ping replies, immediate first replies, and 32 SHA-256-verified 8 MiB transfers. Wi-Fi power saving remained enabled.
  • Ten-minute simultaneous bidirectional TCP: 30.00/30.00 Mbit/s received at a configured 30 Mbit/s cap, with concurrent Bluetooth LE discovery for 15 seconds per board. These are capped smoke tests, not peak-throughput measurements.
  • J813 passes 16/16 initial GPU/health checks; J873 passes 23/23. Both repeat the GPU smoke after the radio soak with 12,288 exact pixels each, no kernel taint and no recorded kernel crash.

Earlier failed trials and packet captures are retained privately. Isolated packet loss also occurred without an AP change during control testing; this result qualifies the reproduced handoff stall, not loss-free RF operation. Bluetooth pairing/ACL/audio, suspend/resume and installed-rootfs stability remain outside this qualification. No firmware or internal-disk installation was performed.

Acelogic and others added 21 commits November 5, 2026 20:05
[Thomas: the driver part of 9f4a5246489a on the J813 line of linux-aurora]

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…g-up

[Thomas: the driver part of c0f6eceacec7 on the J813 line of linux-aurora]

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J813 constants of the port cycle and the ROM boot word were
hard-coded. Move them into one table with a row per machine that has
an N1 and add the J873 (the M6 Mac mini): firmware file name, the
word written to BAR+0x807c, the root port device ID, and the port,
PHY and PERST# addresses with the one differing config +0x130 value,
all taken from the machine's ADT.

Measured on an M6 Mac mini with the rest of this series: the ROM
stage, the port cycle and the Control firmware reach their running
stage. Needs the T8152 SoC and PCIe nodes (base layer).

[Thomas: the driver part of 70d215465d13 on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J813 port cycle zeroes 16 words at port+0x3000 and 256 words at
port+0x3800. On the J873 those are the RID2SID and MSIMAP tables that
pcie-apple programmed. The cycle then drops the stream entry of
function 0 and its DMA arrives as SID 0 (DART "BPF reject"). A board
with keep_port_tables set gets both tables written back after the cycle.

Measured on an M6 Mac mini: with this change the Control firmware
reaches its OS stage after the cycle.

[Thomas: the driver part of fcbcd1529d03 on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Each function of the N1 has its own IOMMU stream on the J873 (RID2SID
entries 1, 2 and 3 and separate DART domains). A buffer that the
firmware of a function reads or writes must be mapped through that
function's own struct device. Function 0 is Control, function 1 Alpha
(Wi-Fi) and function 2 Beta (Bluetooth).

Measured on an M6 Mac mini: Control and Alpha complete their IPC
handshakes with this change.

[Thomas: the driver part of 12a5fd0d8fc0 on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Function 0x1903 is the Beta (Bluetooth) function and btcentauri claims
it. apple_centauri needs only the functions 0x1900, 0x1901 and 0x1902,
so stop it from claiming 0x1903.

Measured on an M6 Mac mini: btcentauri binds 0x1903 and registers its
HCI transport.

[Thomas: the driver part of 0f318aebe42f on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The N1 reports legacy scan responses in LE Extended Advertising
Reports whose event type has the low byte 0x18 (legacy PDU and scan
response, neither connectable nor scannable) and other bits in the high
byte (seen: 0x2518, 0x2618, 0x2718; accepted legacy types arrive as
0x2613 and 0x2610). The core accepts only 0x1a and 0x1b for a legacy
scan response and drops the others as "Unknown advertising packet type".
Report the low byte 0x18 as 0x1a and keep the high byte.

Measured on a Mac mini M6 (J873), 15 s LE discovery with btmgmt (LE on,
power on, find), native startup and the LE and auxiliary rings armed:
- without the change: 72 to 74 reports, 71 distinct addresses, 12 to 13
  "Unknown advertising packet type: 0x18" kernel lines per run;
- with the change: 89 reports, 83 distinct addresses, 0 such lines;
  btmon shows the scan responses as event types 0x251a, 0x261a and
  0x271a.
A first version of this patch compared the whole 16-bit type with 0x0018
and never matched; the high byte has to be ignored.
Not measured: device names or scan response data content, connection,
pairing, audio.

[Thomas: the driver part of d3d8dab9dbf5 on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The macOS 27.0 WLAN adapter differs from the 26.6.1 one the J813 recipe
came from. startInterface carries the three link addresses in TLV 386
(was 385). The scan request (command 0x1000f) carries a channel-list
TLV 0x104 and an OCE TLV 0x165 after the fixed 14 bytes. Both are
selected per board.

Measured on an M6 Mac mini: start and scan commands are accepted. Whether
the TLVs are required is untested.

[Thomas: the driver part of 4061bf02639e on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The Alpha firmware of the J873 asserts in SCHED_Req_Add when the scan
state machine selects a dwell of zero. The scan request carries the
active minimum and maximum dwell at frame +0x20 and +0x22 and the
passive dwell at +0x24 (in ms, as macOS fills them from the scan
request). The driver sent 0 as passive dwell. The whole firmware then
trapped at the first scan with PHY, UMAC crash handler and SCHED_Req_Add
assertions.

Send 110 ms active and 110 ms passive. Also correct the comment in
centauri.h that blamed the missing TLVs.

Measured on an M6 Mac mini with the original firmware table and no
calibration data: the scan returns rc=0 with 24 channels and 51 BSS
(31 to 51 BSS in three runs).

[Thomas: the driver part of 6d4959cc6edb on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The macOS 27.0.1 adapter sends the MLD configuration TLV 0x178 of the
join command (0x20000) with 14 bytes: the 12 default bytes and two zero
bytes. With 12 bytes the Alpha firmware of the J873 rejects the join
with firmware_status 347 and the 4-way handshake never starts.

Measured on an M6 Mac mini: a WPA2-PSK join (CCMP, 5 GHz) completes the
4-way handshake. About 15 s after the join the Alpha firmware still
traps (PHY5G irq_isr.c:105, PHY2G power_manager.c:394); that is not
fixed by this change.

[Thomas: the driver part of 57482a6f8eda on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Without the host power state the Alpha firmware treats the host as asleep,
powers the PHYs down and traps about 15 s after a join (PHY5G irq_isr.c:105,
PHY2G power_manager.c:394, and in a log with a post-join frame also LMAC2G
"PMGR - Assert while waiting"). Before the join, send the three commands that
the host_wakeup and tx_config debugfs files already send: command 6 with a
zero body (host power state awake), command 43 (packet lifetime) and command 52
(control profile). The debugfs files stay; the module skips what they applied.
The new host_setup parameter (default on) turns this off.

Measured on a Mac mini M6 (J873), WPA2-PSK, 5 GHz, with the data rings set up
through the data_probe files as before:
- the firmware stayed alive (stage 2) for the whole test, about 17 minutes
  connected in the run with the three commands sent through debugfs, and for
  the whole test in the run with this change; before, it trapped within 15 s
  in 14 of 14 variants;
- DHCP lease over Wi-Fi; ping through wlan0; iperf3 to a host on the same
  network bound to the wlan0 address: 44.7 / 58.9 Mbit/s up / down with the
  debugfs commands, 43.7 / 40.2 Mbit/s with this change;
- a 600 s iperf3 at 20 Mbit/s moved 1.40 GBytes without a stall.
Not measured: more than 20 minutes, suspend, roaming, 2.4 GHz, WPA3. Some
ping windows lost packets (0 to 50 percent in 10 to 20 packet samples).

[Thomas: the driver part of 1242f12e876b on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
After the join the firmware runs its own roam and background scans. No host
roam profile exists, and each scan takes the radio off channel for seconds.
Send ACI command 0x20016 (no body) once the join succeeded, unless the new
roam_scan parameter is set (default off, so the scan is off).

Measured on a Mac mini M6 (J873), WPA2-PSK, 5 GHz, two boots with the
host-awake commands from the previous patch, a ping every 0.5 s for 120 s to
the Wi-Fi gateway, bound to wlan0:
- without the command: 240 sent, 68 received (71.7 percent loss), received
  per 10 s: 13 0 13 0 10 3 3 10 0 13 0 3 0 of 20;
- with the command in a separate boot: 240 of 240 received, rtt 9 to 73 ms,
  every 10 s bucket 20 of 20;
- with this patch (default settings, third boot): 240 of 240 received, rtt
  9.5 to 96 ms, 20 of 20 in every bucket.
This is a choice for a stationary machine: with the scan off the firmware does
not look for a better access point. A host roam profile (command 0x20014)
would be the way to bring roaming back; it was accepted by the firmware but
not tested as a replacement.

[Thomas: the driver part of 00c55963147d on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A connection needed three manual steps through debugfs: the passphrase file,
the data_probe ids 76, 24, 15 and 12 before the join, and id 25 after it. Do
the ring setup from the connect path (parameter auto_rings, default on): the
four rings under the connect lock before the join, the normal TX ring once the
connection result is reported, so the join stays inside the supplicant's
timeout. The data_probe files keep working and are skipped for rings that are
already open. The data_probe writer is split into a helper that the connect
path calls with the lock held.

The firmware accepts a join with the PMK from the supplicant alone, so the
passphrase debugfs file and TLV 313 are gone.

Measured on a Mac mini M6 (J873), WPA2-PSK, 5 GHz, wpa_supplicant started
with a PSK configuration and nothing else (no passphrase file, no data_probe,
no host_wakeup or tx_config write): the join completed, DHCP gave a lease, the
log shows the four rings at join time, the host-awake and TX default commands,
the roam scan switch-off and ring 25 after the result; 20 of 20 pings through
wlan0 came back and iperf3 gave 56.3 Mbit/s up and 38.4 down over 10 s each.
Measured on the same machine with the module installed and the standard
services enabled (wpa_supplicant-nl80211@wlan0 and systemd-networkd, nothing
started by hand): after one reboot wlan0 had its address 18 s after boot; the
log shows the rings, the host-awake and TX default commands, the roam scan
switch-off and ring 25 between 12.6 s and 17.0 s; 20 of 20 pings through wlan0
came back and iperf3 gave 54.8 Mbit/s up and 40.2 down over 10 s each.
NetworkManager could not be used on that image: its iwd backend does not start
on the test kernel, which lacks CONFIG_CRYPTO_USER_API_HASH,
CONFIG_CRYPTO_USER_API_SKCIPHER and CONFIG_KEY_DH_OPERATIONS among others.

[Thomas: the driver part of 8b8ec1a2ee3e on the J813 line of linux-aurora]

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
wavemon and iw had nothing to show for the N1: the driver implemented no
get_station or get_channel. Report the packet, byte, failure and drop
counters the data path already keeps, the signal of the joined BSS as
cfg80211 has it from the last scan (no firmware query for live link
statistics is known yet), and the joined channel at the 20 MHz the
driver associates with. Bitrates and transmit power stay unreported.

[Thomas: the driver part of 767a663a63de on the J813 line of linux-aurora]

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J873 device tree now uses the machine compatible from the binding,
"apple,j873g" (after the ADT target-type J873g). Match that in the
centauri board table and in btcentauri.

[Thomas: the driver part of 9e911df6e4c0 on the J813 line of linux-aurora]

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Without PM callbacks the PCI core's default suspend disables the
function, which turns bus mastering off under the running N1 firmware.
The firmware cannot be restarted without the bootloader's link training,
and after resume every ring was dead (each scan failed with -EIO).

Save the config space ourselves, so the core leaves the function enabled
and in D0, and keep the PCIe host's power domains up through the noirq
phase (device_set_awake_path()).

Tested on J813: Wi-Fi associated and passing traffic after s2idle.

[Thomas: the driver part of fd2382058a0a on the J813 line of linux-aurora]

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
As for the Wi-Fi functions: without PM callbacks the PCI core turned bus
mastering off at suspend, and the next poll stopped the transport for
good. Save the config space so the function stays enabled and in D0,
keep the PCIe host powered, and pause the poll work across the sleep.

Tested on J813: hci0 UP RUNNING after s2idle.

[Thomas: the driver part of c1f436307d70 on the J813 line of linux-aurora]

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
On T8142 (J813) the bootloader trains apcie port 0 and Linux drives the
root port as a generic ECAM host, which has no MSI support: the N1's
functions could only be polled. The port still turns writes to the MSI
doorbell into AIC interrupts through its MSI map, as on T8140/T602X.

Add pcie-apple's MSI parent domain as a standalone driver
("apple,t8142-pcie-msi"): it programs the port's MSI address, MSI map
and MSI enable and parses msi-ranges like pcie-apple. It depends on
PCIE_APPLE=y for the doorbell address and the DART's reserved doorbell
IOVA.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The data rings were serviced by a 20 ms delayed work (24 ms at HZ=250)
that drains at most 127 RX completions and reclaims the 128 TX slots per
pass, which capped the J813 at ~65 Mbit/s with 0-24 ms of added latency.

When the port has an MSI controller, give Control and Alpha up to 16
MSIs each (the port has 32) and kick the data worker from every MSI;
Beta and ports without a controller keep the DMA mailbox. The poll
stays as a fallback. The port cycle's reset clears MSICFG, MSIADDR and
the MSI map, so save and restore them on every board (the RID2SID table
still only with keep_port_tables).

J813: iperf3 65 -> 579 Mbit/s to the host, 60 -> 451-505 Mbit/s from
it, ping 15.7 -> 2.9 ms average.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The data rings tell the firmware to signal Alpha vectors 3 and 8. On the
J873 the 10GbE NIC shares pcie-apple's 32 vectors with the N1; Control
asked first and took the only free block of 16, Alpha got one vector,
and the Alpha firmware faulted (stage 3) as soon as the first data ring
was opened, so every later command failed with -EIO.

Allocate Alpha before Control and only as a whole block of 16; a
function that cannot get one keeps the DMA mailbox. On the J873 Control
now stays on the mailbox and Alpha gets its 16 vectors.

J873 (Mac mini M6, 5 GHz ch 44, -34 dBm): iperf3 60 -> 511 Mbit/s to
the mini, 60 -> 530 Mbit/s from it, ping 17 -> 2.4 ms average.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The MSI map of one Apple PCIe root port that the bootloader trained and
Linux drives as a generic ECAM host (the J813 N1 on T8142 apcie port 0).

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…nect

The disconnect request sends TLV 328 with a zero timeout. The firmware
uses zero to bypass its deauthentication/disassociation transmit path
and immediately tears down the link. The old AP can retain the station
while Linux associates with another BSSID, leaving cross-AP traffic
without a working return path despite a completed WPA handshake.

Use the firmware's 500 ms default disconnect budget so it can transmit
the management frame and wait for completion before tearing down the
link.
@Acelogic
Acelogic merged commit ffe97f2 into aurora-silicon:aurora-wip Nov 6, 2026
RyanTheTide added a commit that referenced this pull request Nov 6, 2026
Carries the work merged on aurora-wip since #1018 into the release
branch: M5 T8142/J813 bring-up (#995 #996 #997 #998 #1001 #1002 #1003),
T8152/J873 bring-up (#984 #1009 #1013 #1022), J813 AVD/ISP (#1026)
and the selftest harness updates (#1014). The merge is clean; the 13
integration/aurora commits since #1018 are kept.

Signed-off-by: Ryan Murray <81541646+RyanTheTide@users.noreply.github.com>
Assisted-by: LLM
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