Skip to content

T8140 sleep: fixes from review - #1313

Closed
DjDeveloperr wants to merge 16 commits into
fix/t8140-debug-port-selectfrom
fix/t8140-sleep-review
Closed

DjDeveloperr wants to merge 16 commits into
fix/t8140-debug-port-selectfrom
fix/t8140-sleep-review

Conversation

@DjDeveloperr

Copy link
Copy Markdown
Contributor

Fixes from the offline review of the T8140 deep-sleep series (#1208 through #1285), one commit per finding, on top of #1285 at 7072491. All 12 medium-or-higher findings were still real on that head. Ten patches went in as the review wrote them, with comments shortened. Two were rewritten: #3 resets the transport from both firmware-start functions, so the dark-wake finish is covered too, and #7 runs from PM_POST_SUSPEND, so a late answer gets the whole unwind to arrive.

# What breaks User impact
1 The AOP PCMs advertise PAUSE/RESUME, but a sleep stops cout, cin and the speaker tap, and only prepare restarts them Headphones, headset mic and Speaker Sense are silent after a wake until reopened
2 s2idle after DPMS off holds and then unpauses a DCP whose firmware DPMS stopped Internal panel stays dark until reboot
3 A DCP restarted after a crash or an A500 timeout keeps its stale channel depth and active transaction, and drops messages while still marked crashed External display dead and every later sleep refused; internal DCP recovery can't succeed
4 With parking spin-table power ops registered, kexec and hibernation go ahead with secondaries parked in memory the new image overwrites Random crash or corruption on kexec -e; now refused with -EBUSY as before the series
5 apple_rtkit_sleep_stop() fails after both idles were acked, and sleep_return() calls a half-idled firmware running SIO dead and S2R refused until reboot; endpoint sends fail
6 SIO clears asleep before a restart that fails Every later S2R refused until reboot
7 MTP: no .complete after a failed .prepare, so a late idle answer is never followed by AP on Keyboard/trackpad firmware left told the AP is idle while the system runs
8 genpd's noirq power-off of the driver-down ATC0_USB walks up and gates ATC0_USB_AON In-place USB sleep defeated with USB wakeup off; USB devices enumerate again after a wake
9 ADMAC marks held channels lost only after a real sleep, but genpd gates their domains at noirq in every sleep Open audio stream silent or wrong after a sleep refused past noirq
10 Unbinding dwc3-apple while its post-wake SuperSpeed work waits for the device lock deadlocks Unbind hangs in D state, and the next suspend blocks
11 A dark wake's re-sleep reaches NVMe while its post-wake reset still runs -EBUSY turns the dark wake into a full wake behind the closed lid
12 Resuming a lost, paused ADMAC transfer keeps stale ring bookkeeping Position lags the DMA (no current client pauses once #1 is in)

Tested offline only:

  • Asahi-profile compile of every touched directory, with Rust on. RUSTC ran on sound/soc/apple (aop_audio.o, which includes aop_t8140.rs). Exit 0, with no new warnings: the 11 -Wthread-safety-analysis warnings in nvme/host/apple.c reset_work are identical on 7072491.
  • KUnit under QEMU arm64: stack-refresh's suites plus DCP, SIO, ADMAC, the T8140 AOP Rust suites, rtkit-helper and NVMe (compile), with HIBERNATION on to build hibernate.c. 527/528 passed; the 1 skip is an unrelated G17 test that needs UCLAMP_TASK.
  • Host tests: apple-dcp 87/87 (clang wrapper adding -Wno-unused-but-set-global for host clang 23), atc-usb3 with mutations 2/2, rtkit-oslog 6/6, t6030-clock 1/1, neo-stability 10/10.
  • checkpatch is clean on all 12.

Needs an on-device check on the Neo:

🤖 Generated with Claude Code

A firmware-driven suspend stops cout, cin and the speaker tap, and only
their DAI prepare starts them again. The dmaengine refine still adds
SNDRV_PCM_INFO_PAUSE and _RESUME, so after a wake user space resumes
these PCMs instead of preparing them: headphone playback, the headset
microphone and Speaker Sense run silent until reopened.

Mask both flags after the refine, as aop_spkr.rs does. Without RESUME
user space prepares again. PAUSE goes too: with it the suspend only
pauses the ADMAC transfer, and the paused one blocks the next start.

Signed-off-by: Diljit Singh <hey@djdev.me>
On T8140, DPMS off stops the internal DCP's firmware with presentation
paused until DPMS on restarts it. A suspend that isn't firmware-driven
(s2idle, also the default without the loader's PCIe tunables) still
runs dcp_hold_for_sleep(), and dcp_release_after_sleep() then unpauses
presentation on the stopped firmware. A present queued after the wake
is never answered, the IOMFB timeout marks the DCP crashed, and the
next DPMS on fails: the panel stays dark until reboot.

Skip the hold while the firmware is stopped: it sends nothing, and its
presentation stays paused for DPMS on.

Signed-off-by: Diljit Singh <hey@djdev.me>
A DCP that crashed, or gave no reply to A500, is stopped cold at the
next sleep and started again after it, but the AP side of its transport
is left as it was: the unanswered call keeps its channel depth, the
transaction in flight stays iomfb.active, and while crashed is still
set the new firmware's IOMFB messages and calls are dropped. The queue
never runs again: DCPEXT's display stays dead, every later sleep is
refused as "display updates still pending", and the internal DCP's
set_power_state after the restart fails with -EIO.

Before either start function brings up a firmware that stopped while
crashed, release the active and pending transactions, zero the channel
depths and the present state, and clear crashed. This covers the
restart after a sleep, DPMS on, DCPEXT's power cycle and the end of a
dark wake. A failed start marks it crashed again, as before.

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

Once a platform registers spin-table power ops, spin_table_cpus_stuck()
returns false and kexec goes ahead. But machine_shutdown() only parks
the secondaries: each waits in smp_rvbar_spin_table_cpu_die(), in this
kernel's text and on its idle stack, and the power ops run only at a
suspend's commit. The new image overwrites that memory under the parked
CPUs, and the new kernel can't start them. Before the parking ops such a
kexec failed cleanly with -EBUSY. Hibernation has the same problem:
cpus_are_stuck_in_kernel() is false because the ops have a cpu_die.

Take parking CPUs as stuck whether or not power ops are registered, and
check that before hibernating too. Hotplug and suspend-to-RAM don't look
at it, and a crash kexec still goes ahead.

Signed-off-by: Diljit Singh <hey@djdev.me>
apple_rtkit_sleep_stop() promises that on an error the firmware still
runs. But when the AP and IOP idles are both acknowledged and only the
5 ms status poll misses, it returns -ETIMEDOUT with the firmware idled,
and an idled firmware doesn't come back without a real sleep. SIO's
suspend_late then refuses the sleep with nothing to restart the
firmware: every SIO transfer fails, and every later sleep is refused
until reboot. PMP and DCPEXT use the same call.

Once both idles took, warn and finish the stop, so the caller's resume
path brings the firmware back.

The other half: when only the AP idle took, apple_rtkit_sleep_return()
reports the firmware as running after a refused sleep, but its AP state
is still idle and every endpoint send fails. Send AP on there.

Signed-off-by: Diljit Singh <hey@djdev.me>
After a real sleep, sio_resume_early() clears sio->asleep before it
tries the warm restart. If that fails, SIO's firmware is down but marked
awake, so the next sio_suspend_late() sends an idle notice to nothing,
times out and refuses the sleep, and so does every later one until
reboot.

Clear asleep only once the firmware is back. A failed restart then
leaves SIO as the sleep left it: the next real sleep restarts it, and a
refused one takes the existing engine-reset path.

Signed-off-by: Diljit Singh <hey@djdev.me>
When the MTP doesn't answer the AP idle notice in time, .prepare
refuses the sleep and leaves idle_unanswered for .complete, which sends
AP on if the answer came late. But the PM core calls no .complete for a
device whose .prepare failed. A late answer then leaves the MTP, which
carries the internal keyboard and trackpad, told the AP is idle while
the system runs, until a later sleep gets through.

Do the same end-of-sleep step from a PM_POST_SUSPEND notifier when
idle_unanswered is still set. That runs after the failed suspend has
unwound, so a late answer has had time to arrive, and is a no-op after
any sleep whose .complete ran.

Signed-off-by: Diljit Singh <hey@djdev.me>
The USB host's PHY lowers ATC0_USB and ATC0_COMMON itself at noirq, and
the resync after a wake leaves them to it. genpd's own noirq power-off
doesn't know: it keeps ATC0_USB only while a device in it is on the
wakeup path and the domain has GENPD_FLAG_ACTIVE_WAKEUP, which it gets
only when it was on at probe. Otherwise genpd powers ATC0_USB "off" and
walks up, and ATC0_USB_AON, the always-on side the in-place sleep keeps,
really goes off. At the wake, or after a refused sleep, genpd then
raises USB_AON, COMMON and USB before the PHY opens the fabric bridge,
the order the resync avoids. The host restarts and its devices
enumerate again.

During system sleep, refuse genpd's power-off of a domain its driver
took down, as for a sleep-retained one. genpd then leaves it and its
parents alone, and has nothing to raise.

Signed-off-by: Diljit Singh <hey@djdev.me>
admac_resume_early() marks a held channel's registers lost only when
the sleep reached the hardware. The ADMACs' power domains are plain
leaves, though, so genpd powers them off at noirq in every system
sleep, and a firmware-driven sleep refused after that (a wakeup event,
or a refusal in the platform's .enter) brings them back reset. The open
stream then runs with no SRAM carveout and the default channel setup
until it is reopened.

Mark them lost after any firmware-driven sleep. The restore waits for
the next transfer start and only writes the saved values back, so it
costs nothing when the registers survived. SoCs whose suspend isn't
firmware-driven are unchanged.

Signed-off-by: Diljit Singh <hey@djdev.me>
After a wake, the glue's sleep_finish work runs
apple_atcphy_sleep_finish(), which may update the SuperSpeed mux, and
atcphy_usb3_pm_get() takes the dwc3-apple device lock for that. An
unbind of dwc3-apple holds that lock across remove() and devres, where
devm_work_autocancel() waits for the same work: the unbind hangs, and
the next dpm_prepare() blocks on the device lock too.

Queue the finish on a work the PHY owns, with a flush for the next
sleep's PM_SUSPEND_PREPARE. The PHY's unbind cancels it. An unbind of
the controller no longer waits for it: the work takes the lock once the
unbind is done, finds no driver bound, and gets -EAGAIN.

Signed-off-by: Diljit Singh <hey@djdev.me>
After a T8140 sleep, apple_nvme_resume() only queues the reset work,
which waits for ANS and sets the controller up again. A dark wake goes
straight back to sleep, so the next apple_nvme_suspend() can arrive
while the controller is still RESETTING or CONNECTING: sleep_wanted is
false and the change to RESETTING fails with -EBUSY, which turns the
dark wake into a full wake behind the closed lid. That .suspend also
clears sleep_failed and sleep_restarted while the reset work reads them.

Flush the reset work before deciding. It is idle in every other case,
and its wait for ANS is bounded.

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

Resuming a paused cyclic transfer whose rings a sleep took restarts it
with admac_start_current_tx(), but keeps submitted_pos and
nperiods_outstanding from before the sleep. The descriptors the reset
dropped never report, so the position stays up to the ring depth behind
the DMA for the rest of the stream: playback underruns go unnoticed and
capture can overwrite unread periods.

Rewind the transfer to the last period reclaimed before restarting it.

Signed-off-by: Diljit Singh <hey@djdev.me>
Lines the stack up: the review fixes now sit on the debug port's page
select resend, and on integration/aurora 15f88e1. No conflicts.

Signed-off-by: Diljit Singh <hey@djdev.me>
@DjDeveloperr
DjDeveloperr changed the base branch from fix/t8140-debug-link-rearm to fix/t8140-debug-port-select November 9, 2026 18:17
Brings in #1208's aop-bootargs host test fix. No conflicts.

Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Diljit Singh <hey@djdev.me>
Brings in integration/aurora ac11649 (#1311, PCIe multi power domain
support) through #1208. No conflicts.

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

Copy link
Copy Markdown
Contributor Author

Superseded by #1445 (merged as 2b05915); its changes are in integration/aurora. The review fixes are in the files this PR touched.

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