Skip to content

PCI: apple: t8140: bring the radios back after a sleep off resume_noirq - #1717

Merged
DjDeveloperr merged 2 commits into
integration/aurorafrom
fix/t8140-radio-async
Nov 12, 2026
Merged

DjDeveloperr merged 2 commits into
integration/aurorafrom
fix/t8140-radio-async

Conversation

@DjDeveloperr

@DjDeveloperr DjDeveloperr commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

On a DRAM-kept wake, the Neo (J700) brings its radio link back inside the PCIe host's resume_noirq: 110 ms, mostly the 100 ms the spec requires between PERST# and the first config request. The whole noirq phase, and everything after it, waited for that. This moves the radio bring-up into a work item, and only the radios wait for it.

  • e0ea9f47 PCI: apple: t8140: bring the radios back after a sleep off resume_noirq
    • resume_noirq restores the root port, as before, then queues the link-up, the bootstrap and the restore of the functions' config.
    • The bootstrap device, which the host already has a device link to as its supplier, waits for that work in its .resume. The radios sit below the host, so neither the host nor the radios resume before the link is back; the rest of the system resumes in parallel. The host and the bootstrap now resume asynchronously so that this wait blocks nothing else.
    • The PCI core's resume_noirq of the two functions still runs while their link is down:
      • Their saved state is held back until the work restores it. Otherwise the core retries every BAR write for 10 ms against the dead link.
      • Until then, the host answers reads of their PM control register with D0, which is what a function out of reset reports. So the core neither logs them inaccessible nor marks them D3cold.
    • The work waits for the host's resume_early before it touches the functions, so the PCI core's noirq pass over them has finished first.
    • A sleep right after a wake flushes the work first, and so does the recovery work.
  • 03b6aa37 PCI: apple: t8140: resume the host asynchronously only with its sleep path
    • Asynchronous resume is enabled only once the T8140 sleep path is fully set up, PM notifier included.
    • resume_early copes with a host that has no driver data.

Other Apple PCIe hosts are unchanged

  • Everything in pcie-apple.c runs only for the T8140 radio port. The sleep path (neo_cold, the radio work, neo_noirq_done, device_enable_async_suspend()) is set up in apple_pcie_neo_sleep_probe(), which runs only with a bootstrap supplier. apple_piodma_bootstrap_get() returns -ENODEV for any host that isn't apple,t8140-pcie.
  • apple_pcie_resume_early() is in the shared PM ops but does nothing without neo_cold.
  • The config-read intercept is in the shared ECAM ops but answers only while neo_radio_waking is set. Only the T8140 path's resume_noirq sets that flag, behind neo_sleep_armed, which only the T8140 PM notifier sets. Any other read goes straight to pci_generic_config_read(), after the same apple_pcie_lookup() that writes already do.
  • The bootstrap device (apple,t8140-pcie-bootstrap) exists only on the J700.
  • No other host was booted with this, so the above comes from reading the code, not from a test.

Measured on the Neo (J700), 2026-11-12

Kit: this commit on #1715 (both on cae47c00, together with #1700 and #1682's debug line), aura main Stage 2. One boot. Before = the same kit without this commit (#1715's measurements).

before this PR
dpm_resume_noirq (kprobes) 124.8 ms 14.0 ms
dpm_resume_noirq (power trace, median of 3) 124.5 ms; pcie-apple 110.2 ms 14.6 ms; pcie-apple 0.6 ms
dpm_resume (power trace, median of 3) 86.4 ms 111.1 ms; the bootstrap's wait for the radios is 80.7 ms of it
The radio work — 114 ms, ending 107 ms after the noirq phase

8 DRAM-kept wakes, medians in seconds after the PMU RTC alarm:

before this PR
Suspend exit 0.299 0.171 (all 8 in 0.169–0.177)
Internal DCP firmware up 0.210 0.053
KWin's mode set 0.358 0.232
Backlight on 0.486 0.328

Before, the wakes split into two groups about 45 ms apart. In the slow group the SPMI USB-C port controller's resume (sn201202x 1-0a, synchronous) took 28–51 ms instead of 4 ms, holding up every later synchronous device, DCP among them. With the resume phase starting about 110 ms earlier, it took 0.8 ms in every trace, and the 8 wakes form one group.

Soak, same boot (52 sleeps in all):

  • 30 DRAM-kept sleeps, all DRAM-kept, with Wi-Fi (ping to the gateway) and Bluetooth up after every one.
  • 5 more back to back: all DRAM-kept.
  • 3 deep sleeps with dwc3_0 unbound: all deep, radios up after each. Then 2 sleeps after the rebind: both DRAM-kept.
  • H.264 and HEVC 1080p encodes byte-identical before the sleeps, after sleeps 10, 20 and 30, and at the end.
  • dmesg audit: the kernel log of each of the 48 measured sleeps was searched for "radios not back", "radios did not come back", "radios stay down", "device inaccessible", "Unable to change power state", "bootstrap failed", "changed outside its permitted response" and "engine kept state", and for mt7932/bt7932 failures, errors, timeouts and faults. None found. (The Neo's log buffer holds only the last few minutes, so the per-sleep logs are the record.)
  • The default kernel and the committed Stage 2 bundle were restored and checked afterwards.
  • 03b6aa37 moves one call and adds a NULL check. It's behaviour-identical on the J700 and was build-checked only.

🤖 Generated with Claude Code

On a DRAM-kept wake the J700's PCIe host trains the radio link and runs
the bootstrap inside its resume_noirq: 110 ms, mostly the 100 ms after
PERST# before a config request. The whole noirq phase waits for it.

resume_noirq now restores the root port, releases nothing else and
queues a work item for the link, the bootstrap and the functions'
config. Its supplier, the bootstrap, waits for that work in its resume.
The host is the supplier's consumer and the radios are below the host,
so none of them resumes before the link is back, while the rest of the
system resumes in parallel. Both devices resume asynchronously for that.

The PCI core's resume_noirq of the functions would restore their config
with the link still down and retry every BAR for 10 ms. Their saved
state is set aside until the work restores it, and until then the host
answers their PM control register with D0, as a function out of reset
is, so that the core neither logs them inaccessible nor marks them
D3cold.

Signed-off-by: Diljit Singh <hey@djdev.me>
… path

Only a T8140 host with the Neo sleep path set up queues the radio work,
so only that host needs to resume asynchronously. Enable it once the PM
notifier is registered, and let resume_early cope with a host that has
no driver data.

Signed-off-by: Diljit Singh <hey@djdev.me>
@DjDeveloperr

Copy link
Copy Markdown
Contributor Author

Booted at this PR's exact head (03b6aa3), 2026-11-12

Kit built from 03b6aa3 itself (integration 3766943 + e0ea9f4 + 03b6aa3), aura main Stage 2, installed and rebooted over SSH. The running vmlinuz matched the kit's Image (aa12744d).

path suspend exit backlight on Wi-Fi / BT after
wake 1 DRAM-kept 0.171 s 0.317 s ok / on
wake 2 DRAM-kept 0.170 s 0.318 s ok / on
wake 3 DRAM-kept 0.174 s 0.319 s ok / on
sleep with dwc3_0 unbound deep — — ok / on
  • Times are seconds after the PMU RTC alarm. They match the soak on e0ea9f4 (exit 0.169–0.177 s).
  • dwc3_0 was rebound afterwards, and the radios were still up.
  • The default kernel and the committed Stage 2 bundle (364f5ffd) were restored and checked afterwards.

dmesg audit

  • Per-sleep kernel logs, 4 here and the soak's 48: none has a "radios not back", "radios did not come back", "radios stay down", "device inaccessible", "Unable to change power state", bootstrap or "engine kept state" line, nor an mt7932/bt7932 failure, error, timeout or fault.
  • The only radio-driver lines in this boot's log are the probe at boot: "enabling device (0000 -> 0002)" on both functions, and Wi-Fi's interface rename.
  • Correction to the description: the soak's "boot's whole log" check read the Neo's log buffer at the end, which holds only the last few minutes. The per-sleep logs above cover each sleep.

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.

1 participant