Skip to content

[3/7] iommu: apple-dart: Let a host power-cycle its J713 DART for a deep sleep - #1416

Closed
erikwb wants to merge 3 commits into
j713/dock-s2r-rtkitfrom
j713/dock-s2r-dart
Closed

erikwb wants to merge 3 commits into
j713/dock-s2r-rtkitfrom
j713/dock-s2r-dart

Conversation

@erikwb

@erikwb erikwb commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

A deep sleep with a Thunderbolt dock attached powers off the ACIO and tunneled PCIe DARTs and the external display coprocessors' DARTs. Their hosts have to bring them back before the first requester runs again, at a point only the host knows.

Add an owner interface for these DARTs: bind once, then prepare, off, on and finish around a deep sleep. A DART takes part when its device-tree node names its clock protection slice (apple,clock-protection, now also on the eight J713 ACIO, tunneled PCIe and external display DARTs); there is no address table. off closes the DART to hardware commands, saves its registers, disables the streams, flushes and releases the clock-protection slice. on restores the slice and the saved state and reopens it. The generic suspend and resume callbacks leave an owned DART alone, and a DART with no owner bound takes no extra lock on its map paths.

While a DART is closed its page tables are still updated, in memory only, and map, unmap, attach, release and free never touch its registers. Those writers are counted; reopening waits for them and flushes the whole TLB before the DART opens if anything changed. finish always ends the transaction: a DART that was not restored stays closed, its registers unused, until reboot, and nothing panics on it.

Behaviour seen on the J713 that the restore accounts for:

  • The tunneled PCIe DART has 256 stream slots where Linux allocates 64; the whole bank, as the hardware reports it, is saved.
  • An external display DART's clock-protection slice word reads zero after the sleep; it is saved and written back.
  • A locked DART can come back with other bits set in the root of a stream that is not enabled and whose root was and is invalid. That is accepted; everything else must match, and a locked DART is never rewritten.

Part 3/7 of J713 docked S2R, stacked on the native radio series (#1384), which is stacked on #1314.

Series order: #1414 → #1415 → #1416 → #1417 → #1418 → #1419 → #1420.

Depends on #1415.

Validation of the series on this base:

  • Full arm64 Image, modules and DTBs at every PR tip of the series.
  • Ten focused host suites with negative controls (DART owner and register sequence, PHY sleep transaction, USB3 host model, router state transfer, tunnel replay including a dock lost during the sleep, NHI and tunneled-PCIe ownership including its failure paths, dock PM callback ordering, DP repair, DART writer gate and its callers) plus the device-tree policy check. checkpatch is clean for the driver code; it notes the Based-on-work-by: trailers and style in the tools/asahi fixtures.
  • J713 with a CalDigit TS5 (USB4 link, PCIe tunnel with the dock's Ethernet, USB hub with two keyboards, two 4K DisplayPort monitors), on the first version of the series: one suspend-abort test at the platform stage, then one real deep sleep with an RTC wake on the same boot. After the wake the dock link was at 20 Gb/s, dock Ethernet passed traffic at 2.5G, the USB devices were back, both monitors were lit at their previous mode about 10 s after the wake, Wi-Fi and Bluetooth answered, all ten CPUs were online, and there were no kernel warnings or taint. Repeated with one monitor unplugged; a monitor plugged in after the wake came up.

The changes since that version (the DT-selected DART owners and memory-only updates while closed, the unplug handling, the failure unwinds, the stock tb.c resume restored, the PMGR and RTKit hardening) are built and covered by the host suites, but have not run on the J713 yet.

Not covered: repeated sleeps on one boot, wake sources other than the RTC, the desktop's own sleep path (lid, logind), a dock unplugged during the sleep on hardware, and docks other than the TS5. Both native parameters (pcie_apple.cio_s2r, thunderbolt_apple.cio_s2r) default to off.

This series was developed and tested by Claude (Anthropic), working with me on the J713.

@DjDeveloperr DjDeveloperr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[3/7] apple-dart: review at 071e41e

Gating: fine. apple_dart_cio_init() sets dart->cio only on apple,j713 T8110 DARTs at eight fixed bases. Every new branch (suspend, resume, resume_noirq, release_device, domain_free, the writer gate) depends on dart->cio or cio_active, so T6050 and every other SoC is unchanged.

M5: this adds no conflict hunk; apple-dart.c already conflicts with the M5 branch in 9 places through the series base. t6050-sleep.c runs the same SPTM-style display-DART sequence with raw writes (streams off one by one, flush, slice bit 4 cleared; on: slice 0x12, diag 0, streams). There is no runtime overlap, but the sequence now exists in two places.

Findings (most severe first)

  1. Panics the dock path can reach. With dart->cio set, an unmap or TLB flush on a closed DART calls apple_dart_sleep_release_failed(), and release_device() panics for the whole prepare..finish window. For the radio, its owner was the only mapper. Here the mappers are every driver behind the dock and the DRM buffers on the display DARTs. finish() returning -EUCLEAN also leaves cio_active set for the rest of the boot, so a failed wake followed by unplugging the dock (PCI removal, then release_device) panics.
    Suggestion: while a DART is closed, let map/unmap publish to memory only, as the display_dark path already does. Skip the hardware TLB command and mark the TLB stale. dart_cio_restore() flushes before it re-enables streams, so nothing walks a stale entry. Release and free then don't need to panic, and a failed transaction can end with the streams left off but the bookkeeping released.
  2. A rejected map poisons the restore. One map attempted during the window (cio_violation) makes on() refuse to restore, so the DART stays closed until reboot. A late dma_map from a dock NIC refill or a DRM worker then takes out the dock or the display. With (1), a rejected map is harmless.
  3. Selection by base address. The table sits under of_machine_is_compatible("apple,j713"). J715 shares t8132-j713-j715.dtsi and the existing j713_* DART detection (firmware path plus address), but this excludes it. Two of these bases (0x48a300000/0x48a340000) are T6050's internal display DART pair in t6050-sleep.c, so the machine check is load-bearing. A DT property on the DART node carrying the slice number would be sturdier than a code table.
  4. Hot-path cost with the feature off. The writer gate now takes managed_lock (irqsave) on every map, unmap and flush on these eight DARTs even with both cio_s2r params at 0, because dart->cio is set at probe. Gate on a bound owner or on cio_active instead.
  5. commands_gated is set under dart->lock (apple_dart_quiesce_commands()) but cleared under managed_lock here. That's harmless while requesters are stopped, but it's the only writer that skips dart->lock.

Verdict: needs changes, mainly (1) and (2).


Review only: read at the head commit, not built or run (no J713 here). Written with Claude Code.

Comment thread drivers/iommu/apple-dart.c Outdated
for_each_stream_map(j, cfg, stream_map) {
if (READ_ONCE(stream_map->dart->sleep_record))
if (READ_ONCE(stream_map->dart->sleep_record) ||
READ_ONCE(stream_map->dart->cio_active))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This extends the radio's panic-on-release to the eight CIO and display DARTs. Unlike the radio, these front hot-pluggable trees (the tunneled PCIe hierarchy behind a dock) and DRM buffers. Two concrete ways in: (a) apple_dart_cio_finish() returns -EUCLEAN and leaves cio_active set permanently, so unplugging the dock after a failed wake panics here; (b) any PCI removal between the NHI's .prepare and .complete, for example a surprise unplug while sleep is starting. See the summary for a memory-only publish while closed, which makes this check unnecessary.

Comment thread drivers/iommu/apple-dart-sleep.c Outdated
int ret = 0;

if (!dart->managed_sids)
if (!dart->managed_sids && !dart->cio)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dart->cio is set at probe on J713 whatever pcie_apple.cio_s2r/thunderbolt_apple.cio_s2r say, so every map, unmap and flush on the tunneled PCIe, NHI and display DARTs now takes managed_lock with IRQs off, even with the feature off. Suggest keying this on a bound owner (dart->cio && dart->cio->owner) or on cio_active. Also note that with cio set, writer_get() failing on the unmap/flush paths now panics (apple_dart_sleep_release_failed()): see summary point 1.

Comment thread drivers/iommu/apple-dart-cio.c Outdated
unsigned int slice, bank = 0;
bool partitions, display = false;

if (!of_machine_is_compatible("apple,j713") || dart->hw->type != DART_T8110)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

J715 uses the same t8132-j713-j715.dtsi and gets the j713_dispext/j713_dcpext treatment in apple_dart_probe(), which is based on firmware path and address, but is left out here. Also, 0x48a300000/0x48a340000 are T6050's internal display DART pair (T6050_DISP0_DART and the DCP DART in t6050-sleep.c), so this machine check is the only thing separating the two. A DT property on the DART nodes (for example apple,cio-power-slice = <N>) would drop the address table and make the opt-in explicit.

Comment thread drivers/iommu/apple-dart-cio.c Outdated

spin_lock_irqsave(&s->dart->managed_lock, flags);
/* Publish restored translations before admitting hardware commands. */
smp_store_release(&s->dart->commands_gated, false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: every other writer of commands_gated (apple_dart_quiesce_commands(), apple_dart_resume_commands(), apple_dart_host_resume()) holds dart->lock; this one holds managed_lock. It's safe while requesters are stopped, but taking dart->lock here too would keep one rule.

Comment thread drivers/iommu/apple-dart-cio.c Outdated

if (!s || !READ_ONCE(s->dart->cio_active))
return -EINVAL;
if (READ_ONCE(s->dart->cio_violation))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A single map attempted while closed (writer_get() sets cio_violation and returns -EBUSY to the caller, so nothing was written) makes this refuse to restore, and the DART stays closed for the rest of the boot. A late dma_map from a dock NIC refill or a DRM worker during the window is enough to lose the dock or the display. Since a rejected map changed nothing, it shouldn't block the restore. With the memory-only publish from the summary, it wouldn't even need rejecting.

Comment thread drivers/iommu/apple-dart-cio.c Outdated
dart = s->dart;
spin_lock_irqsave(&dart->managed_lock, flags);
if (dart->cio_violation || dart->sleep_closed || (s->changed && !s->restored))
ret = -EUCLEAN;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

On this path cio_active stays true and the runtime-PM reference stays held forever. From then on apple_dart_suspend()/resume() skip this DART, prepare() returns -EBUSY, domain_free() leaks, and release_device() panics. The header calls this fail-closed, but for a removable tree it means a later unplug crashes the machine. Consider ending the transaction here with the streams left disabled, and releasing the bookkeeping.

…DARTs

On T8132, the hosts of some DARTs power them off and restore them around a
deep sleep: the ACIO NHIs, the tunneled PCIe ports and the external display
coprocessors. They need the same clock protection slice as the internal
display DARTs. Extend the description of apple,clock-protection to them.

Signed-off-by: Erik Bourget <erik@ebourget.net>
A deep sleep with a Thunderbolt dock attached powers off the ACIO and
tunneled PCIe DARTs and the external display coprocessors' DARTs, and
their hosts have to bring them back before the first requester runs
again. Runtime PM cannot do it: the host decides when its requesters
are stopped and when its clocks are back.

Add an owner interface for the T8110 DARTs whose device tree node names
a clock protection slice, other than the internal display ones. The
host binds once, then around a deep sleep calls prepare (before any
suspend callback), off (requesters stopped, clocks still on), on
(clocks back, before any DMA) and finish. off closes the DART to
hardware commands, saves the stream table, tunables and partition
words, disables the streams, flushes, and releases the clock-protection
slice. on restores the slice and the saved state and reopens the DART.
The generic suspend and resume callbacks leave an owned DART alone, and
a DART without a bound owner takes no extra lock on its map paths.

While a DART is closed, its page tables are still updated, in memory
only: map, unmap, attach, release and free never touch its registers.
Those writers are counted. Reopening, after a restore or after an off
that failed before changing anything, waits for them, and if anything
changed meanwhile flushes the whole TLB before the DART opens. finish
always ends the transaction. A DART that was not restored stays closed,
its registers unused, until reboot, even once its owner is gone;
nothing panics on it.

Things learned on the J713 that the restore accounts for:

- The tunneled PCIe DART has 256 stream slots where Linux allocates 64.
  The whole bank, as the hardware reports it, is saved, the reserved
  bypass streams included.
- An external display DART's clock-protection slice word reads zero
  after the sleep. It is saved at off and written back at on.
- A locked DART can come back with other bits set in the root of a
  stream that is not enabled and whose root was, and still is, invalid.
  That is accepted. Every control register and every valid root must
  match, and a locked DART is never rewritten.

The slice handling follows the internal display DART's power-down.
A failed restore dumps the registers that differ.

Add host-side models of the register sequence and of the ownership
rules, each with negative controls.

Based-on-work-by: Bartosz Nitka <niteria@gmail.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
…ction

A deep sleep with a dock or an external display attached powers off the
DARTs of the ACIO NHIs, the tunneled PCIe ports and the external display
coprocessors, and their hosts restore them. Give each of them its clock
protection slice, which also marks it as one its host may power-cycle.

Signed-off-by: Erik Bourget <erik@ebourget.net>
@erikwb
erikwb force-pushed the j713/dock-s2r-rtkit branch from dcaf73b to 3db00bc Compare November 10, 2026 13:31
@erikwb
erikwb force-pushed the j713/dock-s2r-dart branch from 071e41e to 8178a9f Compare November 10, 2026 13:31
@erikwb

erikwb commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Closing: this commit set landed in integration/aurora through #1314, which carried the whole docked deep-sleep series (#1414 to #1420). The follow-up that turns the docked deep sleep on by default is #1855.

@erikwb erikwb closed this Nov 13, 2026
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