Repository navigation
M3 Max (J516C): external displays over USB-C and HDMI - #292
digital-brew wants to merge 15 commits into
Conversation
…ssbars Add the M3 Max's four external display processors, their mailboxes and firmware DARTs, all disabled, at the addresses, interrupts and power states of the J516C ADT (dcpextN, dart-dcpextN), translated through /arm-io ranges. The internal DCP's ADT values translate to exactly what t6031-dcp.dtsi already has, which checks the translation. dcpext0 0x3_06c00000 mbox IRQ 1068..1071 DART 0x3_0530c000 IRQ 1047 dcpext1 0x4_4ac00000 mbox IRQ 1104..1107 DART 0x4_4930c000 IRQ 1083 dcpext2 0x4_4ec00000 mbox IRQ 1140..1143 DART 0x4_4d30c000 IRQ 1119 dcpext3 0x2_cac00000 mbox IRQ 1176..1179 DART 0x2_c930c000 IRQ 1155 Each processor's ADT power gate is DISPEXTn-CPU-V, whose parents are DISPEXTn_CPU and DISPEXTn_FE, as DISP-CPU-V is the internal DCP's. So each sits on ps_dispextN_fe, as the internal DCP sits on ps_disp_fe. The node shape is T6030's dcpext: Type-C routes to ATC0..2, disabled until the display gate enables their processor. The compatible keeps apple,t6030-dcpext as a fallback so the display driver takes its T6030 external path. apple,typec-mux-indices is N for dcpextN because the ADT's display-crossbar0 lists its upstream ports as dispext0..7 in order; this is not yet confirmed on hardware. Add the three ATC display crossbars (ADT atcN-dpxbar, atc-dpxbar,t602x) at the T6030 addresses. With the t6020 compatible their probe writes no register. Give the PMP's pmp-dispext0..3 requests apple,always-on and apple,defer-startup-ack, as T6030's. They stay disabled, and the report driver seeds only available entries, so nothing changes until the gate enables one. Add the J514C/J516C Type-C port@3 endpoints for all four processors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
Enable the dcpext gate on T6031 behind its own switch, apple_t6031_display.dcpext=1, which is off by default. Without it, or without the boot loader's handoff markers, every processor and its Type-C routes stay disabled. The gate's dcpext path assumed T6030's power order: the processor on dispextN_cpu, the child of dispextN_fe. On T6031 the processor is on dispextN_fe, the child of dispextN_cpu. Resolve the chain in the SoC's order, and check the mailbox, firmware DART and display DART against the processor's own power state, which also gets the active floor (its parents follow it). The boot loader's markers stay the T6030 ones (apple,t6030-dcpext-memory-ready, apple,t6030-dispext-handoff), so the display driver's checks need no change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
A T6031 dcpext loads the same DCP image as its internal DCP (F493F03E-CC40-3D89-9A08-5F259BA2B246, per the J516C ADT's iop nubs). Accept that image on apple,t6031-dcpext nodes only; every other external processor still needs the T6030 image. Also give T6031 the T6030 display-crossbar programming. Both use the same dispext pipes and the same t602x crossbar, and the T6030 path handles dispext bits 0..3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
The display gate needs the pmp-dispextN report entry to have a phandle, and dtc emits one only for referenced nodes. Name the entry in apple,pmp-report, as T8122 does; the gate sets the same value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
dcp_t6030_ext_native() looked only for apple,t6030-display-subsystem, so a T6031 dcpext the gate had handed its display DART fell back to the manual diagnostic path and never joined the DRM device. Look up the subsystem of the processor's own SoC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
dcpext_check_power() required the processor's power domain to be dispext<N>_cpu, as on T6030. On T6031 the dcpext sits on dispext<N>_fe, whose parent is the CPU domain, and the display gate puts the active floor there, so startup was refused with -EINVAL on attach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
A T6031 dispext<N>_fe power state does not hold the PMGR minimum-state field: with apple,min-state written it still reads 0xff, as the internal display's FE state does, while the CPU states keep it. Floor the CPU domain in the display gate (the same node as before on T6030), and have the driver check the floor there, reached through the FE domain the T6031 dcpext sits on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
The J516C 14.7 firmware on a T6031 external processor publishes every timing with an empty ColorModes array; the display's color modes are only in ColorElements, and each timing names the ones it excludes in UnsafeColorElementIDs and DSCRequiredColorElementIDs. Every timing so had no color mode, parse_mode() rejected all of them, and the connector stayed disconnected with HPD asserted. When an external timing has no color mode of its own, select one from the published ColorElements, less its excluded IDs, with the external SDR ranking (full-range SDR RGB, 8 or 10 bits). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
The display crossbar runs T6031 in its T6030 mode (t6030_dpin): a route
is selected on activate, and its clocks come up only from the DCP's
DidChangeLinkConfiguration call, through dcp_direct_crossbar_link().
That function returned early on anything but apple,t6030, so on T6031
the direct DP PHY output stayed unclocked ('T602X route 0, clocks
disabled'), and every mode set failed in the firmware with
'IOAVVideoInterface prepareLink() failed'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
T6031 (M3 Max) has the T6030 (M3 Pro) display fabric: T8122-generation
ATC PHYs, a crossbar in t6030_dpin mode and dcpext processors bound as
apple,t6030-dcpext. Its DP IN tunnel path was still disabled, though:
- the Thunderbolt driver leaves DP IN off on SoCs missing from
apple_dpin_qualified_soc, and the T6031 ACIO matched only the t8103
fallback (M1 DP IN policy);
- the DCP fabric and the ATC PHY pick the T6030 DP IN pixel clock path
and its source map on of_machine_is_compatible("apple,t6030") alone.
Qualify T6031, give its ACIO the T6030 DP IN policy while it keeps the
t8103 boot sequence (its DT node has no MxWrap region), and take the
T6030 clock path on T6031 too. The T6030 source map (crossbar sources
0 and 2 feed DP IN0/1) is assumed to hold on T6031; untested.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
The T6031 (M3 Max) DCP DPTX firmware drives a DP tunnel like a plain DP link: it never requests bandwidth through the DP IN adapter's allocation registers. With allocation mode enabled the tunnel reported "consumed bandwidth 0/12960 Mb/s", the mode set completed and the sink (a DisplayPort monitor behind a USB4 dock, DP OUT x2 HBR3) dropped the stream about 1.5 s later (DPTX INACTIVE_SINK_DETECTED). The same setup works with thunderbolt.bw_alloc_mode=0. Add a TB_HOST_DP_NO_BW_ALLOC host policy that takes the bw_alloc_mode=0 path for that host's tunnels only, and give T6031 its own policy: the T6030 flow plus the new flag. T6030 keeps its current behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
The J516C's HDMI port is ATC3 used as a DisplayPort-only PHY behind display-crossbar0, feeding an on-board DP-to-HDMI converter, as on the J514S/J516S (t603x-j514s-j516s-hdmi.dtsi). The T6031 tree described only ATC0..2, so the port had no connector. From the J516C ADT: - atc-phy3 and atc3-dpxbar sit where the T6030's do (0x1303000000, 0x130304c000); - dp2hdmi-gpio0: hdmi_pwr_en and dp2hdmi_pwr_en are SMC gP0f and gP1a, the J514S/J516S pins; - atc3-hdmi: function-hdmi_hpd is AOP GPIO 42 (47 on the J514S/J516S). dcpext0 takes the fixed route and stays a hybrid pipeline, lent to a USB-C port while no HDMI display is attached. The atcphy3 alias lets m1n1 copy the ADT's atc-phy3 calibration; without it the PHY reports "Calibration tunables missing, DisplayPort disabled". HDMI audio (DPA1 over the SIO) is not described yet. Signed-off-by: DB <rafal+claudeai@digitalbrew.io> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the J516C only dcpext0 brings up an external display. dcpext1..3 time out in the DPTX remote port on USB-C (HPD assert, link configuration) and dcpext3 does the same on the HDMI route: AppleDCPDPTXRemotePort (chan:1) command type 0xc0 tag 0x0300 timed out after 1000 ms dcp_dptx_connect: failed to assert Type-C HPD: -110 DPTXPort: firmware reports link fault 22 on target 0:3 With the HDMI route on dcpext0, the allocator offers a hybrid pipeline to USB-C last, so a USB-C display always landed on a broken processor. Mark dcpext1..3 "fail": the display gate enables only processors left "disabled", and the DRM device waits only for available ones, so dcpext0 serves USB-C and HDMI alike. This is a stopgap that limits the J516C to one external display until dcpext1..3 are fixed; drop it then. Signed-off-by: DB <rafal+claudeai@digitalbrew.io> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chriskearney
left a comment
There was a problem hiding this comment.
On exact head 26ea9ca83c9cf6050ee9990b08cb662f6b753471, the new shared-color fallback introduces a dependency that the publication callback does not rebuild. dcp_v14_external_published() calls dcp_modes_replace() only for TimingElements. A later ColorElements publication updates the description bits/generation and clears mode_live, but does not reparse the retained timings.
A bounded CPU witness using the exact callback confirms: timings with no local colors first produce zero modes; publishing valid shared colors afterward still produces zero modes; republishing timings then produces the mode. Replacing shared color ID 7 with 11 leaves the catalog selecting 7 until timings are republished. The mode-live guard protects existing swap admission; this is a catalog dependency/order issue, not a demonstrated UAF or hardware reproduction. The fallback also applies to existing native v14.7 external displays, beyond the T6031 opt-in.
Please rebuild or invalidate dependent catalogs for either publication order, replacement and withdrawal, with serialized timing/color parser-publication tests and generation transitions. Alternatively, establish and enforce a protocol ordering invariant that makes these sequences impossible. Waiting longer in external_settle does not rebuild the catalog.
Separately, the documented first-high-bandwidth modeset failure and the unpublished local m1n1 external-handoff dependency remain qualification limits. This source finding can be closed without new hardware access; CI/QEMU would not establish external-display qualification.
|
The m1n1 side is up: omacom/m1n1#21 ( |
A T6031 external processor's timings take their colors from ColorElements, but dcp_v14_external_published() built the mode catalog only on a TimingElements publication. Timings published before their colors so gave no mode until republished, replaced colors (ID 7 by 11) left the catalog on the old ID, and withdrawn colors left an admitted catalog naming color IDs that were gone. The fallback also applied to every native v14.7 external processor, beyond the T6031 opt-in. Opt the T6031 dcpext in explicitly (shared_colors), and on it rebuild the catalog from the published timings and the colors published now whenever either is published, replaced or withdrawn, in either order. A color change that leaves the timings no mode withdraws the catalog (dcp_modes_withdraw(): no longer admitted, kept for snapshot readers) and the timings' part of the description, and reports the display withdrawn once: the firmware withdraws ColorElements before TimingElements on an unplug, and a second report would release a connector kept connected through the withdrawal. A removal has no transfer and applies to the current attachment; a transfer begun before it is still refused as stale. The dcp_v14_shared_colors KUnit suite runs the production callback through both publication orders, replacement, unsafe-ID exclusion, withdrawal in both orders (one report each), republication, stale generations, and a processor without the opt-in. With the rebuild on a color change disabled, six of its eight cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHu9VBxYcBYR8voXKiSV65 Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
|
Thanks for the review. The catalog dependency is addressed in f401690 ("drm: apple: rebuild a T6031 dcpext catalog when its colors change"). Rebuild on either publication.
Generations. A ColorElements transfer rebuilds with its own transfer generation, so one begun before the current attachment is still refused with Withdrawal reported once. On hardware the firmware withdraws ColorElements before TimingElements on an unplug (kernel log: Scope. The fallback is now opt-in per processor: Tests. New suite
arm64 KUnit under qemu: 106/106 pass (the Apple display and parser suites). With the rebuild on a color publication disabled, 6 of the 8 new cases fail; the two that still pass are colors-first and no-opt-in, which the previous code already handled. Still open, as you noted: the first high-bandwidth modeset failure, and the m1n1 external handoff dependency (omacom/m1n1#21, awaiting review). Neither is changed by this commit. |
|
The first high-bandwidth modeset limit is addressed in 8f6ea2e ("drm: apple: wait for a dcpext display's timings before its first swap"). As you suggested, the driver no longer swaps before the link timings are enabled; the firmware reports exactly when that is. Cause. After Fix.
Hardware. J516C, 3840x2560 display through a USB-C dock, aurora11 base with patches 1–11 plus this one, no bring-up workaround:
Tests. New The m1n1 dependency remains, in omacom/m1n1#21. The PR description is updated accordingly. |
A T6031 external session whose first mode was fast hung on its first swap: with 3840x2560@60 (DSC) on the J516C 14.7 image, the firmware dropped swap ID 1 and its A407 never completed, ending the session until reboot: IOMFB: swap_submit_dcp: swallowed swap ID 1 as timinsg are not enabled for External Display DCP call 0x41343037 failed: -110; recovery requires a reboot The firmware starts a display's timings in a deferred run mode change after A472(1) and then sets the boolean property NormalModeActive (D104) to 1; A472(1) sets it to 0 first. With a slow mode (2560x1600@60) the change ends within A472, NormalModeActive already 1 when it replies. With the DSC mode it ends about 20 ms after A472 replied, and the swap sent meanwhile is dropped. Later swaps sent early are held instead, so only a session's first mode failed. Logged on a J516C: 8.597 A472(1) 8.599 D104 NormalModeActive = 0 8.613 A472 replied; A406, A407 sent 8.614 swallowed swap ID 1 as timings are not enabled 8.633 switch to normal mode succeeded; D104 NormalModeActive = 1 After A472(1), wait under the RPC lock for NormalModeActive (service 0) to be 1, handling the queued callbacks as dcp_v14_idle() does, for up to 500 ms. A timeout fails the mode set into the retry path; a failed callback stops the session; a firmware that never reported the property is not waited for. Every power-on is waited for, about 20 ms for the first DSC mode and 10 ms for later ones. On the J516C with a 3840x2560 display (DP over USB-C), six boots (reboot, power cycle, replug) set 3840x2560@60 first and showed its first swap in 18 ms; 14 later mode sets, 3840x2560@120 included, all succeeded. Without the wait, 5 of 5 boots lost the session. The dcp_v14_normal_mode KUnit suite runs the wait and the production D104 handler with a scripted link pump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHu9VCTrUXYgBxj2gKTGrp Signed-off-by: DB <rafal+claudeai@digitalbrew.io>
b66db34 to
8f6ea2e
Compare
Problem. On the 16-inch M3 Max MacBook Pro (J516C, T6031) no external display works: the USB-C ports never get a display pipeline, and the HDMI port has no connector at all. The T6030 (M3 Pro) external display support does not reach T6031: its four dcpext processors and the ATC display crossbars are not described, several T6030 paths check
apple,t6030alone, and the J516C tree describes only ATC0..2, not the ATC3 PHY behind the HDMI port.Fix. Bring T6031 onto the T6030 external display path, then describe the J516C HDMI port.
Patches
apple_t6031_display.dcpext=1.dispext<N>_fe.dispext<N>_festate does not hold the PMGR minimum-state field.IOAVVideoInterface prepareLink() failed.atcphy3alias lets m1n1 copy the ATC3 calibration.Depends on m1n1. The handoff comes from an m1n1 change that is not in this PR:
kboot_display_handoff: hand off the T6031 external display processors, on omacom/m1n1bc79acc4, armed bychosen.asahi,t6031-dispext=1: omacom/m1n1#21.Why patch 13. On this J516C only dcpext0 brings up a display. dcpext1..3 time out in the DPTX remote port, on USB-C and, for dcpext3, on the HDMI route too:
With HDMI on dcpext0, the allocator offers a hybrid pipeline to USB-C last (
dcp_fabric_score), so every USB-C display landed on a broken processor. Marking dcpext1..3"fail"keeps them from the gate, and the DRM device waits only for available processors. This limits the J516C to one external display until dcpext1..3 are understood. I have not found why they fail.Testing (J516C, iBoot
10151.140.19.700.2, OS firmware 14.7, system firmware reported asunknown; m1n1bc79acc4+ the handoff change; boot withapple_t6031_display.dcpext=1; Hyprland):3bdf11d3(aurora11) + this series. Rebased onto12be0475without conflicts; not re-run on that base.DSC=YES). All three ports passed with patches 1–11; with patches 12–13, one port was re-run.dcp_v14_shared_colors(patch 14) anddcp_v14_normal_mode(patch 15) suites, underW=1andPROVE_LOCKING.First-mode failure, fixed by patch 15. When an external display's first mode on a new connection was a DSC one (3840x2560@60), the first swap was dropped and the processor stopped until reboot:
Cause: after
A472(1)the firmware starts the display's timings in a deferred run mode change, then sets the boolean propertyNormalModeActive(D104) to 1. With a slow first mode (2560x1600@60) that change ends inside A472; with the DSC mode it ends about 20 ms after A472 replied, and a swap sent in that gap is dropped. Later swaps sent early are held rather than dropped, which is why only a session's first mode failed. With dynamic debug on the callbacks:Patch 15 waits after every power-on, under the RPC lock, until
NormalModeActiveis 1 (handling queued callbacks asdcp_v14_idle()does, 500 ms limit, no wait if the firmware never reported it). On the J516C through a USB-C dock with the 3840x2560 display, tested on the aurora11 base with patches 1–11 + 15: six boots (reboot, power cycle, hotplug after login) each set 3840x2560@60 first and completed its first swap in about 18 ms; 20 mode sets in all, 8 at 120 Hz with DSC, all succeeded. The wait took 10–21 ms. Without it, 5 of 5 boots lost the session.Related: #61 (M3 Max bring-up), #289 (G15 firmware pin on the J516C, needed for the GPU on this Mac; separate change).
🤖 Generated with Claude Code