Skip to content

PCI: apple: bring up T600x/T602x PCIe-C tunnels without an m1n1 handoff - #10

Merged
iconidentify merged 8 commits into
iconidentify:custom/sepfrom
wesleygrimes:j416s-pciec-kernel-init
Oct 3, 2026
Merged

iconidentify merged 8 commits into
iconidentify:custom/sepfrom
wesleygrimes:j416s-pciec-kernel-init

Conversation

@wesleygrimes

@wesleygrimes wesleygrimes commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

Brings up the Thunderbolt PCIe tunnel (PCIe-C) on M2 Pro/Max (T602x) and M1 Pro/Max (T600x) without an m1n1 handoff, building on the existing M1 (t8103) path. Devices behind Thunderbolt 3 docks and displays now come up. For an Apple Studio Display, that's its USB hubs and USB-C ports, camera, speakers and microphone.

PCIe-C on these chips requires apple,pciec-preinit-status = 1 from m1n1, and no m1n1 sets it, so today the tunnel is always refused. This series is opt-in with pcie_apple.tunnel_kernel_init=1. With the parameter off, nothing changes on any machine.

Commits

Commit What it does
PCI: apple: Cold-init T600x/T602x PCIe-C ports without an m1n1 handoff Adds the parameter and the predicate apple_pcie_tunnel_needs_cold_init(), which also refuses ports whose DART has no apple,tunable. Reuses the t8103 cold-init, prepare and setup paths, with one difference: these ports must report RUN before the root-complex config space is written, because the t8103 order hard-resets a J416c. They use Intr2AXI instead of oe-fabric. The t8103 power and resume paths (pcie->kernel_init) are not used.
thunderbolt: apple: Accept PCIe-C kernel cold init on T600x/T602x ACIO populates the tunnel for these ports.
PCI: apple: Keep cold-initialized PCIe-C functions runtime-active A PCI bus notifier holds a runtime-PM reference on each tunneled function. Without it, after a display power cycle the Titan Ridge xHCI suspends, its wakeup never arrives, and the display's hub never comes back. It's a notifier rather than a fixup so that it also works when pcie-apple is built as a module.
PCI: apple: Keep cold-initialized PCIe-C links up through suspend-to-idle Uses the existing s2idle keep-link path. The stop/restart path left the port unable to train until reboot. If the link is lost while asleep, the host fails like a surprise unplug instead of restarting in place.
PCI: apple: Don't restart a quiesced PCIe-C host on resume Pre-existing on t8103 too. A host quiesced by tunnel deactivation had all its ports restarted in resume_noirq(), which fails without a tunnel and leaves the host marked resume_failed. apple_pcie_tunnel_restore() already restarts them on reactivation.
arm64: dts: apple: t602x: Add PCIe-C DART tunables m1n1 doesn't copy dart-tunables-instance-0 for dart-apciec*, and without it the DART fails to probe. Values were read from J416c and J414s ADTs and are identical on both machines and all ports.
arm64: dts: apple: t600x: Add PCIe-C DART tunables Values read from a J314s (MacBookPro18,3) ADT. They differ from T602x.
tools: aurora-tb: Add a Thunderbolt PCIe test report tools/aurora-tb/tb-pcie-report, a read-only report for testers, with a README.

Why a parameter rather than a DT property: m1n1 bundles one DTB into boot.bin for every installed kernel, so an older kernel would also see such a property. The DART tunables are harmless to older kernels, because without the handoff they never populate the tunnel.

Testing

MacBook Pro 16" M2 Pro (apple,j416s), Aurora 11.25 base and config, Apple Studio Display:

  • Hot plug after boot, and connected at boot: the port cold-initializes, the DART probes with its tunables, and the xHCI (8086:15f0) enumerates the display's USB2/USB3 hubs and 05ac:1114: UVC camera, USB audio (speakers and mic) and its HID interfaces.
  • All three USB-C ports (tunnel-0/1/2).
  • Display power cycle: the tunnel tears down and comes back, and the hub, camera and audio return on their own.
  • s2idle (from a few seconds up to 3 min): the link stays up, and the hub, camera and audio keep working.
  • Unplugging the display while asleep: handled as an unplug, and replugging works.
  • Without the DART tunables: the DART fails to probe and the xHCI dies about 20 s later. Without the runtime-PM reference: after a power cycle the hub only returns once power/control=on is set.

Not tested

  • T600x (M1 Pro/Max) hardware. The tunables come from a macOS dump, and the cold-init order is the T602x one.
  • Other T602x machines, including the 4th port on desktops (apciec3, which has no tunables, so it's refused).
  • pcie-apple built as a module.
  • The lost-link-while-asleep branch. In testing, an unplug during sleep was caught by the normal unplug path instead.
  • The quiesced-host resume path.

Testers: boot with pcie_apple.tunnel_kernel_init=1, plug in a Thunderbolt 3 dock or display, and run tools/aurora-tb/tb-pcie-report.

Studio Display brightness isn't part of this series.

@wesleygrimes
wesleygrimes force-pushed the j416s-pciec-kernel-init branch from a5563cb to b4a0f46 Compare October 2, 2026 22:35
@wesleygrimes
wesleygrimes marked this pull request as draft October 2, 2026 22:36
T600x and T602x PCIe-C (the Thunderbolt PCIe tunnel) is only brought up
after an m1n1 handoff ("apple,pciec-preinit-status" = 1), and no m1n1
performs one. The tunnel is always refused, so PCIe devices behind
Thunderbolt 3 docks and displays (their xHCI, audio, cameras, network)
never appear.

Add pcie_apple.tunnel_kernel_init (default off). When set, ports on
T6000/T6001/T6020/T6021 (M1 and M2 Pro/Max) without a handoff are
cold-initialized by the kernel through the existing t8103 paths:
apple_pcie_tunnel_prepare() clocks the port before its DART probes, and
apple_pcie_setup_port() cold-initializes a port that is not running. It
is a parameter rather than a DT property because m1n1 hands every
installed kernel the same DT. Ports whose DART has no "apple,tunable"
are refused: m1n1 does not provide them, and the DART cannot probe
without them.

apple_pcie_tunnel_cold_init() gains one ordering difference for these
ports: they must report RUN before the root complex config-space
tunables are written, which hard-resets a J416c otherwise. T600x/T602x
have no oe-fabric region and pulse Intr2AXI instead, and they keep the
0x208 reset table value. The t8103 power and resume paths, selected by
pcie->kernel_init, are not used.

Tested on a MacBook Pro 16" M2 Pro (j416s) with an Apple Studio Display
on each of its three USB-C ports, plugged in after boot and connected
at boot. T600x is untested.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
ACIO only populates the tunneled PCIe-C host after an m1n1 handoff or
for "apple,pciec-kernel-init" (t8103) ports. Also accept ports that the
PCIe driver will cold-initialize itself, which it does only when booted
with pcie_apple.tunnel_kernel_init=1, and word the disabled-tunnel
messages so that they no longer name the m1n1 handoff as the only way.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
A function behind a cold-initialized T600x/T602x tunnel stays in D0
(NO_D3) when it runtime-suspends, but its wakeup never arrives. When a
Studio Display is power-cycled, its Titan Ridge xHCI runtime-suspends
before the display's hub reconnects and never sees the hub, so the
display's USB devices do not come back.

Hold a runtime PM reference on each function for as long as it exists,
from a PCI bus notifier registered before the host is scanned, so that
neither the driver nor userspace power policy (power/control = auto)
can suspend it. A bus notifier rather than a PCI fixup, because fixups
in a module are never run.

Tested on j416s with an Apple Studio Display: after a display power
cycle its hub, camera and audio come back by themselves.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
…idle

In suspend-to-idle ACIO and the router links stay powered, and t8103
already leaves a healthy tunnel running instead of stopping the port.
Cold-initialized T600x/T602x ports took the stop path. On resume the
router rebuilt its tunnels, the in-place restart brought the link up
only for it to drop again, the xHCI behind it was lost, and that tunnel
instance could not train a link again until reboot.

Let these ports keep their link through suspend-to-idle too, so the NHI
keeps its tunnels and nothing is retrained. If the link was lost while
asleep anyway, fail the host like a surprise unplug rather than
restarting it in place; ACIO then revalidates the connection and
replaces it.

Tested on j416s with an Apple Studio Display: after 20 s and 3 min
suspends its hub, camera and audio keep working, and unplugging the
display while asleep is handled as an unplug.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
When ACIO deactivates the PCIe tunnel while the cable stays connected,
apple_pcie_tunnel_quiesce() removes the hierarchy and stops the ports,
leaving the host bus_stopped until apple_pcie_tunnel_restore() restarts
them on reactivation. resume_noirq() nevertheless restarts every port
after a system suspend. Without a tunnel that fails, sets
resume_failed, and the host then refuses restore() until it is
replaced.

A quiesced host has nothing to resume, so leave its ports stopped.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
The tunneled PCIe-C DART requires "apple,tunable". iBoot adds
"dart-tunables-instance-0" to the ADT at boot, but m1n1 does not copy
it for the dart-apciec* nodes. Without it the DART fails to probe and
the xHCI behind the tunnel dies after its command ring times out.

Add the values to the apciec0..2 DARTs of every T602x die. They were
read from the ADTs of a J416c (T6021) and a J414s (T6020), and are
identical on both machines and for all three ports; on j416s they bring
up a Studio Display's xHCI. apciec3, present on the desktop machines,
has no values yet, so pcie_apple.tunnel_kernel_init refuses that port.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
As on T602x, the tunneled PCIe-C DART requires "apple,tunable", which
iBoot adds to the ADT at boot as "dart-tunables-instance-0" and m1n1
does not copy for the dart-apciec* nodes.

Add the values to the apciec0..2 DARTs of T6000/T6001. They were read
from a J314s ADT and are identical for all three ports. They differ from
the T602x values. apciec3 has no values yet, so
pcie_apple.tunnel_kernel_init refuses that port.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
A read-only script for testers of pcie_apple.tunnel_kernel_init on
T600x/T602x: it checks the machine and parameter, lists the DART
tunables per tunnel and the PCI and USB devices behind the tunnels,
collects the relevant kernel log, and writes a markdown report. When a
tunnel has no DART tunables, it prints the macOS ioreg command that
reads them.

Signed-off-by: Wes Grimes <wesgrimes@hey.com>
@wesleygrimes
wesleygrimes force-pushed the j416s-pciec-kernel-init branch from 5f07612 to c4726ef Compare October 3, 2026 00:41
@wesleygrimes wesleygrimes changed the title PCI: apple: bring up T602x PCIe-C tunnels without an m1n1 handoff (Studio Display on j416s) PCI: apple: bring up T600x/T602x PCIe-C tunnels without an m1n1 handoff Oct 3, 2026
@wesleygrimes

Copy link
Copy Markdown
Author

Known compromises in this series, and the cleaner follow-ups for each:

  1. DART tunables are hardcoded in the DT (t602x-/t600x-pciec-dart-tunables.dtsi). The binding expects the bootloader to provide them. The proper fix is for m1n1 to copy dart-tunables-instance-0 for dart-apciec*, as it already does for the other PCIe-C tunables. The DT files could then be dropped, and every machine would get its own values.
  2. Opt-in is a boot parameter rather than a DT property. m1n1 hands the same DTB to every installed kernel, so a DT property would also reach older kernels. Once kernels without this series are gone, the parameter could become a default or a DT property.
  3. Machine compatibles are checked in the driver (apple,t6000/t6001/t6020/t6021), because T600x and T602x share apple,t6000-pciec. Adding per-SoC compatibles (e.g. "apple,t6020-pciec", "apple,t6000-pciec") to the binding and DTs would remove that check.
  4. Tunneled functions are kept runtime-active instead of being woken. The underlying problem is that a runtime-suspended function behind the tunnel never gets its wakeup (PME), so its runtime PM is blocked. Delivering that wakeup would let these devices runtime-suspend again. The cost meanwhile is a little power while a Thunderbolt device is connected.

The RUN-before-config-space order in the cold init is also empirical: writing the RC tunables first hard-resets a J416c. It isn't documented anywhere.

None of these affect machines booted without pcie_apple.tunnel_kernel_init=1.

@iconidentify iconidentify left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks @wesleygrimes, this is great work, and thanks for the "known compromises" note. We ran two independent reviews.

Already in good shape:

  • It merges cleanly onto custom/sep and onto our Thunderbolt branch (aurora-silicon#8), and builds with W=1 clean.
  • DTB changes are confined to the 11 T600x/T602x boards. t8103, t8112, Neo and the Ultras are byte-identical.
  • No hunk touches the DP tunnel paths or the Type-C display routing.
  • With the parameter off, T600x/T602x behave as today.

M1 Pro data check: the values in t600x-pciec-dart-tunables.dtsi match a second J314s (MacBookPro18,3, macOS 27.0.1) exactly. That covers all three tuples (0x20c/0x220/0x224 with the same masks and values) on apciec0–2, and apciec3 has none. We're installing Omarchy on that J314s now, to run the cold init with a Thunderbolt 3 dock. So please keep T600x in, pending that run.

Requested changes

  1. PR text and the M1 change. "Don't restart a quiesced PCIe-C host on resume" changes t8103 even with the parameter off (pcie-apple.c resume_noirq).

    • It's a correct fix: today a host quiesced by tunnel teardown gets resume_failed, and the next restore returns -EIO until replug.
    • Please say so in the description.
    • It's the same change as rryan's 18b9e789 in aurora-silicon#50 (Sep 28). Please use his commit (see 6).
    • We'll test it on an M1 with a TB3 dock: tunnel deactivated, suspend, resume, reactivate.
  2. A lost link at resume on cold-initialised hosts (~3104 and ~3184).

    • The stop/restart path is still reachable, with s2idle_keep_link=0 or a stale link-down bit at suspend.
    • Your commit says that path leaves the port unable to train until reboot, and T602x has no PCIe-C block reset.
    • Please fail such a host (-ENOLINK, handled as a surprise unplug) instead of restarting it.
  3. A failed cold init leaves the port clocked (~950–959). On the next plug, prepare and setup_port see it running and skip cold init, so the root-complex tunables are never written. Please stop the port on failure.

  4. Runtime-PM reference (~2157–2176, ~2493).

    • It pins every tunneled function, NICs and NVMe included, and overrides power/control=auto and TLP. That costs battery whenever a dock is connected.
    • Could it be limited to the xHCI class that loses its PME, or use pm_runtime_forbid() so a user can override it?
    • Your note 4, finding the root cause, is the real fix later.
  5. Nits:

    • The 250 ms busy-wait under pcie_tunnel_lock (~951) should be read_poll_timeout.
    • pr_warn_once (~110) hides the second and later refused ports.
    • tb-pcie-report reports M1 DART tunables as missing and sends M1 testers to macOS, but M1's PCIe-C DART has none by design. Please skip that check on t8103.
    • An unplug log, please. apple_nhi_pci_tunnel_deactivate() holds tb->lock while waiting up to 1 s for the reset ack, which is where rryan logged -110. Could you share a log of an unplug or display power cycle with the parameter on, so we can see whether DP teardown or replug stalls?
  6. Credit. aurora-silicon#50 (rryan, Sep 27) got to the same place first:

    • the RUN-before-config-space order (the J416c hard reset comes from his reports);
    • byte-identical T602x DART tunables;
    • the same quiesced-host resume guard.

    Your series is the better base: safer opt-in, built on #8's functions, plus s2idle handling. Please add Co-developed-by: RJ Skerry-Ryan <rryan@alum.mit.edu> to the cold-init commit, use his resume commit in place of yours, and credit him for the T602x tunables. We'll close #50 as superseded, with credit, when this lands.

After the fixes:

  • Default on: per our default-on policy, the plan is to turn this on by default for M2 Pro/Max once 2–4 are in, keeping the parameter as an off switch. T600x follows once the J314s run passes.
  • Upstream follow-ups: your known compromises are the right ones (m1n1 copying dart-tunables-instance-0, per-SoC compatibles instead of the compatible check).
  • On our side: we'll update our installer's test plan, which still calls the old "m1n1 handoff is not initialized" line normal.

Also, thanks for the Touch ID report on sepOS 27.0 (#8). It's the first M2-family keybag on 27.0.

iconidentify pushed a commit that referenced this pull request Oct 3, 2026
Supervised test build: 11.25.1 plus #10 (PCIe-C
cold init on T600x/T602x, opt-in with pcie_apple.tunnel_kernel_init=1),
for an M1 Pro (J314s) dock test. Prerelease only.

Signed-off-by: Chris Kearney <chris@kearneymail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwdG63dRaMrRbYrUBkSa1i
@iconidentify
iconidentify marked this pull request as ready for review October 3, 2026 07:02
@iconidentify
iconidentify merged commit 152b721 into iconidentify:custom/sep Oct 3, 2026
iconidentify added a commit that referenced this pull request Oct 3, 2026
linux-aurora 7.1.12.aurora2-11.31, built from fd04a98:
 - PCIe over Thunderbolt on M1 Pro/Max and M2 Pro/Max by default (#10)
   with the T600x DART window and dock readiness fixes;
 - igb and igc for Thunderbolt 3 and 4 dock Ethernet;
 - the MacBook Neo radios in the shared kernel (#11);
 - Neural Engine runtime PM (aurora-silicon#155) and the ANE
   enabled on the M1, M1 Pro and M2 Pro.

The installer checks a MacBook Neo for its own Wi-Fi firmware, calibration
and country files and prints the Neo's sleep, Wi-Fi and Bluetooth limits.
The test plan covers dock PCIe on the Pro/Max chips with exact log lines,
the Neural Engine on the newly enabled chips, and a MacBook Neo step.

libfprint, aurora-touchid and m1n1-aurora are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FwdG63dRaMrRbYrUBkSa1i
Signed-off-by: Chris Kearney <303316+iconidentify@users.noreply.github.com>
@iconidentify

Copy link
Copy Markdown
Owner

Merged and released in 11.31. Thank you, @wesleygrimes. Your cold init worked first time on an M1 Pro. Testing it there with docks that have several controllers inside found two bugs, now fixed in follow-up commits on custom/sep:

  1. DART table depth on T600x (ae0b063). The fixed PCIe-C topology sets up 42-bit, four-level tables, and the T600x PCIe-C DART walks only three levels. A dock's xHCI therefore faulted on its first DMA (NO PMD FOR IOVA) and then died. apple,dma-range = <0x0 0x0 0x1 0x0> on the four T600x DART nodes keeps the domain at three levels. T602x is unchanged.
  2. Bus scan before the dock is ready (f44aa74). On a CalDigit TS3 Plus, the FL1100 and ASM1142 controllers train their links after the tunnel does, behind switch ports that aren't hotplug-capable. The scan right after cold init missed them for good, so the USB ports they serve stayed dead. The one-second wait from the t8103 path only ran with kernel_init set. A shared helper now waits until the link has been up for one second, before the first scan and before the restore rescan, on every PCIe-C host. Your Studio Display has a single xHCI that's ready at link-up, which is why this didn't show up there.

Also:

  • tunnel_kernel_init is now on by default, with =0 as the off switch (77f974b). tb-pcie-report and its README are updated to match.
  • igb and igc are built for dock Ethernet.

Tested on builds carrying exactly this Thunderbolt code (11.29–11.31):

  • M1 Pro (J314s), CalDigit TS3 Plus: all five dock functions bind, with zero DART faults. A USB keyboard and mouse, webcam, audio, Ethernet at 1 Gb/s and a USB SSD all work behind it.
  • M1 Pro (J314s), CalDigit TS4: cold init on the new default with no parameter, and sleep/wake with the dock attached; both displays came back.
  • M1 13" (J293), TS4: sleep/wake with the dock attached, which exercises your resume change on t8103.

Still untested:

  • M1 Max and M2 Max;
  • an M2 Pro/Max with a multi-controller dock.

A request: 11.31 also turns the Neural Engine on for the M2 Pro. It has the same die-0 ANE as the M2 Max and uses the same ane_t6021 driver, but has never run on an M2 Pro. If you can test it on your J416s:

  1. It needs the ANE firmware, fetched with Joshua Warren's omarchy-ane-firmware-fetch from omarchy-ane.
  2. Then quote:
    modinfo -F filename ane_t6021
    ls -l /dev/accel/
    sudo dmesg | grep -iE 'ane_t6021|\bane\b'
    
  3. If you have omarchy-ane's tools, also run omarchy-ane-check --smoke.

A run with a multi-controller TB3 dock would be welcome too. The installer's --agent-prompt has the full plan. Reports go to https://github.com/iconidentify/aurora-linux/issues (the format is in #6).

🤖 Generated with Claude Code

https://claude.ai/code/session_01FwdG63dRaMrRbYrUBkSa1i

iconidentify pushed a commit that referenced this pull request Oct 8, 2026
ACIO returns -EAGAIN while its PCIe host is being populated or quiesced.
CD321x treats this as a failed cable and forces a complete disconnect on
the next status snapshot, resetting an otherwise unchanged USB4 session.

Keep the bounded fresh-read retry budget, but only request a forced
reconnect for non-deferred errors. Preserve any earlier genuine failure.
Notify the Type-C consumer after asynchronous PCIe activation succeeds so
an exhausted busy retry can read the current cable state again. Return
-EAGAIN when an allegedly populated host is not yet available, rather
than reporting false activation success.

Propagate partner-registration errors and add regression tests for busy
retries, completion after budget exhaustion and a busy retry following a
real failure.

[Chris Kearney: Ported onto custom/sep 1caa9fd (11.35) without code changes.
 The PR #10 cold-init acceptance and PR #15 follow-ups are retained.
 Review trial only: readiness and retry-budget concerns remain in the report.]
Signed-off-by: Chris Kearney <303316+iconidentify@users.noreply.github.com>

(cherry picked from commit cd2525c)
(cherry picked from commit 39176aa921904549e1eeead5a2a36bc3bac6e67f)
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.

2 participants