Skip to content

drm: apple: audio: ask for audio elements like macOS, and balance runtime PM around the DMA stop - #276

Open
nekolyanich wants to merge 3 commits into
aurora-silicon:aurora-wipfrom
nekolyanich:pr/dcp-audio-elements-pm
Open

nekolyanich wants to merge 3 commits into
aurora-silicon:aurora-wipfrom
nekolyanich:pr/dcp-audio-elements-pm

Conversation

@nekolyanich

Copy link
Copy Markdown

Three fixes to the DCP audio driver, found while bringing up DP and HDMI audio on an M2 Pro (j416s) with a Samsung TV on a USB-C to HDMI adapter and a Dell monitor over Thunderbolt.

Commits:

  1. drm: apple: audio: ask for up to 256 KiB of audio elements, as macOS: when the audio elements don't fit the buffer of the get_elements call, DCP returns them cut off at the buffer's end and reports a used size of 0. The TV's elements are 17312 bytes, so with the 16 KiB we asked for, everything past 16 KiB was lost; the stereo formats happen to sit in the first part, so the TV still got a usable card. macOS 13 asks for up to 256 KiB (in a hypervisor trace of macOS with the same TV: max_size 0x40000 in the request, used_size 0x43a0 in the reply). The elements buffer and the call buffers, including the one in afk_service_call_timeout(), are now allocated with kvzalloc().
  2. drm: apple: audio: balance the runtime PM reference of a failed START: trigger START returns -ENXIO before it takes the runtime PM reference when no sink is connected, and ALSA undoes a failed START with a STOP, which put the reference anyway: "Runtime PM usage count underflow!". The stream now remembers whether it holds the reference.
  3. drm: apple: audio: drop the runtime PM reference only after the DMA stopped: trigger STOP put the reference right after starting the asynchronous DMA terminate. Without autosuspend the DPA's power domain (ps_dpa1/ps_dpa2) then went down while SIO could still run DMA towards it, SIO crashed with "Shim DMA unavailable", the TERMINATE timed out, and DP audio was gone on all outputs until a reboot. The reference is now put in sync_stop, after snd_dmaengine_pcm_sync_stop() waited for the terminate; it uses the same hook as ASoC/dmaengine: wait for stopped capture streams (t8140-aop-audio sync_stop, apple-admac synchronize) #259 for the AOP audio. ALSA calls sync_stop at the next hw_params, prepare or hw_free, at close, at system suspend and at disconnect, so after a drain or an xrun the DPA now stays powered until then.

Testing: MacBook Pro 16" M2 Pro (j416s), DCP firmware 13.5. Samsung TV on a USB-C to HDMI adapter (DP alt mode, dcpext0) and a Dell U2725QE over Thunderbolt (DP IN tunnel, dcpext1). Each kernel is aurora-wip 3b2bd35a plus the commits shown, run under the m1n1 hypervisor. Two things differ from your setup:

  • a bench-only DT change that leaves atc0 out of the DCP routes, because m1n1 keeps that port for the hypervisor's USB link; without it the kernel waits forever in deferred_probe_initcall;
  • our bench config: no Rust (our bindgen and rustc fail the bindings layout checks on this tree), with RTKit, the PCIe controller and the USB4 NHI built in.

An initramfs plays 6 s of silence (S24_LE, 2 ch, 48 kHz) on each DCP card and logs the PCM state every second.

aurora-wip + 1 + 1, 2 + 1, 2, 3
TV elements used_size 0 (1 run) 17312 bytes (2 runs) 17312 bytes (2 runs) 17312 bytes (3 runs)
Dell plays 6 s 3 of 3 2 of 2 2 of 2 3 of 3
START on a card without a sink: usage count underflow 3 times (1 run) 3 times (1 run) 0 (1 run) 0 (1 run)

The SIO crash that commit 3 fixes did not show under the hypervisor on any of these kernels. It showed natively on our own tree (Asahi fairydust with the USB4 series backported): 4 of 4 boots before an earlier version of this change, none in 11 after. The code it changes is the same in aurora-wip.

Not fixed here: the TV goes dark after DCP's own replug. On every run where the TV started (15 of 15), DCP refuses the first audio start on this TV ("Restarting interfaces with reason - Capabilities changed") and replugs the display itself; macOS gets one such refusal on this TV too and carries on after the replug. On connected:0, fbdev turns the CRTC off, and the Type-C branch of dcp_poweroff() deasserts the HPD and calls dcp_dptx_disconnect() (DPTX DEACTIVATE). DCP's replug then has nothing to come back on, and the TV stays dark. Stack, from a kernel with a debug dump_stack() there:

dcp_dptx_disconnect.isra.0+0x64/0xc0
dcp_poweroff+0x17c/0x1b8
apple_crtc_atomic_disable+0xac/0xc0
drm_atomic_helper_commit_crtc_disable+0x114/0x1f8
...
drm_kms_helper_connector_hotplug_event+0x28/0x40
dcp_hotplug+0x3c/0x2c0

On our tree dcp_poweroff() leaves the DPTX port alone, and DCP brings the TV back 2.2 s later. I'm not sure what the right change is with your Type-C route logic, so I'm leaving it out of this PR. #275 (relinking a Type-C sink that DCP dropped while it still asserts HPD) may well cover this case; I haven't tried it yet. So far I haven't heard sound from the TV on aurora-wip, only seen its elements and the Dell's stream.

Not tested: natively on aurora-wip, other Macs, other sinks, the DCP 12.3 firmware command table.

🤖 Generated with Claude Code

nekolyanich added 3 commits October 11, 2026 19:11
When the audio elements do not fit the buffer of the get_elements
call, DCP returns them cut off at the buffer's end and reports a used
size of 0. A Samsung TV behind a USB-C to HDMI adapter has 17312 bytes
of elements, so with the 16 KiB we asked for, everything past 16 KiB
was lost. macOS 13 asks for up to 256 KiB (max_size 0x40000 in the
request, 0x43a0 used in the reply for this TV): do the same.

Allocate the elements buffer and the call buffers with kvzalloc(),
including the one in afk_service_call_timeout(), which every AFK
service call goes through; it is only copied into the DMA buffers.

Fixes: cb24343 ("gpu: drm: apple: Add DCP audio driver")
Assisted-by: Claude:claude-opus-5-5 checkpatch
Signed-off-by: nekolyanich <me@nekolyanich.com>
Trigger START returns -ENXIO before it takes the runtime PM reference
when no sink is connected. ALSA undoes a failed START with a STOP,
which put the reference all the same, so the usage count underflowed
("Runtime PM usage count underflow!", three times in one run on a
card whose display was off).

Remember whether the stream holds the reference, take it once in
START and put it in STOP only when it is held.

Fixes: f25cb50 ("drm: apple: audio: Implement runtime PM support")
Assisted-by: Claude:claude-opus-5-5 checkpatch
Signed-off-by: nekolyanich <me@nekolyanich.com>
…topped

Trigger STOP put the runtime PM reference right after starting the
asynchronous DMA terminate. Without autosuspend that suspends the
device at once and powers its domain (ps_dpa1/ps_dpa2) down while SIO
may still run DMA towards the DPA. SIO then crashed with "Shim DMA
unavailable" and the TERMINATE timed out after it, taking DP audio on
all outputs down until a reboot. On an M2 Pro with two DP sinks an
earlier version of this change on a downstream tree took it from 4 of
4 boots to none in 11.

Put the reference in sync_stop, after snd_dmaengine_pcm_sync_stop()
waited for the terminate. ALSA calls sync_stop at the next hw_params,
prepare or hw_free, at close, at system suspend and at disconnect, not
right after the STOP, so after a drain, an xrun or a stop with the
stream left open the DPA stays powered until then. Waiting inside
trigger would also work (the PCM is nonatomic), but sync_stop is the
hook ALSA has for it, as in the AOP audio driver.

sync_stop runs outside the stream lock: test and clear pm_held under
it. The check in START now matters, since a START can follow a failed
one before sync_stop ran.

Fixes: f25cb50 ("drm: apple: audio: Implement runtime PM support")
Assisted-by: Claude:claude-opus-5-5 checkpatch
Signed-off-by: nekolyanich <me@nekolyanich.com>
@sithglan

Copy link
Copy Markdown

@nekolyanich thanks for this. I tested it natively on a Mac mini M1 (j274), and it works.

Setup: aurora-wip 803274a + this PR + #275 (merges cleanly; #275 is needed for the USB-C monitor on this machine), built with Rust, appledrm.hdmi_audio=1. Two EIZO EV2780: one on the HDMI port (dcp, 238330000.audio-controller) and one on USB-C via DP alt mode (dcpext, 238334000.audio-controller).

  • Both cards register, the jack reports connected, PipeWire opens both.
  • speaker-test (S16_LE, 2 ch, 48 kHz) is audible on both monitors; hw_ptr advances at exactly 48 kHz.
  • After repeated short start/stop runs both audio controllers go back to runtime suspend. No "Runtime PM usage count underflow", no "Shim DMA unavailable", no TERMINATE timeout, no warnings in the boot log.
  • Not tested: START on a card without a sink (both ports had a monitor), and your TV / DCP replug case.

One thing you might run into: with PipeWire's default settings, mpv stayed silent on this PR alone. PipeWire asked for 128 periods, SIO has only 64 descriptor slots (one per period), so START failed with -ENOMEM and PipeWire retried it about 100 times a second. That's an old limit in dcp_pcm_open(), not something this PR introduced. I opened #277, which caps the PCM at 16 periods; with #276 + #275 + #277, mpv through PipeWire is audible on both monitors. If you test DP/HDMI audio with PipeWire rather than with a fixed period count, you may need #277 too.

Tested-by: Thomas Glanzmann thomas@glanzmann.de

@nekolyanich

Copy link
Copy Markdown
Author

Thanks for testing on the M1, and for #277. We hit the same SIO limit and carry the same cap (16 periods) on our downstream tree, so +1 for #277.

Some other things we found on an M2 Pro (j416s, 13.5 firmware) with a Samsung TV on a USB-C to HDMI adapter and a Dell over Thunderbolt. Code will follow once each is verified:

  • AFK slots of the AV endpoint. DCP announces DCPAVSimpleVideoInterface and DCPAVAudioInterface again on every display connect, including the replugs it does itself. Their ops are not reusable, so each connect takes two of the 16 slots for good. After eight connects in one boot the next display gets no reusable service slots and no audio until a reboot. A fix that pins the services and marks them reusable, along the lines of 5837706, held over 28 generations in 2 slots on the bench. That PR is next.
  • Short AFK replies. DCP sometimes answers a command with 4 bytes. afk_recv_handle_reply() drops such replies, so the caller times out, and a command that had already timed out keeps its buffers and its slot. We saw this 29 times in 8 boots on 13.5 firmware. It goes into the same PR.
  • DCP refuses the first audio start on the TV. The prepare reply carries 0xe00002e2 with "Capabilities changed" in DCP's syslog, and DCP then drops and re-announces the sink. It hit 26 of our 34 first starts, against 1 of 3 under macOS 13 with the same TV. In the macOS traces it does not open the audio service at the announce. It sends a fixed set of getters once ((1,19), (0,15), (1,18), (0,14), (1,16), (0,20) with the format, (0,13)), opens only on DCP's start request inside the modeset, and prepares 6 ch with speaker allocation 0x0b. We are testing that order natively now.
  • Plugs lost under the hypervisor. On aurora-wip with dac5378, 4–6 of 16 replugs bring up only the Type-C partner, without the adapter's USB or DP. That is under the m1n1 hypervisor only; natively with dac5378 we saw 0 of 8. Not understood yet.
  • WirePlumber profiles. WirePlumber can lose the HiFi profile when its probe lands during one of DCP's own replugs (seen in 3 of 3 boots). This is on the userspace side, but anyone testing on such a sink may run into it.

@RyanTheTide

RyanTheTide commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

All three commits are ported in review/pr276-integration-t3094-v3, head fe5d0c4, preserving original authorship and sign-offs. The port keeps the native T6030 path and sinkless behavior. Generic sync_stop cancels the sinkless work and drains DMA before clearing/releasing the held PM reference under the stream lock. The larger elements buffer keeps its matching allocation/free pairs. The fresh extracted PM/DMA positive control and three runtime mutants pass.

On integration/aurora at 421e314 with shared #438 and #456, DCP is PASS (63/63). KUnit has 905 passes and 1 policy skips. Universal WERROR is PASS; Fedora-base WERROR is PASS.

Software gates pass; hardware and shared #438/#456 review pending. The operator reports #456 passed all 63 host groups with stock make 3.81 and GNU Make; this worker did not rerun native macOS on the assembled base. Hardware still needed: M1 Pro/M2 stop, unplug, sinkless and runtime PM; Neo audio regression.

@nekolyanich

Copy link
Copy Markdown
Author

Tested on J416s (M2 Pro), review/pr276-integration-t3094-v3 (fe5d0c4) plus the bench DT change, under the
m1n1 hypervisor, appledrm.hdmi_audio=1.

  • KUnit: the 16 apple-dcp* suites and apple-av-v14-audio 9/9 pass.
  • Sinkless: 12/12 open/play/close streams on the void clock (2 boots); both audio devices runtime-suspended at
    every read, playing included. 0 usage count underflow.
  • Stop with a sink: 4/4 streams before suspend (Dell and TV), 30/30 on the TV's card across 4 TV replugs; 0
    Shim DMA unavailable, 0 underflow, 0 kernel warnings.
  • After resume: the Dell came back 1/3 (-16, no capacity retry on this branch, thunderbolt: apple: let T6020 DP IN tunnels wait for a display pipeline #426 adds it). Not this PR.
  • TV replugs: 2/4 reached the display; this branch has no usb: typec: tipd: put an empty CD321x port to sleep, as macOS #425, so that is the known base replug loss.
  • Unplug with DMA running (TV alone, 30 s streams, 4 replugs, 2026-10-31): 4/4 unplugs during a running stream (the audio service stopped at 3 of them),
    the next stream after each plug started; 0 Shim DMA unavailable, 0 underflow,
    0 TERMINATE timeouts, 0 kernel warnings.
  • Not run: audio after resume; natively.

Runs and this summary were prepared with Claude (Opus 5.5); counts checked against the logs.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants