Skip to content

mux: apple-display-crossbar: Accept busy T6050 connection status - #1269

Closed
erikwb wants to merge 257 commits into
ewb/j716c-public-usb4-phyfrom
ewb/j716c-public-usb4-mux
Closed

erikwb wants to merge 257 commits into
ewb/j716c-public-usb4-phyfrom
ewb/j716c-public-usb4-mux

Conversation

@erikwb

@erikwb erikwb commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by #1323, which carries the crossbar connection-status change with the original authorship and trailers. The remaining USB4/display PRs now name #1323 as their prerequisite rather than stacking on this PR. Closing this duplicate submission; the branch remains available.

Created with GPT-6 Astra in Codex.

funkzy and others added 30 commits November 4, 2026 20:33
The SART that filters the T6050 (M5 Ultra) ANS's DMA keeps SARTv3's
entry format (config, page address and size, 4 KiB units) but moves the
address and size arrays from 0x40/0x80 to 0x60/0xc0. Read on J775D before
Linux started: the config words at 0x00+4i and seven live entries whose
addresses at 0x60+4i and sizes at 0xc0+4i name ANS's MMIO and two DRAM
ranges.

Let the v3 ops carry the array offsets, so v3 and v4 share one
implementation, and match apple,t6050-sart with the v4 offsets. There is
no v3 fallback compatible: a v3 driver would read the wrong registers.
Log the number of entries the boot loader left (protected) at debug
level.

v2 and v3 SARTs keep their offsets.

Testing: on J775D, every boot since protects the seven entries the boot
loader left, and ANS's NVMe runs behind it (Linux boots from and writes
to its NVMe partition).

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add apple,t6050-nvme-ans2: T6040's three-region layout (nvme, ans,
nvmmu) and power domains. The T6050 ANS is adopted running and never
reset through PMGR, so it requires no reset.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add apple,t6050-nvme-ans2 for the M5 Ultra's ANS when Linux runs on the
hardware. Its layout is T6040's, as the ADT
and the registers read before Linux started show: a 64 KiB "nvme" block,
a separate NVMMU and linear submission queues. The controller reports
254 queue entries (CAP.MQES 0xfd); the profile uses T6040's 64. FUA
writes are not shown durable here, so the profile flushes instead
(NVME_QUIRK_BROKEN_FUA).

The boot loader leaves ANS's firmware running and the controller shut
down (CC.EN 0, CSTS.RDY 0). The driver adopts that session, and, like
every post-M4 ANS without cold_reset, never resets it through PMGR, so
the profile sets reset_optional, a hardware-profile flag that takes the
controller reset as optional, and the binding requires no reset for it.
Every other profile still requires its reset.

Testing: on J775D, every boot since 2026-10-30 adopts ANS's running
firmware and enables the controller with this profile; Linux runs from
its NVMe root partition (ext4) through daily builds and reboots without
an NVMe error.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The boot loader leaves the T6050 ANS's firmware running with the
controller disabled and, as on T8132 and T8140, adds apple,rtkit-handoff
to the node so the kernel adopts that RTKit session instead of starting
the co-processor again. The T6050 profile relies on that adoption. Allow
the property on apple,t6050-nvme-ans2.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add the T6050 DisplayPort/USB combo PHY and four-clock display crossbar profile. Carry the current Type-C, USB2, USB3 PIPE sequencing, and teardown behavior from the published T6050 source.

Source snapshot: t6050-gpu ee68abb105200e99636a5edfc647a5cfc1d89954
Integration base: 9cbdd07

Signed-off-by: Dj <hey@djdev.me>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The combo PHY's USB2 PHY and digital banks are in the USB controller's
power domain. Let a combo PHY name it fourth, as "usb", so the PHY can
hold it on a port with no USB controller bound. The fixed DisplayPort
PHY keeps exactly three domains.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The combo PHY's USB2 PHY and digital banks are in the USB controller's
power domain. Only a bound USB controller holds that domain, so on a
port with no controller (a DisplayPort-only port, as on J775D with Linux
on the hardware) the first Type-C mode switch wrote an unclocked bank
and took an SError.

A combo PHY node may now name that domain as a fourth power domain,
"usb". When it does, the PHY holds it from combo start to combo stop,
after the common domain on the way up and before it on the way down.
Nodes that name three domains probe and run exactly as before, and the
fixed DisplayPort PHY still takes exactly three.

Testing: on J775D (M5 Ultra), with the fourth domain named on each combo
port, Type-C mode switches and DisplayPort on the rear ports ran without
an SError on every boot since 2026-10-30.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
apple,port-index has an enum but no type, so dtschema cannot decode the
cell and every combo PHY node fails with "b'\x00\x00\x00\x02' is not one
of [1, 2]" and a core-schema type error, whatever port it names. Give it
the uint32 type.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add apple,t6050-dwc3, the T6050 (J775D) DWC3 on bare metal. It uses the
shared tunable table, extended with the tables Diljit Singh's T6050 USB
support applies, in the same order: the T6040 tables plus CIO (the Apple
region from 0xcd20, after the fixed CIO values), bulk fabric and AUSBC
debug (the optional drd-bulk and drd-ausbc regions).

It is host-only, like T6040. The USB2 port reset follows the root hub's
start (a no-op without a PHY reset op), and a USB3 PHY failure is fatal,
as on T8132.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The PIPE lock that starts the USB3 handoff is acknowledged only while
the combo PHY's dummy PIPE PHY is enabled (pipehandler
NONSELECTED_OVERRIDE bit 15), as on the earlier ATC PHYs (T8122,
T8140), and the bit resets to clear with the controller's domain.
Without it the lock timed out, the PHY faulted and rear port 2 had no
USB at all.

Enable it before the controller leaves reset. Each PIPE-switch step
returns its own code, and every fault of the PHY is logged with its
reason (the pipehandler registers for PIPE faults); dwc3-apple logs a
fatal USB3 PHY failure.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
A USB3 PIPE switch can fail before the mux moves, with the PIPE still on
its dummy producer. The PHY then faulted and lost USB2 with it.

Leave the port on USB2 in that case, as four DisplayPort lanes do.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add the T6050 SMC consumer profile, described pcIO GPIO outputs, power-supply identity sizing, and charging policy. Preserve the newer shared SRAM accessor and metadata serialization.

Source snapshot: t6050-gpu ee68abb105200e99636a5edfc647a5cfc1d89954
Integration base: 9cbdd07

Signed-off-by: Dj <hey@djdev.me>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The combo PHY refused any apple,port-index other than 1 or 2, the two ports a
guest description produced. Nothing else in the driver reads the index: the
firmware buffer, banks and sequences are port-agnostic. Validate it against
the board's port set instead: 1 to 5, with 0 (the port that carries the boot
loader's debug link) still excluded, here and in the binding.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The T6050 profile (Aurora's t6050/smc) registers input, power and hwmon
only, so a T6050 kernel that owns the machine (J775D on bare metal) had
no SMC restart or power-off and no RTC: poweroff halted Linux and left
the machine on.

Add macsmc-reboot and macsmc-rtc to the T6050 profile as cells that are
registered only where the device tree has their child node, the rule
the pcIO outputs and the second GPIO bank already follow; those two
blocks become one table and one helper. A T6050 device tree without
reboot or rtc children registers exactly what it did,
and other SMCs are unchanged (same cells in the same order; the
registration failure messages now name the cell).

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
apple,soc-die-keys adds a "SoC die <key>" channel per listed key for the
macsmc_soc_die zone, and apple,discover-temperatures refuses to run once
any temperature channel exists, so a machine that discovers its
temperatures (T8142, J775D) could not have the zone: the probe failed
with -EINVAL.

With discovery, a listed key now adds no channel; the channel discovery
creates for it (labelled with the key) is marked for the zone instead.
Machines that set only one of the two properties are unchanged.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Expose the PMU's power-on policy byte as the ac_power_mode attribute
Asahi's downstream driver had, with a third mode. The byte has two
policy bits: bit 0 keeps the machine off when power returns after a loss,
bit 5 keeps it off when power is connected. Asahi's driver wrote the whole
byte (0x02 restore, 0x03 off), which clears bit 5: on machines whose PMU
powers on at connect that write means "always", the opposite of "off".
Change only the two bits and leave the rest to the PMU:

  off      bit 0 and bit 5 set
  restore  bit 5 set, bit 0 clear
  always   bit 5 clear (bit 0 set)

The pm_setting cell is optional; the attribute appears only with it.
The binding takes it as a fifth cell, as the t600x/t602x device trees
already list it.

Based on Asahi's ac_power_mode (marcan).

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
signal.c calls apple_afp_enabled() while sizing the signal frame, behind
a runtime check, but the function is only declared with
CONFIG_ARM64_APPLE_X86_FP, so a kernel built without it fails to
compile. Report AFP as disabled when it is not built.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Without CONFIG_OF_DYNAMIC, of_node_put() is a static inline no-op, so
bindgen emits no binding for it and Rust users such as the AOP driver
fail to build. Wrap it in a helper in that case.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Add the T6050 SoC in its two-die M5 Ultra form, and the Mac Studio board
that uses it: the device tree for Linux running on the hardware, as the
boot loader hands it over (the loader fills the tunables and calibration
the nodes name).

Every T6050 is made of the same die, so the SoC description is split as
for the M3 Max and Ultra (t6031-base, -die0, -dieX):

- t6050-base.dtsi: the nodes common to every size, die 0's bus and the
  CPU node template;
- t6050-die0.dtsi: the blocks used on die 0 only, among them the
  interrupt controller that serves both dies;
- t6050-dieX.dtsi: the per-die template, included once for each die,
  with each die's blocks at the die stride and its interrupts on its
  own die of the AIC;
- t6050-ultra.dtsi: the two dies, their CPUs (five cores in each
  cluster) and the blocks so far described for the two-die part only:
  the video decoders, the memory cache controllers, the display
  subsystem, die 1's ATC0 and its DisplayPort PHY;
- t6050-j775d.dts: the board, its USB-C ports and their PD controllers,
  the HDMI port, PCIe, audio, the boot NOR and the fans.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[erik: dropped references to private files from a comment]
Signed-off-by: Erik Bourget <erik@ebourget.net>
Build t6050-j775d.dtb, the Mac Studio with the M5 Ultra (J775D, T6050),
with the other Apple device trees.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
T6050's coprocessors (ANS among them) use the ASC v4 mailbox.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add the J775D board and its T6050 (M5 Ultra) SoC.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
T6050's AIC3 is the T8122 one with two dies, so it takes the T8122 fallback
and, like T6031's, four interrupt cells (the second is the die).

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
T6050's watchdog is compatible with T8103's.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add apple,t6050-dart, the DART of the T6050 (M5 Ultra) PCIe ports and
display co-processors, with the t8110 fallback like the other recent
SoCs.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
T6050's boot NOR sits behind a QSPIMC controller with the same P and C
apertures (C at P + 0x800) as T8140's. Describe it as
"apple,t6050-qspi", "apple,t8140-qspi" so the driver's T8140 policy (data
reads anywhere, programs and erases only inside the nvram partition)
applies without a driver change.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The PMU's info-pm_setting byte (0x2001, as on T6030) holds the power-on
policy: bit 0 keeps the machine off when power returns after a loss, bit 5
keeps it off when power is connected. Hand it to the SMC reboot node as the
pm_setting cell so macsmc-reboot's ac_power_mode can read and set it.
Written 0x03 ("always") from Linux on the Studio, a wall-power cycle after
a Linux shutdown powered it on by itself.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The T6050 (M5 Ultra) SART keeps SARTv3's entries but moves the address
and size arrays, so it is not compatible with apple,t6000-sart and has
no fallback.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
The SART that filters the T6050 (M5 Ultra) ANS's DMA keeps SARTv3's
entry format (config, page address and size, 4 KiB units) but moves the
address and size arrays from 0x40/0x80 to 0x60/0xc0. Read on J775D before
Linux started: the config words at 0x00+4i and seven live entries whose
addresses at 0x60+4i and sizes at 0xc0+4i name ANS's MMIO and two DRAM
ranges.

Let the v3 ops carry the array offsets, so v3 and v4 share one
implementation, and match apple,t6050-sart with the v4 offsets. There is
no v3 fallback compatible: a v3 driver would read the wrong registers.
Log the number of entries the boot loader left (protected) at debug
level.

v2 and v3 SARTs keep their offsets.

Testing: on J775D, every boot since protects the seven entries the boot
loader left, and ANS's NVMe runs behind it (Linux boots from and writes
to its NVMe partition).

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
Add apple,t6050-nvme-ans2: T6040's three-region layout (nvme, ans,
nvmmu) and power domains. The T6050 ANS is adopted running and never
reset through PMGR, so it requires no reset.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Erik Bourget <erik@ebourget.net>
funkzy and others added 9 commits November 8, 2026 12:03
…acks

An rmmod of aurora that raced a GPU probe on M4 (G16), M6 (G18) or the
M5 RAM-boot path could unload the module under a GPU firmware whose
RTKit and mailbox callbacks run its code. Each of these backends now
refuses to start the firmware once the module's removal has begun.

They pinned the module with __module_get(), which does not stop an
unload already under way: a deferred or asynchronous probe can run after
rmmod has passed its reference check, take the reference anyway and
start the firmware while module exit unloads the code. try_module_get()
fails once removal has begun. G15, and G17 on M5, pin with it through
the shared pin in retain.rs and retain.c; the other backends now use the
same pin, each with its own flag:

- G16: admission pins the module before it holds the GFX power domain
  and fails with ENODEV, holding nothing, once removal has begun. The
  firmware start and Gpu::retain only confirm the pin. The C helper
  asahi_g16_module_get() goes.
- The M5 RAM-boot path (M5Runtime::prepare): the pin is still taken
  before either CPU starts. Once removal has begun, prepare fails with
  ENODEV and the unstarted session is freed.
- G18: the pin is taken before the session is marked as attempted. Once
  removal has begun the stage fails with ENODEV with nothing started,
  and the attempt is not marked.

Build-checked only: there is no M4, M5 RAM-boot or M6 hardware here.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
An rmmod of the display driver that raced a DCP probe, a display attach
or an external start request could unload the module under a DCP
firmware whose RTKit callbacks run its code. These paths now refuse once
the module's removal has begun.

They pinned the module with __module_get(), which takes the reference
even after rmmod has passed its reference check, so the unload goes on
under the firmware. Each now uses try_module_get() and backs out what it
had set up:

- The firmware 26.6 and 14.7 panel sessions: pinned before the mailbox
  is registered; -ENODEV with nothing started. The reference is given
  back if RTKit init fails.
- The external DCP's one-shot start (its sysfs request and a display
  attach) and the external scanout pattern request: the request is
  withdrawn and no work is queued.
- A retained live session (dcp_pin_live_session): -ENODEV before any
  reference is taken, as for its allocation failure.
- A refused takeover: the pin is taken before the takeover trials, while
  the bind can still unwind. A refusal keeps it; it is given back once
  every trial passes.

While the module is live each path takes the same reference as before.
Build-checked only: no unload race was run on hardware.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…, with firmware-27 cases added

The firmware-27 display support makes the extracted production code
name DCP_FIRMWARE_V_27_0 and new fields and helpers, so 20 apple-dcp
fixtures stopped compiling: the AFK service-init parser gained an
EPICUnit-first argument and service ops an init_props hook, parser.c
fills struct dcp_dptx_caps from dcp-peer-v27.h, dcp_get_disp_regs()
records firmware-physical register bases, the IOMFB dispatcher
size-checks firmware-27 callbacks, the DPTX lane policy keys on
dcp_is_external_h17p() and the hotplug callback completes
typec_display_ready. The dictionary record the desktop panel identity
uses moved to iomfb.h as struct dcp_dict_passthrough_req.

The fixtures gain those definitions and stubs, and their existing
checks run unchanged. Where firmware 27 takes a branch a fixture
already drives, it is checked too: dpavserv uses AFKV2 on the legacy
endpoint without waiting, EPICUnit wins over interface-id in either
order, and powerlog takes a framed report's takeover hint but not the
desktop-only headerless pairs.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…as no room for another queue

When QUEUE_CREATE found no room (no render channel pair left beside the
installed compute channels, or no free execution context while a closing
queue still held one), it gave back the logical queue slot it had just
claimed and then waited for a slot to be returned. Giving the slot back
woke the slot waiters, the creator itself included, so its wait ended at
once: the ioctl spun on a CPU, with the device mutex taken on every turn,
until its timeout. One unprivileged process could keep a CPU busy that
way; in testing it refused over half a million times in 29 seconds.

The render-channel check now runs before the slot is claimed, so a
refusal claims nothing. A creator that gives back a claimed slot to wait
for a context now waits from the sample after its own return: other
waiters still wake, and the creator sleeps until a slot or context is
actually returned or its timeout passes. QUEUE_CREATE still fails with
EBUSY after the timeout.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>

[Rebased onto integration/aurora: upstream checks execution-context capacity before reserving a slot, so keep that stronger no-slot-on-refusal path; the cancellation helper and its tests remain.]
…airs in firmware, so a second render pair never waits on the host

An owner (one logical queue) holds one render pair today. Before it can
hold two, its renders whose attachments overlap must stay ordered when they
land on different pairs, and that ordering must not be a host wait: the
old plan returned Plan::Wait for own overlaps on two pairs.

dj's b141ca9 orders a render after overlapping renders of other
owners in the VM, in firmware. This extends it to the owner's own
renders on its other pair, which his plan held with a host wait.

This uses the mechanism other owners of the VM already get. Under the
device mutex, publication_barriers names the newest published fragment
kick of every other pair whose live same-VM render overlaps. It now
includes the owner's own renders on its other pair that the render's
queue prefix (wait_through[0]) does not already cover. Those were left
out because an owner had one pair.

hazard::plan:
 - Own overlaps prefer the pair that holds them (Plan::Join). With
   overlaps on two own pairs it prefers the pair of the newer overlap.
 - pick takes the preferred pair, or else the owner's least-loaded other
   pair. Either is ordered: ring order on the same pair, the barrier
   across pairs. So a full preferred pair never makes the render wait.
 - Plan::Pair (strict) remains only when no barrier slot is left. That is
   the case with PUBLICATION_BARRIERS other pairs of the VM and a foreign
   overlap.
 - In that crowded case, a render with own overlaps on two pairs waits,
   as a crowded publication already does. It needs eight same-VM pairs,
   so it never happens on a one-queue workload.

The quarantine checks (refusal, the admission wait, admits_render,
spared_quarantined) looked at the owner's first pair only. They now look
at any pair the owner holds through State::owner_quarantine. A
quarantined sibling is not bypassed through the healthy pair. The owner
is spared only if every quarantined pair of its is spared.

Locking and ordering:
 - plan, pick and owner_quarantine run under the pool's state lock, as
   before.
 - publication_barriers runs under the device mutex and the state lock.
   The device mutex totally orders publications.
 - An owner publishes in submission order: inline publication runs only
   with an empty backlog, and the ordered run_job worker publishes one
   command at a time. So every earlier own render already carries its
   fragment kick when a later one publishes, and the barrier sees it.
 - Tile loads and stores happen only in fragment work, so ordering the
   fragment kick is enough. The tiling kick keeps its prefix rules.
 - A barrier on the same pair is skipped, as before: one fragment ring
   completes in kick order.

With RENDER_PAIRS_PER_OWNER = 1 no owner holds two pairs. Join then
behaves as Pair, and no own entry sits on another slot, so behaviour is
unchanged.

KUnit:
 - asahi_g17_hazard_plan covers preference, prefix cover, the newer of
   two pairs, the foreign barrier beside them, and the barrier slot.
 - asahi_g17_owner_pairs covers the Join fallback when the preferred
   pair is full, any-pair quarantine, and the own barrier against prefix
   cover.

Suggested-by: DjDeveloperr <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…om its own pair count, so a queue on two pairs can keep both rings full

drm_sched fixes a scheduler's credits when it is created. Those credits
were one pair's ring (RENDER_DEPTH, 32), so a queue on two pairs would
have been held at one ring's depth. Its render pool's per-pair extras
and its sibling limit read the device-wide constant.

dj's 22516bf45e61 sized credits as pairs × depth. This keeps that per
queue, so a queue's credits follow the pairs it may hold.

Each logical queue (Backend) now records the render pairs it may hold,
`pairs`, when it is created. Three things follow from that one value:
 - Its render scheduler's credits are RENDER_DEPTH x pairs, through
   job::Backend::render_pairs (default 1).
 - required_blocks sizes its render pool's compute-pool extras for
   `pairs` pairs.
 - Admission's claim_sibling takes another pair only while the owner
   holds fewer than `pairs`. acquire_render, try_acquire_render, pick and
   acquire carry the value from the queue.
A queue never mixes the two sizings, so a later change of the device's
pair count (the next commit's switch) applies only to queues created
after it.

`pairs` is still RENDER_PAIRS_PER_OWNER (1) here, so credits, pool size
and claiming are unchanged.

Locking and ordering: `pairs` is immutable after Backend::new. The
scheduler reads it once, in Scheduler::new, before the entity exists.
Admission reads it under the pool's state lock, as it reads every other
admission input.

Suggested-by: DjDeveloperr <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
… shared QID capacity, 9-15% faster heavy 1080p rows at depth 8

dj's 87cab74 ran each queue on two pairs. Ryan's shared capacity
(780956a) protects one render pair per execution context and shares
the rest, and set one pair per queue to fit it. Most of the two-pair
machinery stayed behind the constant: acquire's least-loaded fallback,
claim_sibling, warmable_sibling and ensure_render's sibling warm-up.
This restores two on G17D and keeps Ryan's protected pair, so the second
comes from the shared surplus.

An owner's overlapping renders on its two pairs are ordered in firmware,
by dj's barriers, never by a host wait (the first commit of this
series).

M5 Ultra, gpubench, 1080p, us per submit, one boot, client pinned,
three runs each:

| row | 1 pair | 2 pairs | 1 pair again |
|---|---|---|---|
| fill d8 | 41.98 / 41.95 / 41.97 | 38.13 / 38.13 / 38.14 | 41.97 / 41.98 / 42.00 |
| geom d8 | 39.10 / 38.91 / 39.11 | 33.19 / 33.22 / 33.21 | 38.83 / 39.16 / 38.16 |

GPU time per render is unchanged (41.8 and 39 us): the gain is the two
pairs overlapping. With this commit alone a depth-2 queue switches pairs
on almost every render and loses time: fill d2 95-102 -> 126, geom d2
104-117 -> 127. The next commit keeps such a queue on one pair.

What changes:
 - g17::pairs picks the count at probe: 2 on G17D boards (g17d_forms(),
   as the other G17D forms are chosen), 1 everywhere else. Each queue
   samples it once at creation, for its credits, pool and pair limit
   (previous commit).
 - The constant becomes the per-variant maximum RENDER_PAIRS_MAX = 2,
   and hazard::MAX_OWNER_PAIRS follows it.
 - max_in_flight_per_queue reports the count.
 - ensure_render's early return follows the queue's own count.

The second pair comes only from surplus:
 - render_reserve still counts one pair per open queue. The kick.rs
   assert that pinned the constant to 1 now states that, so queue_limit
   and the capacity guarantees are unchanged.
 - claim_sibling takes a sibling only from a released or unowned slot,
   and only while a pair can still be built (build_room).
 - reassign_unused_spare gives an unused spare only to an owner with no
   pair, as before.

Completion order across one queue's two pairs, which needs no code:
 - The driver's completion fences, the ones userspace sees, each have an
   independent timeline (fence.rs), so they may signal out of order.
 - drm_sched's finished fences share the entity's context. They are
   internal and never exported. free_job takes jobs from the head of
   pending_list, so a render that finishes early on the other pair is
   freed, and its credit returned, after its predecessor. That delays a
   credit; it never waits on the GPU.
 - Firmware flushes a context's work-state nodes in word order
   (377324b), so retirement evidence stays ordered per pair.

Memory: on G17D every render pool carries two pairs' compute-pool
extras.

Locking and ordering:
 - The count is a static, written once at probe before any queue
   exists, and read in Backend::new.
 - Admission state (owners, refs, build_room) changes only under the
   pool's state lock, as before.
 - Sibling construction is off-lock under a host lease
   (warmable_sibling), as integration has it.

KUnit asahi_g17_two_pairs:
 - a two-pair owner spreads its depth to 2 x RENDER_DEPTH over a surplus
   sibling, then is held;
 - without build room no sibling is claimed, and a spare goes only to an
   owner with no pair;
 - a one-pair owner claims no sibling.

Suggested-by: DjDeveloperr <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
… has a backlog, so depth-2 rows keep one pair's latency (20-28% faster than always splitting)

With two render pairs per queue (previous commit), acquire took the
owner's idle sibling whenever one existed. At depth 2 that switched
pairs on 98% of renders, and each render then waited, across pairs, for
the one before it.

A two-pair queue's next render now stacks on its least-loaded busy pair
while that pair holds at most sibling_backlog() renders (1). Only after
that does it take the idle sibling. At depth 1-2 a queue stays on one
pair. A deeper queue still spreads, because its third render in flight
finds a backlog. A one-pair queue takes the existing path unchanged.

M5 Ultra, gpubench, 1080p, us per submit, one boot, client pinned. That
boot also carried the per-submit fixes, which speed up the depth-2 and
geom rows:

| row | 1 pair | 2 pairs, always split | 2 pairs, backlog 1 | backlog 16 |
|---|---|---|---|---|
| fill d2 | 82.2 | 125.2 / 129.0 | 78.3 / 81.9 | 80.3 |
| geom d2 | 84.4 | 118.8 / 129.5 | 84.3 / 83.5 | 90.5 |
| fill d8 | 41.97 | 37.75 / 38.14 | 38.13 / 38.14 | 41.95 |
| geom d8 | 38.98 | 31.50 / 31.82 | 31.85 / 31.84 | 38.23 |

Backlog 1 brings depth 2 back to one pair's time and keeps the depth-8
gain. A deep backlog (16) gives the depth-8 gain up.

This series alone (two pairs, backlog 1), two runs: fill d2 92.8 / 91.0
and geom d2 102.1 / 102.0, against 126.0-126.5 and 127.2-127.7 always
splitting on the same series without this commit, 20-28% faster.
Depth 8 stays at the two-pair level: fill 38.14 / 38.15.

Locking and ordering: acquire runs under the pool's state lock, as
before. Stacking on a busy pair is the existing least-loaded stacking,
applied earlier. The pair's ring orders the render after that pair's
earlier work, and the queue prefix and the hazard plan order it across
pairs, as before. The backlog is a relaxed static: a policy value read
per pick, never a sizing.

KUnit: asahi_g17_two_pairs gains a_shallow_two_pair_queue_stays_on_its_busy_pair.
The spreading test runs with backlog 0.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Combine integration/aurora with the published platform, Thunderbolt and
version-27 display series. Preserve newer controller variants and binding
constraints while applying the T6050 router initialization changes.

Reuse the existing public combo-PHY binding and its matrix tunable types;
do not resurrect the obsolete T6050-only schema. Keep one DockChannel
nonblocking-output property definition when combining the public series.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
erikwb and others added 9 commits November 8, 2026 21:01
Distinguish the coprocessor-driven T6050 GPU from its raw disabled
inventory node with the G17 family fallback. Describe both mailboxes,
the six reserved memory regions and loader-filled power data.

Accept the existing mini-LED panel compatible and the named boot
framebuffer reservation supplied to the display subsystem.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
Include integration/aurora at 8c4b5c5, including the merged display
audio updates, before testing the complete J716c desktop and USB4 stack.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
Supply the one-die GPU resource layout, GFX/GFX1 mailboxes, six memory
reservations and fourteen operating points with four cluster voltage
lanes. Put its existing PMGR resources under the GPU aperture and replace
the raw disabled GPU inventory nodes. Keep the J716c inventory on its
existing SoC bus so loader path lookups are unambiguous.

Connect the internal DCP to its bandwidth scratch, clocks and mini-LED
panel, with display scanout through disp0 SID 0. Reuse the existing DCP,
DART, mailbox and PMC definitions.

The GPU, its power states and mailboxes, memory reservations and internal
display remain disabled until the loader validates and fills the handoff.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
Describe the ASCWrap v6 compatible for the physical GPU aperture shared
by J775D and J716c, distinct from the existing T6050 alias profiles.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
The existing T6050 profiles validate alias addresses and reject the physical
GFX/GFX1 resources used on J716c. Add the shared J775D/J716c profile for
0x2302600000 and 0x2302e00000, including their vector-base registers.
Reuse the ASCWrap v6 transport and retain all existing address profiles.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Retain the PCIe DART inventory guards where native tunnel DARTs already provide the resources.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Provide the four USB4 DisplayPort tunnel pixel-clock gates and clock-source
switches, unwind failed starts, and park idle clocks in USB4 mode. Accept
port index zero when its controller has handed the port to the host.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
T6050 can report a busy crossbar connection after selecting a valid
output. Log that status while retaining the selected route, so an active
USB4 DisplayPort tunnel can complete its display setup.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
@erikwb
erikwb force-pushed the ewb/j716c-public-usb4-mux branch from 7cdbabf to c7c09ce Compare November 9, 2026 02:48
@erikwb
erikwb force-pushed the ewb/j716c-public-usb4-phy branch from 859806e to 0301a07 Compare November 9, 2026 02:48
@erikwb
erikwb marked this pull request as ready for review November 9, 2026 03:59

@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.

Reviewed and load-tested on the M5 Pro. The new T6050 profile equals the T8142 one apart from status_log_only, so T8142 keeps faulting on a busy status as before. On J714s the crossbar node is enabled only by the loader's external-display handoff, so the module loaded but didn't bind here, and the busy-status path wasn't exercised without a display. No regression from it.

Setup. 14-inch M5 Pro (J714sAP, T6050, macOS 27.0 firmware). RAM boot through aura of #1273's head (the whole stack on ewb/j716c-usb4-public-base), plus our M5 Pro GPU commit, plus #1308. #1308 is needed because the base doesn't build t6050-j714s.dtb; see there. The kit DT is the J714s one compiled on this tree. Modules: nvme, display, smc, input, gpu and usb4 (mux-apple-display-crossbar, phy-apple-dptx, atc-t6050-dp, dwc3-apple, sn201202x, thunderbolt, thunderbolt_apple). No display or dock was attached. ATC0 carries the debug link.

Whole-stack result on J714s.

  • Boots to a shell with no oops, WARN or BUG.
  • 2ab000000.phy and 383000000.phy -> apple-t6050-dp-phy (phys .0-.5); 2aa280000.usb and 382280000.usb -> dwc3-apple. typec port0 and port1 register (device/sink, no partner). The debug port is left alone and the KIS link stayed up throughout.
  • Internal panel: display-subsystem -> apple-drm, card1-eDP-1 connected and enabled at 3024x1890. GPU: card0 and renderD128.
  • ATC2 router mailbox 381108000 binds and thunderbolt_apple loads. 381ac0000.acio stays deferred because J714s has no board ports/DROM yet.

KUnit on the same tree (ARM64 QEMU, panic_on_warn=1, PROVE_LOCKING, DEBUG_OBJECTS_WORK): 356 tests, 354 passed, 2 skipped by design.

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