Skip to content

t600x: drive both displays of a dual-stream Thunderbolt dock (j314/j316) - #373

Open
kloploid wants to merge 2 commits into
omacom:integration/aurorafrom
kloploid:t600x-dock-dual-stream
Open

kloploid wants to merge 2 commits into
omacom:integration/aurorafrom
kloploid:t600x-dock-dual-stream

Conversation

@kloploid

@kloploid kloploid commented Nov 5, 2026 •

Copy link
Copy Markdown

Problem

On M1 Pro/Max laptops, a dual-stream Thunderbolt dock (Dell WD19TB) drives only one of its two displays.

  1. The t600x laptop DT names only each USB-C port's DP-alt crossbar control on dcpext0/dcpext1. So the dock's second DP tunnel (DPIN1) has no connector:
    no Type-C connector for the DP tunnel
    
  2. With DPIN0/DPIN1 named, both displays connect, but only one stays lit. On t600x, atc_tunnel_start_t8103() programs one tunnel pixel-clock selector for PCLK1, PCLK2 and DPRX. Here one display trained at HBR3 x4 and the other at HBR2 x2. Whichever trained second reselected the shared clock, and the first stream died with a DPTX FIFO error.

Changes

  • arm64: dts: apple: t600x-j314-j316: name typecN-dpin0 / typecN-dpin1 on both external DCPs, the same as t602x-j414-j416.dtsi.
  • drm/apple: dptxep: on t6000/t6001, report HBR2 as the maximum link rate for DP tunnels only, so both streams share one rate and the shared clock. HBR2 x4 still covers 4K60 at 8-bit colour. Direct DP-alt and HDMI are unchanged. The module parameter appledrm.t600x_tunnel_max_hbr2 (default Y) turns the cap off.

I also tried giving each DPIN its own selector (DPIN0→PCLK1, DPIN1→PCLK2, as on t602x). With DPRX following either DPIN, the second stream failed link training (DPTXPort: firmware reports link fault 22, prepareLink() failed with 0xe00002ed). So the cap is the fix that works for now. Correct per-DPIN clocking on t600x is still an open question.

Trade-off: on M1 Pro/Max, a single display over a Thunderbolt tunnel is also limited to HBR2 x4. Modes that need HBR3 over a tunnel lose them while the cap is on (for example, uncompressed 4K at 120 Hz), so appledrm.t600x_tunnel_max_hbr2=0 restores the old behaviour. The cap could later be limited to when a second DPIN is live, but the rate is chosen at training time, before that is known.

Related: #154 (Thunderbolt bandwidth sharing between Apple host DP tunnels). The two PRs don't conflict, and I've tested them together (see Results). With mismatched monitors, this PR needs #154. @JarenKempton tested on j314s with a CalDigit TS4 and found that the cap is only a ceiling: with #373 alone, a starved second tunnel trained at HBR while the first ran at HBR2, and that brought back the FIFO error. With #154 added, both tunnels got an even bandwidth share, both trained at HBR2, and there were no FIFO errors. So the two should land together, or #154 first. On j314c, #154 also ends with both 5K tiles at HBR2 x4, which fits the shared-clock explanation: two tunnels at the same rate work.

Machine and build

Results

Test Result
Both dock displays lit together, j316s pass (after a reboot and several hotplugs)
DPTX FIFO errors in the kernel log none, at boot and after hotplug
DT change compared with the hand-applied overlay this was tested with identical mux-controls / mux-control-names on both DCPs
checkpatch.pl --strict, W=1 build of dptxep.c clean
Link rate with the cap, j316s (dyndbg on dptxep.c) SET_LINK_RATE 0x14 on both DCPs at boot, on wake and on replug: dpin0 HBR2 x4, dpin1 HBR2 x2
Boot with dock and both displays attached, j316s, #373 alone and #373 + #154 pass, identical apart from #154's sharing the path with 2 DP OUT adapters, offering 18000 Mb/s
Dock replug, j316s, #373 + #154 pass: both displays came back without intervention, no FIFO errors (the first PCIe tunnel activation fails and retries; this isn't display related)
s2idle suspend/resume, j316s, #373 + #154 known issue: on wake, one display stays dark (see below)
j314s (M1 Pro 14") + CalDigit TS4, mismatched sinks (5120x1440@120 + 2560x1440@155), by @JarenKempton #373 alone: FIFO errors. #373 + #154: pass, both at full mode
j314c, j316c (M1 Max) not tested, no hardware
M1 Ultra (t6002) not tested; it probably has the same shared clock but isn't covered by the cap
Single display over a tunnel at HBR3 with the cap off not tested

The cap message is dev_dbg. To see it, along with DPTXPort: SET_LINK_RATE and the tunnel clock rate, enable dynamic debug:

echo 'file dptxep.c +p; file atc.c +p; file dcp-fabric.c +p' > /sys/kernel/debug/dynamic_debug/control

On Omarchy installs that boot a UKI through limine, appledrm is loaded from the UKI's embedded initramfs, so rebuilt modules need limine-mkinitcpio (thanks @JarenKempton).

Known issue: one display stays dark after s2idle wake (not caused by this PR, fix to follow separately)

I reproduced this twice on j316s + WD19TB. It's the same failure @Dazzzz saw after DPMS wake. On each wake, one DPIN of the pair loses its tunnel route: the dpin1 display the first time, the dpin0 display the second time. The other DPIN comes back normally at HBR2. Link rates were HBR2 on both sides throughout, so this isn't the shared-clock rate problem this PR fixes.

What the log shows for the stuck DPIN (second wake, dyndbg on):

289c00000.dcp: failed to deassert Type-C DPTX HPD: -110          (at suspend)
[drm:drm_mode_config_helper_resume] *ERROR* Failed to resume (-22)
28cc00000.dcp: display routed to Thunderbolt DP tunnel dpin1     (the other DPIN: OK, SET_LINK_RATE 0x14)
289c00000.dcp: Type-C IOMFB hotplug not ready on wake
289c00000.dcp: set_digital_out_mode(color:85 timing:83) "1920x1080" ...
289c00000.dcp: setmode failed
289c00000.dcp: ERROR: Modeset done, but pipe not enabled: fSoftPowerState=1, fDisplayPowerState=0, fHardPowerState=1
289c00000.dcp: set_digital_out_mode returned 80000104            (x6, then gives up)

The stuck DPIN is never routed again and never gets a SET_LINK_RATE. It recovers only if that DPIN gets an HPD cycle (which happened by itself on the first wake) or the dock is replugged. dcp_poweron() goes on to a modeset after the 3 s IOMFB hotplug wait times out. For a tunnel route, it should probably tear the route down and attach it again instead. I'll send that as a separate PR.

Remaining DCP firmware log lines, also present without these patches:

IOMFB read_pmu_data_sync: pmu ram read error (e00002d8)
IOMFB updateFrequencies EDT ERROR: getClockFrequency(0) (0) < videoClock 74250000! Giving up on frequencies.

🤖 Generated with Claude Code

A Thunderbolt dock carries each display in its own DP tunnel, and the
second one arrives on a USB-C port's DPIN1. The M1 Pro/Max laptop device
tree only names each port's DP-alt control on dcpext0 and dcpext1, so
apple-drm creates no tunnel connector and the second stream fails with
"no Type-C connector for the DP tunnel": a dual-stream dock drives only
one display.

Name typecN-dpin0 and typecN-dpin1 on both external DCPs, as
t602x-j414-j416.dtsi does for the M2 Pro/Max laptops. Cell 1 of each
port's crossbar selects DPIN0 and cell 2 selects DPIN1.

On its own this lets the second display connect but not stay up; t600x
also needs the following drm/apple change to keep two tunnels at one
link rate.

Tested on a MacBook Pro 16" M1 Pro (j316s) with a Dell WD19TB dock and
two 1920x1080@60 displays. Not tested on j314s, j314c or j316c.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Mark Zelinski <mark.zelinski890@gmail.com>
With both DP IN adapters of a port in use, as with a dual-stream dock,
only one display stays lit on the M1 Pro/Max. On t600x,
atc_tunnel_start_t8103() programs a single tunnel pixel clock selector
for PCLK1, PCLK2 and DPRX. When the two streams train at different link
rates (here HBR3 x4 for one display and HBR2 x2 for the other), whichever
trains second reselects that clock under the first, and the first stream
dies with a DPTX FIFO error.

Report HBR2 as the maximum link rate for DP tunnels on t6000 and t6001,
so both streams train at the same rate and share the clock. HBR2 x4
still covers 4K at 60 Hz with 8-bit colour. Direct DP-alt and HDMI
outputs are unaffected. The t600x_tunnel_max_hbr2 module parameter
(default on) restores HBR3 for single-display tunnel setups.

Giving each DPIN its own selector (DPIN0 -> PCLK1, DPIN1 -> PCLK2, as
atc_tunnel_set_t602x() does) was also tried on j316s. With DPRX on
either DPIN, the second stream failed link training with firmware link
fault 22, so the cap is the fix that works for now.

Tested on a MacBook Pro 16" M1 Pro (j316s) with a Dell WD19TB dock:
both displays light up together after a reboot and several replugs,
with no DPTX FIFO errors. Not tested on M1 Max or M1 Ultra.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Mark Zelinski <mark.zelinski890@gmail.com>
@Dazzzz

Dazzzz commented Nov 6, 2026

Copy link
Copy Markdown

Same failure on j314c (M1 Max 14", t6001), Microsoft Surface Thunderbolt 4 Dock, on unpatched aurora14

Here is a data point from the board this PR covers in code but lists as "not tested". This is the failure without the patch. I'm happy to test the PR branch.

Setup

  • MacBook Pro 14" M1 Max (apple,j314c apple,t6001), firmware asahi,os-fw-version 13.5
  • linux-aurora 7.2.11.aurora14-1 (7.2.11-14-1-ARCH), Arch Linux ARM / Omarchy, Hyprland 0.56.2
  • Microsoft Surface Thunderbolt 4 Dock (vendor 0x17c, device 0x856, Intel retimer 0x8087:0x15ee), USB4 40 Gb/s, on cio f01ac0000
  • Two 3840x2160@60 monitors on the dock: HP Z27 and HP Z27k G3

Behaviour

  • The first stream (dpin0 → dcp 289c00000, crossbar f0304c000) comes up and works.
  • The second stream goes to dcp 28cc00000 on dpin1 and fails with no Type-C connector for the DP tunnel, which matches the DT gap described in this PR.
  • On this machine the failure goes further than the PR describes. About 0.8 s later the USB4 PCIe tunnel reset times out and the whole Thunderbolt link drops. The first display, USB and Ethernet on the dock all go, and then the dock re-enumerates. Unplugging also logs Failed to lock pipehandler (compare aurora14: DPTX re-enumeration lands on 1920x1080, sink re-time is silent, and teardown leaves the ATC pipehandler locked (panel dead until power cycle) #375).
  • Workaround: one monitor on the dock and the second on a direct USB-C port or HDMI. Three displays work that way.

Log (journalctl -k, the second tunnel's attempt)

[3120.274515] apple-dcp 28cc00000.dcp: allocated Type-C DPTX PHY 2
[3120.274700] apple-dcp 28cc00000.dcp: display routed to Thunderbolt DP tunnel dpin1
[3120.274912] apple-dcp 28cc00000.dcp: no Type-C connector for the DP tunnel
[3121.053006] pcie-apple f30000000.pcie: port /soc/usb4-pcie-tunnel-2/pcie@f30000000/pci@0,0 tunnel reset acknowledgment timed out
[3121.054170] apple-dcp 289c00000.dcp: dcp_hotplug() connected:0 valid_mode:1 nr_modes:21
[3121.060520] thunderbolt 0-1: device disconnected
[3121.105034] apple-display-crossbar f0304c000.mux: Switched dpin0 to disconnected state
[3121.674009] phy-apple-atc f03000000.phy: Pipehandler lock not acked.
[3121.674203] phy-apple-atc f03000000.phy: Failed to lock pipehandler
[3121.678329] thunderbolt-apple-nhi f01f00000.nhi: device links to tunneled native ports are missing!
[3123.195877] thunderbolt 0-1: Microsoft Surface Thunderbolt(TM) 4 Dock

This repeats every time the second tunnel is attempted. Full logs are available on request.

Both monitors are 4K60, so the HBR2 cap in this PR is fine for my case: one 4K60 stream fits in HBR2 x4. If there's a test build or a branch I can build, I'll report back with results, including whether the link-drop described above goes away once dpin1 has a connector.

@kloploid

kloploid commented Nov 6, 2026

Copy link
Copy Markdown
Author

Thanks, this is very useful. The j314c is covered (it includes t600x-j314-j316.dtsi, and the cap matches apple,t6001), but it hasn't been tested on one yet, so a report would help a lot.

The branch is t600x-dock-dual-stream on kloploid/linux-aurora, two commits on top of b798f2b0c767. You don't need a full kernel build on 7.2.11-14-1-ARCH, only the DTB and appledrm.ko:

git clone --filter=blob:none --sparse -b t600x-dock-dual-stream https://github.com/kloploid/linux-aurora
cd linux-aurora
git sparse-checkout set arch/arm64/boot/dts/apple include/dt-bindings drivers/gpu/drm/apple
K=/usr/lib/modules/$(uname -r)/build

# 1. DTB (use the kernel's dtc; the system dtc rejects the float properties in t600x-die0.dtsi)
cpp -nostdinc -undef -D__DTS__ -x assembler-with-cpp -I include -I arch/arm64/boot/dts \
    arch/arm64/boot/dts/apple/t6001-j314c.dts -o j314c.pre.dts
$K/scripts/dtc/dtc -q -I dts -O dtb -o t6001-j314c.dtb j314c.pre.dts
D=/usr/lib/modules/$(uname -r)/dtbs
sudo cp -a $D/t6001-j314c.dtb $D/t6001-j314c.dtb.orig
sudo install -m644 t6001-j314c.dtb $D/
sudo update-m1n1

# 2. appledrm with the HBR2 tunnel cap
make -C $K M=$PWD/drivers/gpu/drm/apple LLVM=1 -j$(nproc) modules
sudo install -Dm644 drivers/gpu/drm/apple/appledrm.ko /lib/modules/$(uname -r)/updates/appledrm.ko
sudo depmod -a
sudo mkinitcpio -p linux-aurora && sudo limine-mkinitcpio   # if you boot a Limine UKI

Reboot, then plug in the dock. To undo, restore t6001-j314c.dtb.orig and run update-m1n1, then remove updates/appledrm.ko and rebuild the initramfs. If boot hangs, boot with the dock unplugged. A kernel package update replaces the DTB anyway.

The logs that would help most:

  • journalctl -k | grep -E 'dpin|DPTX|link rate|SET_LINK_RATE|FIFO|tunnel|pipehandler' from one plug.
  • Whether both monitors stay lit, and for how long.
  • Whether the Thunderbolt link still drops.

The link drop isn't something I saw on the WD19TB. My guess is that it follows from the failed second tunnel, but I can't promise it goes away.

Two 4K60 streams at HBR2 x4 come to about 34.6 Gb/s of a 40 Gb/s link, so the dock's USB and Ethernet will have less room. #154 changes how the connection manager splits that bandwidth. It's a separate thunderbolt.ko change, worth trying as a second step if this one misbehaves.

@Dazzzz

Dazzzz commented Nov 6, 2026

Copy link
Copy Markdown

Tested on M1 Max 14" (j314c), 7.2.11-14-1-ARCH, built from t600x-dock-dual-stream @ bac0e09efe27 (DTB + appledrm.ko only, as you described). Dock: Surface Thunderbolt 4 Dock, two 4K60 monitors (HP Z27 + HP Z27k G3) both on the dock.

Result: dual-stream works after a replug, but not at boot. This is the first time I've had two monitors on this dock under Linux. The second stream trains at HBR2 and comes up at 3840x2160@60 next to the first one. Out of three plug events:

Event dpin0 (Z27) dpin1 (Z27k) TB link
Boot with dock attached ✅ 4K60 (after retrain) ❌ DPTX calls time out ❌ dropped and retrained
Replug 1 (21:09) ✅ unaffected ❌ no EDID, 640x480, startLink() failed ✅ stayed up
Replug 2 (21:15, dynamic debug on) ✅ unaffected ✅ 4K60 ✅ stayed up

Replug 2 (works). The cap is applied, and dpin1 trains at HBR2:

21:15:11 apple-dcp 28cc00000.dcp: display routed to Thunderbolt DP tunnel dpin1
21:15:11 apple-dcp 28cc00000.dcp: DP tunnel: capping max link rate at HBR2 (t600x shared tunnel clock)
21:15:11 apple-dcp 28cc00000.dcp: DPTXPort: SET_LINK_RATE 0x14
21:15:11 apple-display-crossbar f0304c000.mux: Switched dpin1 to dispext1,0
21:15:11 apple-dcp 28cc00000.dcp: set_active_lane_count: unexpected lane count:4 phy: 0
21:15:11 apple-dcp 28cc00000.dcp: dcp_hotplug() connected:1 valid_mode:0 nr_modes:27
21:15:11 apple-dcp 28cc00000.dcp: set_digital_out_mode(color:73 timing:50) "3840x2160": 60 594000 ...

Boot (fails). dpin1's DPTX calls time out, teardown fails, and the whole Thunderbolt link drops and retrains. dpin0 then comes back on its own, but dpin1 doesn't:

21:08:44 apple-dcp 289c00000.dcp: display routed to Thunderbolt DP tunnel dpin0
21:08:45 apple-display-crossbar f0304c000.mux: Switched dpin0 to dispext0,0
21:08:45 thunderbolt-apple-acio f01ac0000.cio: dpin1: waiting for display driver/firmware readiness
21:08:46 apple-dcp 28cc00000.dcp: display routed to Thunderbolt DP tunnel dpin1
21:08:47 pcie-apple f30000000.pcie: port .../pci@0,0 tunnel reset acknowledgment timed out
21:08:47 apple-dcp 28cc00000.dcp: AFK[ep:2a]: AppleDCPDPTXRemotePort (chan:1) service call group 8 cmd 8 failed to complete: -110
21:08:47 apple-dcp 289c00000.dcp: DPTXPort: firmware reports link fault 22 on target 0:2
21:08:48 apple-dcp 28cc00000.dcp: AFK[ep:2a]: AppleDCPDPTXRemotePort (chan:1) service call group 0 cmd 7 failed to complete: -110
21:08:48 thunderbolt-apple-acio f01ac0000.cio: RTKit: syslog message: lane_init.c:954: SBX disconnected!
21:08:49 thunderbolt-apple-acio f01ac0000.cio: dpin1: display teardown failed: -110
21:08:49 phy-apple-atc f03000000.phy: Failed to lock pipehandler
21:08:49 usb usb4: USB disconnect, device number 1
21:08:51 apple-dcp 28cc00000.dcp: DPTXPort: firmware reports link fault 22 on target 0:2
21:08:51 apple-dcp 28cc00000.dcp: DPTXPort: firmware reports link fault 24 on target 0:2
21:08:53 apple-dcp 289c00000.dcp: display routed to Thunderbolt DP tunnel dpin0

dpin1 was set up about 2 s after dpin0, while the USB4 PCIe tunnel was still coming up ("tunnel reset acknowledgment timed out" in the same second). That looks like a bring-up race rather than the clock problem.

Replug 1 (fails). dpin1 is routed, but no EDID is read (3 fallback modes), and the 640x480 modeset fails at startLink() failed with 0xe00002e3. I can't tell from this log why it failed when replug 2 worked. The only difference I know of is that dynamic debug was on for replug 2.

No DPTX FIFO errors appeared in any of these, or in my pre-test log.

I'll report back on how long both monitors stay lit, and I can test #154 or a fix for the boot-time race if that helps.

@kloploid

kloploid commented Nov 6, 2026

Copy link
Copy Markdown
Author

Thanks for testing so quickly. Your replug 2 confirms the main point of the PR on t600x: the second tunnel gets a connector, both streams train at HBR2 (SET_LINK_RATE 0x14), both 4K60 displays run side by side, and there are no DPTX FIFO errors. I'll add j314c to the tested list.

About the boot failure. I don't think the PCIe tunnel causes it. tunnel reset acknowledgment timed out comes from apple_pcie_tunnel_stop(), so the PCIe tunnel is being torn down because the link is already going. Two things in your log point at the second display bring-up instead:

  • dpin1 starts about 2 s after dpin0, while dpin0 is still training (it comes up "after retrain").
  • The fault is reported on dpin0's DCP, for dpin1's target: 289c00000.dcp: DPTXPort: firmware reports link fault 22 on target 0:2.

That fits the t600x shared tunnel pixel clock from the PR description: bringing up dpin1 reprograms the selector that dpin0 is still training on. My j316s with a WD19TB gets dpin1 about 5 s after dpin0. The firmware drops its first attempt once and the retry sticks, but the link never goes down.

A patch to test. Branch t600x-dpin-stagger on kloploid/linux-aurora is this PR plus one commit to drivers/thunderbolt/apple.c. It holds the second DP IN adapter back until the first one has been handed to appledrm for thunderbolt_apple.dpin_stagger_ms (default 4000 on t600x, 0 elsewhere). It builds the same way as before, plus the Thunderbolt modules:

git fetch origin t600x-dpin-stagger && git switch t600x-dpin-stagger   # in the clone from before
git sparse-checkout add drivers/thunderbolt
K=/usr/lib/modules/$(uname -r)/build
make -C $K M=$PWD/drivers/thunderbolt LLVM=1 -j$(nproc) modules
sudo install -Dm644 -t /lib/modules/$(uname -r)/updates/thunderbolt \
    drivers/thunderbolt/thunderbolt.ko drivers/thunderbolt/thunderbolt_apple.ko
sudo depmod -a && sudo mkinitcpio -p linux-aurora && sudo limine-mkinitcpio

Install both .ko files together, because they come from the same tree. With it you should see dpin1: waiting NNNN ms for dpin0's link to settle at boot. The parameter is writable at run time, so you can also try other values before a replug, for example echo 8000 | sudo tee /sys/module/thunderbolt_apple/parameters/dpin_stagger_ms, and 0 turns it off.

What would tell us the most:

  1. A few cold boots with the dock attached: does dpin1 come up, and does the USB4 link still drop?
  2. Replugs, so we can see whether the replug 1 failure (no EDID, startLink() failed) still happens.
  3. For any failure, the journalctl -k from the first thunderbolt line to the retrain.

If the stagger helps, I'll send it as a separate PR on top of this one. If it doesn't, those logs should show what to try next. #154 probably won't change this, since both your tunnels already end up at HBR2 x4.

@Dazzzz

Dazzzz commented Nov 7, 2026

Copy link
Copy Markdown

Thanks for adding j314c. I tested t600x-dpin-stagger @ e8c8947e2a43 (the two Thunderbolt modules on top of the earlier DTB + appledrm.ko), same machine, kernel and dock. dpin_stagger_ms was left at the default (-1, so 4000 on t600x).

Result: replug is fine, but boot still fails, and the stagger shows dpin1 isn't the trigger.

Event dpin0 (Z27) dpin1 (Z27k) TB link
Cold boot with dock attached ✅ 4K60 (after the link retrained) ❌ never handed over ❌ dropped and retrained
Replug 1 ✅ unaffected ✅ 4K60 first time (27 modes) ✅ stayed up

Cold boot. The stagger kicked in as intended, but the link dropped while dpin1 was still waiting. dpin1 was never routed to the DCP before the drop:

11:26:01 apple-dcp 289c00000.dcp: display routed to Thunderbolt DP tunnel dpin0
11:26:03 apple-display-crossbar f0304c000.mux: Switched dpin0 to dispext0,0
11:26:03 thunderbolt-apple-acio f01ac0000.cio: dpin1: waiting 4000 ms for dpin0's link to settle
11:26:03 apple-dcp 289c00000.dcp: dcp_hotplug() connected:1 valid_mode:0 nr_modes:21
11:26:03 apple-dcp 289c00000.dcp: set_digital_out_mode(color:91 timing:48) "3840x2160": 60 533250 ...
11:26:03 thunderbolt-apple-acio f01ac0000.cio: RTKit: syslog message: high_speed_lane.c:289: Gen2/3 link error. lane=0, error=83
11:26:03 thunderbolt-apple-acio f01ac0000.cio: RTKit: syslog message: high_speed_lane.c:289: Gen2/3 link error. lane=1, error=84
11:26:03 usb 3-1.3-port1: cannot reset (err = -71)
11:26:04 pcie-apple f30000000.pcie: port .../pci@0,0 tunnel reset acknowledgment timed out
11:26:04 thunderbolt 0-1: device disconnected
11:26:05 thunderbolt-apple-acio f01ac0000.cio: RTKit: syslog message: lane_init.c:954: SBX disconnected!
11:26:05 apple-dcp 289c00000.dcp: DPTXPort: firmware reports link fault 22 on target 0:2
11:26:07 usb usb4: USB disconnect, device number 1
11:26:10 thunderbolt 0-1: Microsoft Surface Thunderbolt(TM) 4 Dock
11:26:10 apple-dcp 289c00000.dcp: display routed to Thunderbolt DP tunnel dpin0
11:26:12 apple-dcp 289c00000.dcp: dcp_hotplug() connected:1 valid_mode:0 nr_modes:21

The first sign of trouble is the Gen2/3 lane errors on both lanes, in the same second as dpin0's 4K60 modeset. USB on the dock starts failing (-71) right after, then the PCIe tunnel reset times out and the link goes down. dpin0's link fault 22 comes after the TB link is already down, so it looks like a result, not the cause. I saw the same Gen2/3 lane errors in my earlier dock-plug log with the #373 build.

After the link retrained, dpin0 came back on its own, but there's no further dpin1 activity at all: no new "waiting" line and no handoff. The Z27k stayed dark until I replugged it. So separately from the drop, dpin1 doesn't seem to be re-requested after the link comes back.

Replug (works). The Z27k came up first time, with no link fault and no TB drop. The no-EDID / startLink() failure from my first replug last time didn't happen:

12:02:16 apple-dcp 28cc00000.dcp: display routed to Thunderbolt DP tunnel dpin1
12:02:16 apple-display-crossbar f0304c000.mux: Switched dpin1 to dispext1,0
12:02:17 apple-dcp 28cc00000.dcp: dcp_hotplug() connected:1 valid_mode:0 nr_modes:27
12:02:17 apple-dcp 28cc00000.dcp: set_digital_out_mode(color:73 timing:50) "3840x2160": 60 594000 ...
12:02:17 apple-dcp 28cc00000.dcp: set_digital_out_mode finished:8266

This is one cold boot and one replug. I can do more boots, try other dpin_stagger_ms values, or test #154 if any of that would help. Full logs are available if you want them.

@Dazzzz

Dazzzz commented Nov 7, 2026 •

Copy link
Copy Markdown

One more failure with the same t600x-dpin-stagger build: dpin1 can fail to come back after the screens turn off (DPMS) and wake, and once it fails, only a full dock replug brings the picture back.

Sequence (Hyprland idle: screen locked, then DPMS off and back on twice in quick succession):

Time Event dpin0 (Z27) dpin1 (Z27k)
21:05:11 DPMS on ✅ ✅ modeset OK
21:05:17 DPMS off dcp_dptx_disconnect, HPD removed
21:05:25 DPMS on ✅ ❌ startLink() failed with 0xe00002e3, then connected:0
21:24:55 Z27k reconnected at the dock ✅ ⚠️ routed to dpin1, modeset reports success, screen stays black
21:28:27 Display disabled/re-enabled from the compositor ✅ ⚠️ modeset reports success (different timing), still black
21:29:41 Whole dock unplugged and replugged ✅ ✅ picture back at 4K60

There was no Thunderbolt link drop and no link fault at any point before the dock replug. dpin0 was unaffected throughout.

Wake failure (21:05:25). This is the same startLink() error I saw on my first replug with the #373 build:

21:05:25 apple-dcp 28cc00000.dcp: RTKit: syslog message: eoInterfaceIOAV.cpp:714: ...plug_gated(...): display HPD asserted
21:05:25 apple-dcp 28cc00000.dcp: dcp_hotplug() connected:1 valid_mode:0 nr_modes:27
21:05:25 apple-dcp 28cc00000.dcp: set_digital_out_mode(color:73 timing:50) "3840x2160": 60 594000 ...
21:05:25 apple-dcp 28cc00000.dcp: RTKit: syslog message: eoInterfaceIOAV.cpp:963: ...plug_gated(...): IOAVVideoInterface startLink() failed with 0xe00002e3
21:05:25 apple-dcp 28cc00000.dcp: RTKit: syslog message: nifiedPipeline.cpp:7739: setmode failed
21:05:25 apple-dcp 28cc00000.dcp: RTKit: syslog message: nifiedPipeline.cpp:7758: ERROR: Modeset done, but pipe not enabled: fSoftPowerState=1, fDisplayPowerState=0, fHardPowerState=1
21:05:25 apple-dcp 28cc00000.dcp: RTKit: syslog message: nifiedPipeline.cpp:7465: set_digital_out_mode returned 80000104
21:05:25 apple-dcp 28cc00000.dcp: dcp_hotplug() connected:0 valid_mode:0 nr_modes:27
21:05:25 apple-dcp 28cc00000.dcp: dcp_dptx_disconnect(port=0)

Black screen after reconnecting the Z27k (21:24:55). Everything looks normal in the log, including link: 1, but the monitor shows nothing:

21:24:55 apple-dcp 28cc00000.dcp: display routed to Thunderbolt DP tunnel dpin1
21:24:55 apple-display-crossbar f0304c000.mux: Switched dpin1 to dispext1,0
21:24:55 apple-dcp 28cc00000.dcp: dcp_hotplug() connected:1 valid_mode:0 nr_modes:27
21:24:55 apple-dcp 28cc00000.dcp: RTKit: syslog message: nifiedPipeline.cpp:7356: ...mode_set_gated(...): 3840x2160@60 Hz link: 1
21:24:55 apple-dcp 28cc00000.dcp: RTKit: syslog message: UPPipe.cpp:3767: IOMFB read_pmu_data_sync: pmu ram read error (e00002d8)
21:24:55 apple-dcp 28cc00000.dcp: set_digital_out_mode finished:8274

The disable/re-enable at 21:28 picked a different timing (color:79 timing:48, 533.25 MHz, 10-bit), with the same result: success in the log, black on the monitor. The identical-looking sequence after the full dock replug at 21:30:05 did give a picture. So after the failed startLink(), something stays wrong on the dpin1 path that only a Thunderbolt link reset clears, even though the DCP believes the link is up.

Full journalctl -k from 21:04 to after the dock replug is available if useful. I'm leaving DPMS on idle enabled, so I can retest any fix for this. The workaround for now is a full dock replug.

@briantoll

Copy link
Copy Markdown

I run a j314c (M1 Max) with an Apple Studio Display XDR over a Thunderbolt DP tunnel on integration/aurora plus a few local patches, daily since mid-October, so I can test this on the single-display case if that helps.

Two things I would check on this hardware, since the XDR is the demanding end of what a t600x drives:

  • Whether the HBR2 cap in dptxep.c keeps the XDR's 5120x2880@60 10-bit tiled mode (2x1 dual-SST, colour mode 69) and its 3840x2160@120 mode alive. Both train today at the rate the firmware picks.
  • Interaction with thunderbolt: share an Apple host link between DP tunnels that come up together #154, which shares the Apple host link between DP tunnels that come up together. That one is about the Thunderbolt side (sharing the path … 17325 Mb/s when two tunnels race at plug-in), and a dual-stream dock is exactly the case where both tunnels appear at once, so the two patches may want to land together or at least be tested together.

If you can point me at a branch with the DT change and the dptxep change applied, I'll build it into my local kernel and report the dmesg for plug, unplug and replug with the XDR. Notes on how this display behaves on the TB tunnel, including the mode and colour-mode tables decoded from DCP: https://github.com/briantoll/omarchy-thunderbolt-display.

@JarenKempton

Copy link
Copy Markdown

Tested on MacBook Pro 14" M1 Pro (j314s, t6000), 7.2.11-14-1-ARCH (aurora14, 4fdd0fc), with a CalDigit TS4 and two different monitors on the dock: Samsung C49RG9x (5120x1440@120) and Dell S2719DGF (2560x1440@155). I applied #373 on top of 4fdd0fc: rebuilt appledrm.ko, plus the equivalent mux-controls / mux-control-names change in the shipped t6000-j314s.dtb.

Result: #373 alone was not enough with mismatched monitors. With #154 added, both run at full mode.

#373 only: both DPINs get a connector (the no Type-C connector for the DP tunnel error is gone), and the cap is applied on both DCPs. But the cap is a ceiling, not a target, so the two sinks still trained at different rates:

289c00000.dcp: DP tunnel: capping max link rate at HBR2 (t600x shared tunnel clock)
289c00000.dcp: DPTXPort: SET_LINK_RATE 0x14      <- ultrawide, HBR2
28cc00000.dcp: DP tunnel: capping max link rate at HBR2 (t600x shared tunnel clock)
28cc00000.dcp: DPTXPort: SET_LINK_RATE 0xa       <- Dell, HBR
28cc00000.dcp: AppleDCPDPTXController::handleInterrupt(): FIFO error interrupt COMMON_INT_STA_3=0x00000020   (x14)

That is the shared-clock failure the PR describes. The Dell's stream was torn down. The Dell also only offered modes up to 2560x1440@60 on the second tunnel, which is consistent with that tunnel getting too little bandwidth for HBR2 (the #154 issue). Your two-4K60 test probably didn't hit this because both sinks needed HBR2 anyway.

#373 + #154: both tunnels get an even bandwidth share, both train at HBR2, and there are no FIFO errors:

0:5 <-> 1:13 (DP): bandwidth reduced to 5400 Mb/s x4 = 17280 Mb/s
0:6 <-> 1:14 (DP): bandwidth reduced to 5400 Mb/s x4 = 17280 Mb/s
289c00000.dcp: DPTXPort: SET_LINK_RATE 0x14
28cc00000.dcp: DPTXPort: SET_LINK_RATE 0x14

The Dell now runs at 2560x1440@154.85 and the ultrawide at 5120x1440@119.97, both on the dock with 0 FIFO errors after link-up. firmware reports link fault 23 on target 0:2 still appears about 11 times on dcpext0 during initial bring-up, then stops.

Suggestion: on t600x the cap might need to pin both tunnels to the same rate, not only cap them, or depend on #154. Otherwise a lower-bandwidth sink can still train below HBR2. One caveat: I also had a Hyprland rule asking for the Dell's 155Hz mode, and I didn't test whether a 60Hz mode with #154 would still train at HBR.

Tester note: on Omarchy installs that boot a UKI through limine, appledrm is loaded from the UKI's embedded initramfs. So a module in updates/ only takes effect after limine-mkinitcpio; mkinitcpio -P alone is not enough.

I haven't tested boot with the dock attached or DPMS wake on this setup yet.

@kloploid

kloploid commented Nov 9, 2026 •

Copy link
Copy Markdown
Author

@JarenKempton thanks for testing this. I've now tried #373 + #154 together on my setup, as you suggested.

Setup: MacBook Pro 16" M1 Pro (j316s, t6000), Dell WD19TB, two 1920x1080@60 monitors on the dock (Lenovo T24i, Acer VG240Y). Two kernels built from the same tree:

Both kernels booted with the dock and both monitors already connected, then I compared kernel log snapshots.

Result: #154 works with #373 on this setup, and nothing regresses.

New with #154 (apple_dpin_share_bandwidth=Y):

thunderbolt-apple-nhi b01f00000.nhi: 0:5 <-> 1:11 (DP): sharing the path with 2 DP OUT adapters, offering 18000 Mb/s

The same on both kernels:

  • dpin0 → dispext0 (289c00000) and dpin1 → dispext1 (28cc00000). Both externals run at 1920x1080@60, and every modeset finishes with link: 1.
  • 0 FIFO errors and 0 link faults on both builds.
  • Hotplug, modeset and error counts are identical, including 4x unexpected lane count and 5x pmu ram read error, which were already there with t600x: drive both displays of a dual-stream Thunderbolt dock (j314/j316) #373 alone.

Caveats:

  • Two 1080p60 sinks use very little bandwidth, so this setup doesn't hit the mismatched-rate failure you saw with the TS4. Both builds work, and thunderbolt: share an Apple host link between DP tunnels that come up together #154 shows up only as the sharing message above. This only shows that the two PRs work together without breaking anything. It doesn't add evidence for the rate-pinning question.
  • I don't get the DP tunnel: capping max link rate at HBR2 or DPTXPort: SET_LINK_RATE lines in journalctl -k on either build (t600x_tunnel_max_hbr2=Y), so I can't confirm which rate each sink trained at. If those need dyndbg or a DCP log level, let me know and I'll rerun.
  • I only see one sharing the path line, for 0:5 <-> 1:11. I don't see a second one for the other tunnel.

This covers the boot-with-dock-attached case you hadn't tested yet: it works with both builds.


Update: dock replug and s2idle sleep on the same build

Dock replug: OK. Both screens came back without me doing anything. The first re-enumeration failed on the PCIe side (0:3 <-> 1:8 (PCI): activation failed, then PCIe tunnel activation failed, aborting, and the ACIO firmware restarted). About 1.5 s later the dock came up cleanly, #154 printed sharing the path with 2 DP OUT adapters, offering 18000 Mb/s again, and both DCPs got link: 1 with no FIFO errors.

Wake from s2idle: dpin1 fails at first, then recovers by itself after HPD cycles.

dpin0 (289c00000) came back straight away (link: 1), with one FIFO error at resume and one more about 5 s later that triggered a retrain:

289c00000.dcp: AppleDCPDPTXController::handleInterrupt(): FIFO error interrupt COMMON_INT_STA_3=0x00000010

dpin1 (28cc00000) failed six modesets in a row over about 7 s. This is the same pipe-not-enabled failure @Dazzzz reported, but without the startLink() failed with 0xe00002e3 line:

28cc00000.dcp: failed to parse modes
28cc00000.dcp: set_digital_out_mode(color:71 timing:64) "1920x1080": 60 148500 ...
28cc00000.dcp: nifiedPipeline.cpp:7739: setmode failed
28cc00000.dcp: nifiedPipeline.cpp:7758: ERROR: Modeset done, but pipe not enabled: fSoftPowerState=1, fDisplayPowerState=0, fHardPowerState=1
28cc00000.dcp: nifiedPipeline.cpp:7465: set_digital_out_mode returned 80000104
28cc00000.dcp: swap not completed, retraining the display
  (repeated 6x, 437.3 s - 444.3 s)

After that, the sink dropped HPD (display HPD removed, dcp_dptx_disconnect). The DP paths were torn down, but the TB link stayed up. 3.3 s later the tunnel was set up again and the modeset succeeded with a different colour mode (color:86 timing:79, previously color:71 timing:64), giving link: 1. Two more HPD remove/re-add cycles followed about 4 s apart, each ending in link: 1, and then it stayed stable. I didn't touch the monitor or cables at any point, so those HPD cycles were spontaneous. The Acer is showing a picture afterwards, so this isn't the black-screen case. The whole recovery took about 30 s from wake.

Totals for the boot: 13 FIFO errors (0 before sleep), 56 unexpected lane count (4 at boot), and 6 swap not completed. There were no link faults.

So on my setup, wake has the same dpin1 problem Dazzzz saw, but it heals on its own here. That might be because 1080p60 leaves more bandwidth headroom than 4K60. Full kernel logs are available if useful.

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.

4 participants