Skip to content

drm: apple: Type-C relink and DPMS wake fixes, DCP audio, AVD buffers, AV slot reuse (port to aurora-wip-rebase) - #328

Merged
DjDeveloperr merged 9 commits into
aurora-silicon:aurora-wip-rebasefrom
sithglan:wip-rebase-dcp-fixes
Oct 16, 2026
Merged

DjDeveloperr merged 9 commits into
aurora-silicon:aurora-wip-rebasefrom
sithglan:wip-rebase-dcp-fixes

Conversation

@sithglan

Copy link
Copy Markdown

This ports my open aurora-wip PRs onto aurora-wip-rebase (7f83518):

The commit messages are unchanged apart from a short note in the relink commit. See the original PRs for the full background.

Why the relink is needed on aurora-wip-rebase

On aurora-wip-rebase alone, the USB-C display (EIZO EV2780, dcpext on the Mac mini j274) came up at boot and went dark about 5 s after login. PipeWire probed the DP audio PCMs, and then DCP restarted its AV interfaces and dropped a sink that still asserted HPD. There was no Type-C unplug and no IRQ_HPD, so nothing recovered it:

08:14:28 DCPAVService.cpp:954: [AFK]Restaring interfaces with reason - Capabilities changed..
08:14:33 VideoInterfaceIOAV::unplug_gated(IOAVVideoInterface *): display HPD removed
08:14:33 dcp_hotplug() connected:0 valid_mode:0 nr_modes:18

That is the case the first commit handles ("DCP dropped a sink that asserts HPD").

Commits

  1. drm: apple: relink a Type-C sink that lost its link to a hub's USB3: ported into the split dcp.c / dcp-fabric.c.
    • It also leaves out H17P and native external pipes (external_native), which recover through their own lifecycle.
    • When a sink raises IRQ_HPD, the relink goes first. Once its budget is spent, upstream's dcp_dptx_recover_irq() (disconnected connector) or dcp_retrain_oob() (connected) run as before.
    • The replay still uses dcp_dptx_disconnect_oob() / dcp_dptx_connect_oob(). Each of them bumps typec_generation exactly once, so the check for a real replug during the replay still holds.
  2. usb: typec: tipd: report a coalesced sink HPD pulse as IRQ_HPD: unchanged.
  3. drm: apple: do not report a bad link for the HPD a Type-C wake awaits: git applied the dcp.c hunk to dcp_poweron_h17p(). I moved it to dcp_poweron(), which is the path the 12.x/13.x firmware uses.
  4. drm: apple: audio: play into the void without a sink: applied cleanly next to the new dcp_pcm_hw_unplugged and open_unplugged code. On this base, opening a DCP PCM without a sink is only allowed on H17P. Elsewhere open fails with -ENXIO, so the void path covers a sink that goes away after open, and H17P's unplugged open.
  5. drm: apple: reset the Type-C relink budget on every DPMS wake: still needed. The DPMS off of a 12.x/13.x Type-C CRTC (dcp_poweroff()) does not bump typec_generation, so it does not reset the budget.
  6. drm: apple: do not report a bad link for a powered-off Type-C CRTC: the same guard at the non-H17P link-status site in dcp_hotplug().
  7. drm: apple: audio: limit the number of periods to what SIO can serve (drm: apple: audio: limit the number of periods to what SIO can serve #277): also caps the new dcp_pcm_hw_unplugged.
  8. media: apple: avd: allow more than 32 capture buffers per context (media: apple: avd: allow more than 32 capture buffers per context #281).
  9. drm/apple: av: recycle AV endpoint service slots (drm/apple: av: recycle AV endpoint service slots #322): still needed. aurora-wip-rebase has generic AFK slot reuse, but the AV services in av.c still don't opt in with .reusable.

I dropped one commit of #275, "drm: apple: update connector dimensions without a hotplug". aurora-wip-rebase already applies DisplayAttributes dimensions through its own dimensions_wq without a hotplug.

Testing

What Result
Build, W=1, apple drm + tipd + avd no new warnings
KUnit (arm64/qemu, DRM_APPLE_KUNIT_TEST, DRM_APPLE_DISPLAY_KUNIT_TEST, DRM_APPLE_FABRIC_KUNIT_TEST) 210/212 passed, 1 skipped. The one failure, apple-display-parser.parser_route_programming_guard (dcp_crtc_needs_route_start() with a tunnel route, parser-test.c:823), fails the same way on 7f83518 alone (209/211). The new afk_service_close_pin_test passes.
DCP host selftests (tools/testing/selftests/drivers/gpu/apple-dcp/run.sh) not run. On 7f83518 alone the harness stops in quiesce.py (a typec_mux_* marker is not found in mux.c).
Mac mini (j274), two Fujitsu E24T-7 Pro (USB-C + HDMI), swaymsg "output * power off/on" 8/8 wakes came up once, no kernel warnings
Mac mini, Fujitsu (no audio), aplay to both DCP cards open returns -ENXIO (upstream behaviour on non-H17P). PipeWire: 0 errors, 0% CPU
Mac mini, 4K H.264 with mpv --hwdec=v4l2request, vd-queue 96 (needs more than 32 capture buffers) plays. ffmpeg -hwaccel v4l2request decodes 15 s (900 frames) without AVD errors

Not tested yet on this base: the EIZO EV2780 on USB-C, which triggers the relink above, and a cable replug. I'll add the results here.

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.

H17P and native external pipes keep their own recovery. Once the relink
budget is spent, a sink request falls back to dcp_dptx_recover_irq() or
dcp_retrain_oob() as before.

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>
SIO has 64 descriptor slots, shared by all its channels, and its
cyclic prep takes one slot per period. The PCM allowed any number of
periods, so PipeWire's default of 256-frame periods in a 32768-frame
buffer asked for 128 of them. sio_prep_dma_cyclic() then ran out of
slots, snd_dmaengine_pcm_trigger() returned -ENOMEM, ALSA undid the
START with a STOP (a stop_link round trip to DCP), and PipeWire
retried about 100 times a second with no sound.

Up to three DP audio outputs share one SIO on t600x and t602x, so
allow at most 16 periods per stream, 48 slots for all three.

Fixes: cb24343 ("gpu: drm: apple: Add DCP audio driver")
Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The capture queue never sets max_num_buffers, so vb2 limits every
context to VB2_MAX_FRAME (32) buffers. Nothing in the driver needs that
limit: reference frames are looked up by timestamp (vb2_find_buffer())
and handed to the hardware by DMA address, never by buffer index.

32 buffers leave too little room for userspace to decode ahead. With a
16-frame DPB, four frame threads and the frames held for display, mpv's
decoder queue (--vd-queue) gets only a few frames before
VIDIOC_CREATE_BUFS fails with ENOBUFS, and the FFmpeg V4L2 request
hwaccel then falls back to software decoding. Decoding ahead is needed
because the t8103 AVD decodes 4K59.94 H.264 at only ~41 fps during a
125-156 Mbit/s burst, so playback drops frames unless the decoder has
built up a lead of 60-90 frames beforehand.

Add a max_capture_buffers module parameter, default 128. Buffers are
still allocated on demand by userspace, so this only raises the limit.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The AV endpoint (0x29) announces a fresh DCPAVSimpleVideoInterface and
DCPAVAudioInterface on new channels for every DPTX connection, so every
display replug or DPMS wake that drops HPD uses two more slots. Since
"drm/apple: pin DP service owners before recycling retired AFK slots" a
torn-down slot is only reclaimed if its ops opt in with .reusable, which
the AV services never did. After eight connections the 16-slot table is
full, every later announcement fails with

  AFK[ep:29]: no reusable service slots

and the audio service of that port is not re-registered until reboot.

Convert the AV owners and opt in to reuse:

 - DCPAVSimpleVideoInterface keeps no pointer to its service.
 - asrv->srv now holds an owner pin taken in init and dropped in
   teardown. A teardown of an older generation no longer clears the
   pointer or unplugs the jack of its successor.
 - av_service_connect() and av_service_disconnect() call into the
   service after dropping srv_rwsem, so they pin it for the duration.
   Add afk_service_pin() for this; unlike afk_service_get() it also
   succeeds after teardown, so the close is still sent.

Add a KUnit case for a close pinned across teardown.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>

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

LGTM! Thanks

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