Skip to content

DCP/tipd: USB-C monitor recovery (lost DP link, DPMS wake loop) and DCP audio without a sink - #275

Closed
sithglan wants to merge 7 commits into
aurora-silicon:aurora-wipfrom
sithglan:dcp-typec-relink
Closed

sithglan wants to merge 7 commits into
aurora-silicon:aurora-wipfrom
sithglan:dcp-typec-relink

Conversation

@sithglan

@sithglan sithglan commented Oct 11, 2026 •

Copy link
Copy Markdown

Problem

A USB-C monitor with a built-in USB hub (EIZO EV2780, plain DP alt mode, pin assignment D) intermittently stays dark after a cold boot, a hot-plug or a macOS → Linux reboot. Seen on the M1 MacBook Air (j313) and the M1 Mac mini (j274).

DCP trains the DP link for the first modeset at about the time the monitor's hub brings its USB3 SuperSpeed link up. When the two overlap, the sink loses the link, but DCP still reports 2560x1440@59 Hz link: 1. Then either:

  • DCP gives up on its own exactly 2.008 s after asserting HPD (IOAVVideoInterface terminated, display HPD removed), or
  • DCP keeps driving the dead link without noticing, and the monitor pulses HPD ~2–4 s later to be detected again.

The CD321x still reports DP alt mode with HPD high the whole time, and nothing re-detects the sink.

With dwc3_apple blacklisted (no USB3 on the port), 5/5 boots came up. With it, 3/6 failed on the Air.

Changes

  1. drm: apple: relink a Type-C sink that lost its link to a hub's USB3. When DCP drops a sink that still asserts HPD, or the sink asks for attention (IRQ_HPD), replay an unplug and a replug 1 s later. The link is then retrained after the hub has settled, as a cable replug does.

    • A sink request within 4 s of a relink is the sink's late reaction to the loss already handled, and is ignored.
    • IRQ_HPD is not deduplicated as an unchanged route state.
    • At most three relinks per connection and per DPMS wake (see item 5); the replay does not reset that count.
    • Not for external H17P (J700), which recovers through its own lifecycle (dcp_external_hotplug()).
    • Pulsing only DCP's HPD with the connector kept connected (first attempt) raced with DCP's own re-detection and left the Mac mini dark.
  2. usb: typec: tipd: report a coalesced sink HPD pulse as IRQ_HPD. The EIZO's ~250 ms HPD pulse falls within the 500 ms debounce of cd321x_update_work(). The worker then saw the state it had already applied and never told the mux. On j313/j274 the out-of-band DRM hotplug path doesn't cover it either: the connector nodes have no displayport reference, so connector_fwnode is NULL.

  3. drm: apple: do not report a bad link for the HPD a Type-C wake awaits. Waking a DP alt mode display from DPMS off (swaymsg "output * power off" / "power on") power cycled it about every 750 ms until reboot, on the Mac mini with a Fujitsu E24T-7 Pro on USB-C (it keeps HPD asserted while blanked). dcp_poweron() reconnects DPTX and waits for IOMFB to report the sink before the commit's modeset. dcpep_cb_hotplug() took that HPD for a sink that asserted HPD only after a failed modeset, so dcp_hotplug() set link-status BAD. wlroots treats a bad link as a disconnect and recreates the output, which disables and re-enables the CRTC, and that wake does the same again. Each round also leaked AFK service slots (AFK[ep:29]: no reusable service slots) and the system became sluggish. The HPD dcp_poweron() waits for is no longer handled as a late one; an HPD after the wait is still handled as before.

  4. drm: apple: audio: play into the void without a sink. Found on the same Mac mini: the Fujitsu monitors have no audio (single-block EDID 1.3), so DCP never brings up an audio interface. Without a sink, opening, configuring and preparing the PCM succeed (userspace drops an output that fails its probe), but START failed with -ENXIO. PipeWire ignores that error and retries on every wakeup; any other error ends up in its recovery, which prepares and starts again. With mpv moved to the display output in pavucontrol it logged snd_pcm_start: No such device or address ~45 times a second per stream, and after ~80 min (spa.alsa: hw:0p: impossible timeout) ~130000 times a second: 4.1 million lines in one boot and a busy core. Now, without a sink (or once the stream's sink is gone), START succeeds and the stream runs on the clock without DMA or a DCP audio link, its samples dropped, as HDMI PCMs elsewhere do. A stream playing into the void is not stopped as disconnected; a sink attached later is used by the next stream opened. This adds a minimal sync_stop; on top of drm: apple: audio: ask for audio elements like macOS, and balance runtime PM around the DMA stop #276, which adds one too, it reduces to one cancel_delayed_work_sync() there.

  5. drm: apple: reset the Type-C relink budget on every DPMS wake. The EIZO EV2780 on the Mac mini loses the link after the first modeset of every DPMS wake, not only after a hot-plug, and item 1 recovers it each time. But the three relinks were counted per connection, i.e. until the cable is replugged, so all wakes shared them: the 4th wake after a replug was handled as an unplug and the display stayed dark (seen in daily use: relinks 1/3, 2/3, 3/3 on three wakes, then dark). The limit is there to stop relink loops within one link bring-up, so dcp_poweron() now resets it when the CRTC comes back from DPMS off. No relink can run while the CRTC is off.

  6. drm: apple: do not report a bad link for a powered-off Type-C CRTC. Since 57f9f36 ("drm/apple: fence EDID publication by service lifetime and unblock RX") the teardown of DCP's AV service schedules the connector hotplug worker. DCP tears that service down on every DPMS off, and dcp_hotplug() then reported the link of the powered-off CRTC as bad, which wlroots answers on wake by replacing the output. There is no link while the CRTC is off and dcp_poweron() retrains one on wake, so the link status is left alone then; the EDID is still invalidated.

  7. drm: apple: update connector dimensions without a hotplug. The same commit also moved the dimension update for DisplayAttributes to the hotplug worker. DCP sends DisplayAttributes on every DPTX connect, which a Type-C DPMS wake does before its modeset, so dcp_hotplug() found the CRTC being enabled without a valid mode, replayed its state (a second modeset) and reported a bad link: on the current aurora-wip the display did not come back from the first DPMS off, and every round of wlroots replacing the output added DPTX reconnects until the system crawled. The dimensions now get a work item of their own, which does what the RX worker did before, outside it. External H17P keeps the hotplug worker.

Rebased onto aurora-wip 27da3a5 (no conflicts). The DCP host selftests (tools/testing/selftests/drivers/gpu/apple-dcp) get stubs for the new calls in their harnesses. On 5f6c917 52/52 passed at every commit of this PR. On 27da3a5 45/52 pass at the head of this PR, and the same 7 (oslog-events, service-rebind, provider-admission, parser-policy, publication-hpd, typec-probe, probe-mux-pm-dispatch) already fail on the base alone: the harnesses lack stubs for dev_dbg() and cd321x_display_taken(), added upstream.

Testing

Machine Result
MacBook Air (j313) 8/8 cold boots came up, 6 needed a relink (first version of the relink)
Mac mini (j274), EIZO on the port that failed 8/8 cold boots came up with the final version; 2 hit an unreadable EDID (TimingElements without valid modes) first and recovered on the second relink
Mac mini, macOS → Linux reboot came up after one relink
Mac mini (j274), Fujitsu E24T-7 Pro on USB-C + one on HDMI, DPMS off/on via sway before item 3: endless power cycle on the first wake; with it: 4/4 wakes came up once
Mac mini (j274), two Fujitsu E24T-7 Pro (no audio), silence played to the DCP audio outputs before item 4: 26–42 snd_pcm_start errors/s per stream (~130000/s after ~80 min in daily use); with it: 0 errors on both outputs, aplay of 3 s takes 3.006 s, two streams for 30 s keep PipeWire at 1% CPU (tested on top of #276 + #277)
Mac mini (j274), EIZO EV2780 on HDMI and on USB-C, tone playing via PipeWire, cable unplugged and replugged (on top of #276 + #277 + #281) the running stream stops as DISCONNECTED; PipeWire logs one snd_pcm_drop: File descriptor in bad state and closes the device, no retry loop, PipeWire at ~0.1% CPU; HDMI playback runs again ~3 s after replug; USB-C sink comes back after replug, tone audible before unplug
Mac mini (j274), EIZO EV2780 on USB-C + one on HDMI, output DP-1 power off; sleep 20; power on; sleep 20 in a loop (on top of #276 + #277 + #281) before item 5: display stays dark on the 4th wake after a replug; with it: 55/55 wakes came up, each after one relink (1/3), no kernel warnings. Needs wlroots not to fall back to the legacy gamma ioctl on CRTCs without gamma support, else sway's power on commit fails before reaching the kernel (separate wlroots fix)
Mac mini (j274), EIZO EV2780 on USB-C + one on HDMI, rebased onto aurora-wip 27da3a5 together with #276, #277, #281 and the AV slot fix, swaymsg output DP-1 power off/on 17/17 wakes came up, no kernel warnings; DP audio on both EIZOs plays before and after
Mac mini (j274), EIZO EV2780 on USB-C + one on HDMI, rebased onto aurora-wip 5f6c917, the same loop before items 6 and 7: the display does not come back from the first DPMS off (wlroots replaces the output every ~1.4 s); with them: 30/30 wakes came up, each after one relink (1/3), no kernel warnings; a cable replug afterwards came up too

The earlier test kernels exported dwc3_event_buffers_cleanup() locally to build with CONFIG_USB_DWC3=y and CONFIG_USB_DWC3_APPLE=m; aurora-wip now does that itself (e87b71d).

Also checked on the Air: a USB-C → HDMI adapter and a dock still work.

@sithglan sithglan changed the title DCP/tipd: recover a USB-C monitor that loses its DP link to its own USB3 hub DCP/tipd: USB-C monitor recovery (lost DP link, DPMS wake loop) and DCP audio without a sink Oct 12, 2026
theoparis pushed a commit to tinted-software/linux that referenced this pull request Oct 13, 2026
On t6020 with the 13.5 firmware, DCP's DPTX port sends AP calls of group
1, commands 3 and 4, around link (re)configuration, audio prepare
included. The AFK v1 notify path dropped the group, so they reached the
group 0 handlers of the same numbers, SET/GET_DRIVE_SETTINGS: DCP
addresses from the payload were stored as drive settings. Behind a USB-C
to HDMI adapter DCP then refused the audio start ("Capabilities
changed") or never confirmed the stream.

Pass the group through a call_v2 op, with the signature of call_v2 on
aurora's J700 test branch (85d81b7, not in aurora-wip), and keep .call
for group 0 only. A group call to a service without call_v2 gets a
warning and no reply: AFK has no handler and does not guess one.

The DPTX port answers group 1 commands 3 and 4 the way macOS 13 does in
42 of 42 traced pairs (TV through the adapter, Dell over Thunderbolt):
an echo of the request with +0x00..0x03 cleared for (1, 3), and
+0x10..0x13 and +0x20 to the end cleared for (1, 4). The lane count sits
at +0x10 in (1, 3) and at +0x00 in (1, 4); macOS keeps it in all 42 and
clears the other slot. The J700 code reads (1, 3) the same way, with
per-lane levels from +0x20, which macOS does not clear there; it refuses
(1, 4), and its group 0 GET (0, 4) has the count at +0x00. Other groups
are echoed with a rate-limited warning.

Testing: MacBook Pro 16" M2 Pro (j416s), DCP firmware 13.5, a TV on a
USB-C to HDMI adapter and a Dell over Thunderbolt, 8 audio starts on
the TV per boot. Cold boots under the m1n1 hypervisor, aurora-wip with
PRs aurora-silicon#275-aurora-silicon#277, the AFK slots series and a bench DT change,
interleaved: with this commit the first start was accepted in 5 of 5
boots, with 0 refusals and 8 of 8 starts played; without it, 3 of 3
boots refused the first start, 2 refusals each, 4 of 8 played. The
Dell had no refusals in any boot, and no group warning was logged.
Natively, on a downstream tree with the same reply rule, the boot
connect and 3 of 8 TV replugs reached DCP (the rest failed earlier, in
the PHY): 0 refusals in 4 connects; before, 10 refusals in 5 connects.
Not tested: other SoCs, other firmware, AFK v2.

Assisted-by: Claude:claude-opus-5-5 checkpatch
Signed-off-by: nekolyanich <me@nekolyanich.com>
@sithglan
sithglan force-pushed the dcp-typec-relink branch 2 times, most recently from 7aeb413 to 3a6e391 Compare October 15, 2026 05:18
A USB-C monitor with a built-in USB hub (EIZO EV2780) intermittently
stays dark after a cold boot or a hot-plug on the M1 MacBook Air and
the M1 Mac mini. DCP trains the link for the first modeset at about the
time the monitor's hub brings its SuperSpeed link up. When the two
overlap, the sink loses the link, but DCP still reports the modeset as
linked:

  UnifiedPipeline::mode_set_gated(): 2560x1440@59 Hz link: 1

Then one of two things happens. DCP gives up on its own 2.008 s after
asserting HPD:

  IOMFB: IOAVVideoInterface terminated
  VideoInterfaceIOAV::unplug_gated(): display HPD removed

or it keeps driving the dead link without noticing, while the monitor
pulses HPD about 2-4 s later to be detected again. In both cases the
CD321x still reports DP alt mode with HPD high, and nothing re-detects
the sink. With dwc3 kept off the port, 5 out of 5 boots brought the
display up; with it, 3 out of 6 failed.

Replay an unplug and replug of the sink when DCP drops a sink that
still asserts HPD, or when the sink asks for attention (IRQ_HPD): the
link is then retrained after the hub has settled, as a cable replug
does. A request from the sink within 4 s after a relink is the sink's
late reaction to the loss the relink already handled and is ignored;
another relink would only interrupt the new link. A sink request must
not be dropped as an unchanged route state either. A connection gets at
most three relinks; the replay does not reset that count.

Pulsing only DCP's HPD instead, with the connector kept connected,
raced with DCP re-detecting the sink on its own and left the display
dark on the Mac mini.

With this, 8 out of 8 cold boots of the MacBook Air and 8 out of 8 of
the Mac mini (EIZO on the port that failed) brought the display up.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A DisplayPort sink that lost the link drops HPD and raises it again to
be detected anew. The EIZO EV2780 does so for about 250 ms, which falls
within the 500 ms debounce of cd321x_update_work(). The worker then
sees DP alt mode with HPD high, the state it already applied, and
cd321x_typec_update_mode() returns without telling the mux, so the
display driver never learns that the sink wants attention and the
display stays dark.

The out-of-band DRM hotplug event does not cover this either on the
M1 MacBook Air and Mac mini: their connector nodes have no
"displayport" reference, so connector_fwnode is NULL.

When HPD changed within the debounce window and is high before and
after, pass the state on with DP_STATUS_IRQ_HPD set, even though it
otherwise matches the applied one.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Waking a DP alt mode display from DPMS off with sway ("output * power
on") on the M1 Mac mini (Fujitsu E24T-7 Pro on USB-C, which keeps HPD
asserted while blanked) power cycles the display about every 750 ms
until reboot:

  dcp_hotplug() connected:1 valid_mode:0 nr_modes:1
  dcp_poweron() starting
  set_digital_out_mode finished
  dcp_poweroff() done
  ...

With the cable attached, dcp_poweron() reconnects DPTX and waits for
IOMFB to report the sink before the commit does its modeset. At that
point no mode is valid yet, so dcpep_cb_hotplug() takes the HPD for a
sink that asserted HPD only after a failed modeset and sends
DCP_HOTPLUG_NOTIFY. dcp_hotplug() then sets the connector's link-status
to BAD. wlroots treats a bad link as a disconnect and recreates the
output, which disables the CRTC and enables it again. That wake does
the same, so the loop never ends. Each round also leaks AFK service
slots ("AFK[ep:29]: no reusable service slots") and the system becomes
sluggish.

Do not take the HPD that dcp_poweron() waits for as a late one: the
modeset of the same commit follows. A sink that asserts HPD only after
dcp_poweron() stopped waiting is still handled as before.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
On the M1 Mac mini with two Fujitsu E24T-7 Pro monitors, which have
no audio (single-block EDID 1.3), PipeWire logs

  spa.alsa: hw:0p: snd_pcm_start: No such device or address

about 130000 times a second, for hours, as soon as something plays to
a DCP audio output: 4.1 million log lines in one boot, with journald
dropping most of them, and a core kept busy.

Without a sink, opening, configuring and preparing the PCM succeed, as
userspace drops an output that fails its probe for good, but START
fails with -ENXIO. That does not stop userspace from trying: PipeWire
starts playback after its first write and ignores the error, and a
stream that never starts never makes it wait, so the next wakeup is
immediate and tries again. Any other error ends up in its recovery,
which prepares and starts again.

Play into the void instead, as HDMI PCMs on other platforms do: without
a sink, or once the sink the stream was opened for is gone, START
succeeds and the stream runs on the clock, without DMA and without a
DCP audio link. The position follows the time since START and a work
item reports the periods. The samples are dropped. A stream playing
into the void is not stopped as disconnected when a sink goes away; a
sink attached later is used by the next stream opened.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A USB-C monitor with a built-in hub drops the DP link right after the
first modeset of every DPMS wake, which dcp_typec_relink() recovers by
replaying an unplug and replug. Its retry counter is only reset when
the Type-C generation changes, i.e. on a real cable replug, so all wakes
of one connection share DPTX_RELINK_RETRIES tries: after three wakes the
fourth drop is handled as an unplug and the display stays dark until the
cable is replugged.

The limit exists to stop relink loops within one link bring-up. Reset it
in dcp_poweron() when the CRTC comes back from DPMS off. No relink can
run while the CRTC is off, dcp_typec_relink_start() refuses then.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Since commit 57f9f36 ("drm/apple: fence EDID publication by service
lifetime and unblock RX"), the teardown of DCP's AV service schedules the
connector hotplug worker to invalidate the EDID. DCP tears that service
down whenever a Type-C CRTC is powered off with the display attached
(DPMS off). dcp_hotplug() then finds the connector connected without a
valid mode and reports the link as bad. wlroots treats a bad link as a
disconnect and replaces the output on the next wake, which powers the
CRTC off and on again, and that power off does the same: the display
never comes back from the first DPMS off (Mac mini, EIZO EV2780 on USB-C).

There is no link while the CRTC is off, and dcp_poweron() retrains one on
wake. Leave the link status and the CRTC alone then; the EDID is still
invalidated.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Commit 57f9f36 ("drm/apple: fence EDID publication by service
lifetime and unblock RX") moved the connector dimension update for
DisplayAttributes from the RTKit RX worker to the connector hotplug
worker. Besides the dimensions, dcp_hotplug() reports the link as bad
and retrains an active CRTC without a valid mode.

DCP sends DisplayAttributes on every DPTX connect, and a Type-C DPMS
wake connects DPTX in dcp_poweron(), before the modeset of its commit.
dcp_hotplug() then finds the CRTC being enabled without a valid mode,
replays its state (a second modeset) and reports a bad link. wlroots
treats that as a disconnect and replaces the output, which powers the
CRTC off and on again, and that wake does the same: the display never
comes back from the first DPMS off, and every round leaks AFK service
slots until the system crawls (Mac mini, EIZO EV2780 on USB-C).

Defer the dimension update to a work item of its own, which does what
the RX worker did before, outside it. External H17P keeps using the
hotplug worker, as its display publication was written for that.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
DjDeveloperr pushed a commit that referenced this pull request Oct 16, 2026
On t6020 with the 13.5 firmware, DCP's DPTX port sends AP calls of group
1, commands 3 and 4, around link (re)configuration, audio prepare
included. The AFK v1 notify path dropped the group, so they reached the
group 0 handlers of the same numbers, SET/GET_DRIVE_SETTINGS: DCP
addresses from the payload were stored as drive settings. Behind a USB-C
to HDMI adapter DCP then refused the audio start ("Capabilities
changed") or never confirmed the stream.

Pass the group through a call_v2 op, with the signature of call_v2 on
aurora's J700 test branch (85d81b7, not in aurora-wip), and keep .call
for group 0 only. A group call to a service without call_v2 gets a
warning and no reply: AFK has no handler and does not guess one.

The DPTX port answers group 1 commands 3 and 4 the way macOS 13 does in
42 of 42 traced pairs (TV through the adapter, Dell over Thunderbolt):
an echo of the request with +0x00..0x03 cleared for (1, 3), and
+0x10..0x13 and +0x20 to the end cleared for (1, 4). The lane count sits
at +0x10 in (1, 3) and at +0x00 in (1, 4); macOS keeps it in all 42 and
clears the other slot. The J700 code reads (1, 3) the same way, with
per-lane levels from +0x20, which macOS does not clear there; it refuses
(1, 4), and its group 0 GET (0, 4) has the count at +0x00. Other groups
are echoed with a rate-limited warning.

Testing: MacBook Pro 16" M2 Pro (j416s), DCP firmware 13.5, a TV on a
USB-C to HDMI adapter and a Dell over Thunderbolt, 8 audio starts on
the TV per boot. Cold boots under the m1n1 hypervisor, aurora-wip with
PRs #275-#277, the AFK slots series and a bench DT change,
interleaved: with this commit the first start was accepted in 5 of 5
boots, with 0 refusals and 8 of 8 starts played; without it, 3 of 3
boots refused the first start, 2 refusals each, 4 of 8 played. The
Dell had no refusals in any boot, and no group warning was logged.
Natively, on a downstream tree with the same reply rule, the boot
connect and 3 of 8 TV replugs reached DCP (the rest failed earlier, in
the PHY): 0 refusals in 4 connects; before, 10 refusals in 5 connects.
Not tested: other SoCs, other firmware, AFK v2.

Assisted-by: Claude:claude-opus-5-5 checkpatch
Signed-off-by: nekolyanich <me@nekolyanich.com>
@RyanTheTide

Copy link
Copy Markdown
Contributor

Closing as superseded: this work was ported by #328 (merged 2026-10-16) and carried into the maintained 7.3 integration/aurora line. Thanks for the original change; nothing here is lost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants