No description
Find a file
fromport f29fe93359 r4: add fixes #15-#22 (SIO/DCP crash paths, FW restarts, display-off audio) and contrib helpers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:07:15 +01:00
contrib r4: add fixes #15-#22 (SIO/DCP crash paths, FW restarts, display-off audio) and contrib helpers 2026-10-16 09:07:15 +01:00
count-cycles HDMI video + audio fix set for asahi-7.1.13-3 2026-09-26 12:42:12 +01:00
hdmi-fixes-asahi-7.1.13-3-r2.patch r2: add fixes #12/#13 (display wake after power saving) 2026-09-28 19:24:06 +01:00
hdmi-fixes-asahi-7.1.13-3-r3.patch r3: add fix #14 (no 30 s stall when the TV wakes from standby) 2026-09-29 07:55:44 +00:00
hdmi-fixes-asahi-7.1.13-3-r4.patch r4: add fixes #15-#22 (SIO/DCP crash paths, FW restarts, display-off audio) and contrib helpers 2026-10-16 09:07:15 +01:00
hdmi-fixes-asahi-7.1.13-3.patch HDMI video + audio fix set for asahi-7.1.13-3 2026-09-26 12:42:12 +01:00
README.md r4: add fixes #15-#22 (SIO/DCP crash paths, FW restarts, display-off audio) and contrib helpers 2026-10-16 09:07:15 +01:00

HDMI audio & display-wake fixes for Asahi Linux (Apple DCP / AFK)

Patches for the Apple display coprocessor (DCP) driver in the Asahi Linux kernel (drivers/gpu/drm/apple/), fixing a family of bugs where HDMI audio dies after turning the TV/monitor off and on again and only a reboot brings it back, plus the coprocessor crashes found while chasing them.

Tested on a Mac mini (J474s, M2 Pro) running Debian "Bananas" with the Asahi 7.1.13-3 kernel, driving a 4K TV at 3840x2160@60 over HDMI.

Status: personal patches, not submitted upstream. See AI assistance below.

Asahi kernel Patch Git tag Contents
asahi-7.1.13-3 hdmi-fixes-asahi-7.1.13-3-r4.patch asahi-7.1.13-3-r4 fixes #1-#22 (use this)
asahi-7.1.13-3 hdmi-fixes-asahi-7.1.13-3-r3.patch asahi-7.1.13-3-r3 fixes #1-#14
asahi-7.1.13-3 hdmi-fixes-asahi-7.1.13-3-r2.patch asahi-7.1.13-3-r2 fixes #1-#13
asahi-7.1.13-3 hdmi-fixes-asahi-7.1.13-3.patch asahi-7.1.13-3 fixes #1-#11

This repository supersedes my two earlier repositories, whose fixes are integrated here: asahi-hdmi-dcp-fix (video) and asahi-hdmi-audio-fix (first audio fix).

Test setup

  • Computer: Mac mini (J474s, M2 Pro), Debian "Bananas", Asahi kernel 7.1.13-3 with these patches.
  • Display: TCL 43T69C (T69C series, model year 2025; 43" QLED, 3840x2160, 60 Hz panel, HDR10/HLG/HDR10+/Dolby Vision, 3 HDMI ports, Google TV). EDID: manufacturer TOL, product name "Beyond TV", product code 10357, EDID manufacture date week 20 of 2023.
  • The HDMI port matters on this TV: on HDMI 1 it offers an HDMI 1.4 EDID (340 MHz), so 4K60 is only possible as YCbCr 4:2:2 (coloured fringes on red/blue text). On HDMI 3 it offers an HDMI 2.0 EDID (600 MHz) and 4K60 runs as RGB. All r4 tests were done on HDMI 3.
  • TV settings: picture mode "PC" (only offered while the HDMI input is active) and Motion Clarity off; without these the TV's own processing smears text.
  • Cable: use an Ultra High Speed certified HDMI cable; 4K60 RGB is ~18 Gbit/s, more than a "High Speed" (10.2 Gbit/s) cable is rated for.

The short version (ELI5)

Your Mac doesn't talk to the TV directly. There is a little helper computer inside the chip (the DCP) that handles the screen and the sound going to the TV. The Linux driver talks to that helper by sending it letters, and the helper keeps a row of 16 mailboxes, one per conversation ("video", "audio", ...).

Every time you switch the TV off and on, the helper closes the old conversations and opens new ones. The bugs were all variations of the driver getting its bookkeeping wrong during that switch:

  1. The "already open" sticky note that never came off. The driver wrote "audio is open" on a note, but never erased it when the TV went away. Next time it saw the note and thought there was nothing to do. (first fix, published earlier)
  2. Mailboxes that were never emptied. Every TV on/off took a fresh mailbox and never gave the old one back. After ~8 switches all 16 were full and sound was gone for good.
  3. Lost letters blocking a mailbox. If the helper never answered a letter, the driver kept waiting for that answer forever, so the mailbox could never be recycled.
  4. Waiting for a reply to "goodbye". When closing audio, the driver waited for the helper to answer "goodbye", while the helper was busy closing things itself. Now it just says goodbye and moves on.
  5. Switching the TV back on too fast. If you flipped the TV off and on quickly, the driver never noticed it had been away, so it never restarted the sound.
  6. Two people waiting for each other. The sound part of the driver held a door shut while waiting for an answer, and the answer could only come in through that same door. Classic deadlock; it only broke after a 1-second timeout, and sometimes not at all.
  7. A pile of smaller bookkeeping bugs found while fixing the above: memory that was never given back, a lock that was never released, a warning that could never print, and mailboxes being reused while someone was still using them.
  8. Pulling the plug while the music plays. When the TV went away while sound was playing, the driver let the sound keep streaming until the helper yanked the cable out from under it. The sound chip (a second helper, the SIO) crashed. Now the driver stops the music the moment the TV signal drops.
  9. Answering the wrong phone. When the connection had to be retried, the helper briefly offered two audio conversations. The driver opened the old one, then ignored the new one because "audio is already open".
  10. Abandoned conversations. On that same retry, the helper simply walked away from the old audio conversation without saying goodbye, so its mailbox stayed occupied forever: one more full mailbox per TV switch. The driver now notices this and cleans up the abandoned mailbox itself.
  11. "Sure, done!" when it wasn't. When the screen woke up from power saving, the helper sometimes refused to switch the picture back on, but the driver never read the helper's answer and assumed it had worked. The screen stayed black until you switched the TV off and on. The driver now reads the answer, and while the TV itself is asleep it simply waits for the TV to come back instead of trying.
  12. Just ask twice. After power saving, the helper often says "no" the first time and "yes" to the exact same request a moment later. The driver now asks a second time after 0.2 s, so the picture comes back right away instead of after ~30 seconds.
  13. Waiting for a letter that was thrown away. While the TV was asleep, the driver still handed the helper a new picture. The helper silently threw it away, and the desktop then waited 30 seconds for a "picture shown" confirmation that never came. The driver now doesn't send pictures while there is no valid screen mode and confirms them itself.
  14. The helper announces "I'm restarting" and pulls the cable 5 s later. Sometimes the DCP restarts its audio/video conversations on its own ("Capabilities changed"), even with the TV on. It writes that in its diary first. The driver now reads the diary and stops the music in time, without sending the helper any letters while it is busy restarting (letters in that window crashed the DCP itself).
  15. A letter to someone who already left. In several races the driver still sent "open" or "close" to an audio conversation the helper had already thrown away, and the helper crashed. The driver now marks a conversation as "gone" as soon as it learns the helper is closing it (a farewell note the helper sends just before, or its diary) and never writes to it again.
  16. Stop the music and wait until it has stopped. When the driver itself resets the TV link (TV comes back without first saying goodbye, a link retry), it now stops the sound and waits for the SIO to confirm before pulling the link. Before, "stop" was only posted, and the link was gone 0.3 ms later while the SIO was still streaming: SIO crash, sound gone until reboot.
  17. Don't start the music while nobody can see the screen. Some TVs wake up by themselves at night for half a minute. The driver used to offer HDMI sound as soon as the TV appeared, even with the screen asleep, so a stream started, and when the TV went back to sleep 33 s later the SIO crashed. Sound is now only offered once the screen is actually on (it was already switched off whenever the screen sleeps).
  18. A screaming diary. One of the debug messages was printed ~1500 times per second while music played. Quiet now.
  19. Two optional switches: keep the desktop layout when the TV is switched off (hdmi_keep_connected), and automatically reset the TV link when the helper stops answering audio letters (audio_recover). See Module options.

Result: HDMI audio survives turning the TV off and on, fast or slow, while playing or idle, indefinitely; the screen comes back after power saving without touching the TV, within seconds of the TV coming on; and none of the SIO/DCP crash paths seen so far can be triggered any more by the driver's own actions. With the helper script in contrib/ a video that is playing in the browser simply continues after the TV was off.


What you still might notice

  • The first link attempt after switching the TV on fails every time (three AppleDCPDPTXRemotePort timeouts in the log). A retry, already present in this tree, recovers it within ~6 seconds. The root cause is on the video/link side and is not addressed here.
  • An audio stream that is playing when the TV goes away is stopped (applications see -EBADFD / "File descriptor in bad state"). This is intentional: it prevents the SIO crash. A raw aplay/speaker-test has to be restarted. PipeWire does not reopen the device by itself after this (re-tested with r4); contrib/hdmi-audio-watch does that for you.
  • "Capabilities changed". On some stream starts the DCP firmware restarts its AV interfaces by itself, which blanks the screen for ~3 s. Not every time; it is firmware behaviour and is not prevented here, only made harmless (fix #15).
  • A crashed SIO is not restarted. If the SIO coprocessor crashes anyway, HDMI audio stays dead until reboot (apple-sio ... Device is crashed), and browser videos stall because their audio output never starts. Switch to the internal speaker meanwhile. The one remaining crash path seen: the TV dropping HPD while a stream runs, where the SIO died 0.8 ms after the interrupt, before any driver code could act. Fix #22 makes sure no stream runs in the situation where that happened (screen asleep, TV waking up by itself). With the screen on, HPD-low during playback survived every time it was observed.
  • Silent black screens without any kernel message turned out to be a loose HDMI plug at the TV on this setup (reproducible by wiggling it). 4K60 RGB needs ~18 Gbit/s; a "High Speed" (10.2 Gbit/s) cable is marginal. Use an Ultra High Speed certified cable.
  • Not explained yet, seen once each: a DCP firmware assert (ASSERT!AFKSerialize.cpp:548) when an audio OPEN overlapped a modeset retry right after DPMS-on; and a full system hang on a second systemctl suspend with audio playing (first suspend fine).
  • Some TVs switch between EDID profiles (here: 42 modes / 8-bit vs. 34 modes / 10-bit BT.2020 after a TV-menu "repair"). The DCP firmware seems to reject the first modeset after power saving mainly in the 10-bit mode; fix #13 retries it. The bit depth is chosen by the driver from what the TV offers, not by the desktop.
  • KDE may rearrange windows after a reconnect, because the screen really did disappear for a few seconds. That's desktop behaviour, not a kernel issue.

Technical description

Architecture in one paragraph

The DCP coprocessor exposes several AFK endpoints over RTKit mailboxes. The AV endpoint (ep 0x29) carries EPIC services announced by firmware: DCPAVSimpleVideoInterface and DCPAVAudioInterface. Each announced service occupies one entry in ep->services[AFK_MAX_CHANNEL] (16). Commands to a service use per-service command slots (cmd_map, MAX_PENDING_CMDS = 16) whose replies arrive through the endpoint's ordered workqueue, which also processes ANNOUNCE and TEARDOWN reports. Audio DMA from memory to the DisplayPort audio block is done by a separate coprocessor, SIO, through the dmaengine API (DPA*_TX channels). The ALSA side lives in audio.c (dcp-dp-audio), and the glue between ALSA and the audio service in av.c.

Fix list

# Symptom Root cause Fix
1 Audio dead after first reconnect is_open never cleared in teardown handlers; av_service_connect() returns early forever Clear is_open in av_audiosrv_teardown() / av_interface_teardown() (first patch set)
2 Audio permanently dead after ~7-8 reconnects, too many enabled services ep->num_channels only grew; service entries never reclaimed Reuse disabled entries; deferred disable via afk_service_try_disable() once torndown && bitmap_empty(cmd_map)
3 Same, after ~15-17 reconnects A command that timed out after teardown kept its cmd_map bit (free_on_ack) forever, so bitmap_empty() never became true Force-release free_on_ack orphans in afk_recv_handle_teardown(); release immediately on timeout if already torn down
4 Residual leak of #3 Blocking CLOSE in av_service_disconnect() racing TEARDOWN CLOSE is fire-and-forget (afk_service_call_async()); nothing used its reply
5 Fast unplug/replug: video returns, audio never reopens dptxport[0].connected stale-true; FW disconnect suppressed as stale; dcp_dptx_connect() returns early GPIO HPD handler always does dcp_dptx_disconnect() before dcp_dptx_connect()
6 -ETIMEDOUT on aplay -D hw:0,0 during reconnect prepare/startlink/stoplink/unprepare/get_elements/get_product_attrs held srv_rwsem for write across a blocking AFK call; TEARDOWN on the ordered workqueue blocked on that rwsem, and the reply was queued behind it Snapshot the service pointer under a short read lock; never hold srv_rwsem across a blocking call
7a Possible UAF / double free on slot reuse Window between snapshot and cmd_map allocation: TEARDOWN + re-ANNOUNCE could memset() a slot under a stale caller; OPEN and stale caller collide on command index 0 afk_send_command*() refuse !enabled || torndown under service->lock; reuse only entries with empty cmd_map; reinitialise a reused entry under its lock with enabled = true last; spin_lock_init() once in afk_init()
7b Slot bypass av_service_disconnect() set enabled = false directly, bypassing deferred disable while async CLOSE still held a bit Removed
7c Same deadlock class as #6 via plug_lock av_service_disconnect() held plug_lock across dcpaud_disconnect() → snd_pcm_stop() → stoplink Call dcpaud_disconnect() outside plug_lock
7d AV endpoint hangs forever after a DMA-channel request failure dcp_pcm_open() returned with data_lock held Unlock on the error path
7e Memory leak on every PCM open dcp_audiosrv_osobject_call() never freed its buffer kfree() on all paths
7f Errors never logged warned_* checks inverted; uninitialised size printed Fixed
8 STOP_LINK from AFK workqueue self-deadlocks (1 s stall) snd_pcm_stop() from av_interface_teardown() (runs on the ordered AFK workqueue) issues a synchronous STOP_LINK whose reply is queued behind itself in_teardown flag: STOP_LINK is skipped when called from a teardown handler. STOP_LINK stays synchronous everywhere else (see "What didn't work")
9 SIO coprocessor crash (Shim DMA unavailable) on unplug during playback GPIO HPD-low IRQ was ignored by design; the late FW disconnect is suppressed as stale after a fast replug; firmware then tears down the DP audio link under running SIO DMA (DPA1_TX : Enabled in the crash dump) dcp_dp2hdmi_hpd(): on HPD low, call av_service_disconnect() immediately (audio only, video still left to DCP)
10 -ENXIO on open after a reconnect that needed a link retry During dptx_retry the old audio service is still current: a plugged:0 connect OPENs it and sets is_open; the new service's connect then returns early. Also: a late TEARDOWN of the old service cleared asrv->srv of the new one Teardown only acts if asrv->srv == service; a newly announced service starts with is_open = false; connect with is_open refreshes ALSA state instead of returning silently; after OPEN, don't mark open if the service was replaced meanwhile
11 One service slot leaked per reconnect (→ #2 again after ~15 reconnects) On dptx_retry firmware announces a new DCPAVAudioInterface and never tears down the previous one afk_service_retire(): when a new audio service is announced while an old one is still current, the driver applies the AFK-side teardown cleanup to the old one itself (mark torn down, drain orphans, deferred disable)
12 Black screen after power saving until the TV is power-cycled iomfb_modeset() ignored the u32 status returned by firmware for set_digital_out_mode (e.g. setmode failed, "pipe not enabled") and set valid_mode = true anyway; when the display came back, cb_hotplug() saw "already connected, valid mode" and sent no uevent, so the compositor never re-modeset Read the firmware status in the completion callback; on rejection keep valid_mode = false (the existing TimingElements path then re-fires the hotplug uevent). While the DP2HDMI HPD GPIO is low, skip the modeset entirely instead of retrying a doomed one on every commit
13 After power saving the picture returns only after ~33 s (3 × flip_done timed out) The first set_digital_out_mode after DPMS-on is rejected by firmware (status 0x6, seen with the 10-bit BT.2020 colour mode); an identical second request succeeds Retry a rejected modeset once after 200 ms; if it still fails, complete pending flips via vblank_wq so userspace doesn't stall
14 TV woken from standby: picture only ~33 s after the TV comes on (3 × flip_done timed out) After #12 skipped the modeset (HPD low), do_swap() still submitted the swap because it only checked connector->connected; firmware silently swallowed it (swallowed swap ... fControllerPowerState is 0) with no swap-complete, so that commit's flip_done never fired and the next commit waited 3 × 10 s do_swap() also requires valid_mode; otherwise it completes the flip immediately via dcp_drm_crtc_vblank()
15 SIO crash (and, in a first attempt, a DCP crash, ESR 0xbe000000) when firmware restarts AV interfaces with HPD high ("Restaring interfaces with reason - Capabilities changed") Firmware tears the DP audio link down ~5 s after logging the restart; SIO DMA still running. Sending STOP_LINK/CLOSE into the restarting service crashes the DCP New optional .syslog callback in apple_rtkit_ops (rtkit.c/rtkit.h; Rust VTABLE gets syslog: None). dcp matches the restart message and runs av_service_quiesce_local(): stops DMA on the AP side only (in_teardown so stoplink is skipped), no commands to firmware; a 10 s safety-net reconnect, cancelled by the normal announce/teardown
16 Compositor hangs waiting for a page flip after a suppressed stale FW disconnect Swap in flight when firmware sends cb_hotplug(0) with GPIO still high is swallowed without completion Schedule vblank_wq in the suppressed-disconnect branch too (pairs with #14)
17 (option) Desktop relayout every time the TV is switched off Real HPD-low → disconnect uevent → compositor drops the output hdmi_keep_connected (default off): keep connector->connected, invalidate the mode, free the EDID, no disconnect uevent; return path re-modesets via TimingElements/bad_link
18 (option) Audio service stops answering every command (1 s timeouts) indefinitely, no HPD event, no restart Firmware state; only a link reset recovers it audio_recover (default off): 3 consecutive -ETIMEDOUT from audio calls (≥60 s apart per recovery) → quiesce locally + full DPTX disconnect/reconnect
19 DCP crashes on OPEN/CLOSE sent to an audio channel the firmware is already destroying (HPD bounce during the 500 ms debounce; cable moved between ports; FW-initiated restart) Commands reached a channel in the window between the firmware's decision and its TEARDOWN Per-service fw_gone flag checked in afk_send_command*() (→ -ENODEV). Set from: the firmware's 0x60-byte going-away REPORT (le32@4==1, le32@8==0x20, "xcpi"@12, arrives 100-500 ms before teardown); the firmware syslog line AFKMailboxSharedMemoryEndpointInterface ... if:<ch>; before every driver-initiated dcp_dptx_disconnect(); and CLOSE is skipped within 1.5 s of an HPD-high irq. Cleared on slot reinit
20 Journal flood: ~12 000 lines/s during playback; ~2 M expected announce but got 0xc0 lines in 27 min after a FW restart #19's diagnostic report logging; status reports (~260/s) keep arriving on a retired channel 0x70 status reports at dev_dbg; unknown reports once per 10 s with a 128-byte dump; NOTIFY+REPORT on a retired channel at dev_dbg, other unexpected messages dev_err_ratelimited
21 SIO crash (Shim DMA unavailable, DPA1_TX kStateActive) when the TV comes back with HPD-high only while a stream runs (also link retry) The HPD-high path and dptx_retry dropped the link without stopping DMA. First fix only posted the stop: dmaengine_terminate_async() schedules the SIO terminate on a workqueue, the link was gone 0.3 ms later (the HPD-low path only survived because its synchronous STOP_LINK gave the work time) av_service_quiesce_local() before those disconnects, plus dcpaud_sync_stop() → dmaengine_synchronize() so the SIO has acknowledged the terminate before the link drops (audio DMA quiesced in N ms)
22 SIO crash at night: TV wakes by itself for ~30 s with the screen asleep, audio stream started, TV drops HPD, SIO dies 0.8 ms later The driver offered HDMI audio (OPEN + jack on) on HPD-high even while the display was powered off by the compositor, although dcp_poweroff already tears audio down display_off flag (set first in dcp_poweroff, cleared first in dcp_poweron, written under plug_lock); av_service_connect() neither OPENs nor reports the jack while it is set (display off, not offering audio); dcp_poweron connects as before

Diagnostics added along the way: slot index in new service ... (slot N), a warning with service name / channel / tag on every AFK command timeout, the service-call group/command on timeout, and teardown/connect lines with channel and state.

What didn't work (and why it matters)

An intermediate version made STOP_LINK fire-and-forget to avoid the workqueue self-deadlock (#8). That crashed the SIO coprocessor on every stream stop, about 5-6 s after playback started (the speaker-test loop boundary, and PipeWire's 5-second idle suspend). Without waiting for the STOP_LINK reply, UNPREPARE/PREPARE reach the DCP while the link is still stopping and SIO DMA is still terminating. Reverting just that change removed the crash completely. Firmware needs STOP_LINK completion, not just ordering in the ring buffer.

Test method

  • speaker-test -D hw:0,0 -c2 -t sine running continuously (bypasses PipeWire, keeps a stream active during every transition)
  • TV power cycles, both fast (off/on within ~4 s) and slow (> 10 s)
  • journalctl -k (not dmesg: the ring buffer wraps and loses entries) watched for: SIO crashes, AFK timeouts on live services, slot index, too many enabled services
  • helper: count-cycles (in this repo). Note: it counts the DPTX link-setup timeouts under "LIVE service"; those are the known video-side ones.

Results on the final kernel (7.1.13-audiofix11), 2026-09-26:

  • Every TV power cycle: HPD low -> audio stream stopped immediately, no SIO crash; firmware abandons the old audio service during the link retry and the driver retires it (retiring abandoned audio ch N).
  • Service slots on ep 0x29 stay bounded: only slots 0 (video) and 1/2 (audio, alternating) are ever used. Before: one new slot per reconnect.
  • too many enabled services, SIO crashes, hung_task, WARNING/BUG: 0.
  • After each reconnect speaker-test -D hw:0,0 plays again.
  • Remaining timeouts are exclusively the three AppleDCPDPTXRemotePort link-setup calls per reconnect (video side, see above).

Results for fixes #12/#13 (7.1.13-fix12 / -fix13), 2026-09-28:

  • Before: after screen power saving, several times the TV got no signal until it was power-cycled (log: setmode failed, pipe not enabled, followed by no state change ... hotplug_wq NOT scheduled).
  • With #12: both recovery paths observed in the log: firmware rejection -> hotplug re-fired -> successful modeset; HPD low -> modeset skipped -> modeset on reconnect. Recovery without touching the TV, but ~33 s (3 × flip_done timed out).
  • With #13: picture back within seconds of pressing a key after power saving. Note: in that run the first modeset was already accepted (8-bit mode), so the retry itself has been reviewed but not yet seen firing in real use.

Results for fix #14 (7.1.13-fix14), 2026-09-29:

  • Before (fix13, TV woken from standby): HPD high -> picture after ~33 s, of which 30.7 s were 3 × flip_done timed out.
  • With #14, same scenario: HPD high -> picture after 8.7 s, zero flip_done timed out over a full night. The remaining ~7 s is the known first-link-attempt failure plus retry (see "What you still might notice").

Results for r4 (fixes #15-#22, 7.1.13-fix15 ... -fix25), up to 2026-10-16, on 4K60 RGB (HDMI 2.0 EDID):

  • #19/#20: dropped by FW, not sending (group 0 cmd 4) seen repeatedly where a DCP crash used to follow; journal size back to normal.
  • #21: three SIO crashes in the HPD-high-only case before (14 Oct 15:27, 20:40, 15 Oct 07:11, the last one with the first version of #21 that didn't wait). With the final #21: same scenario reproduced on purpose (wiggling a loose TV-side plug, stream RUNNING): audio DMA quiesced, then dcp_dptx_disconnect, SIO alive, stream reopened without reboot.
  • #22: screen asleep, TV off and on with the remote: link comes up, display off, not offering audio, no stream until the mouse is moved; then audio is offered immediately and PipeWire reopens the device within ~5 s. Screen sleep/wake with YouTube playing: continues by itself (with contrib/hdmi-audio-watch).

One event not explained by this patch set: once, 5 s into playback, the DCP firmware logged Restarting interfaces with reason - Capabilities changed and the TV showed an HDMI error until it was "repaired" from the TV menu (after which it reported a different EDID: 34 instead of 42 modes, 10-bit BT.2020). This happens inside TV/DCP firmware; the driver recovered correctly, but the running stream was lost.


Applying

hdmi-fixes-asahi-7.1.13-3-r4.patch (current) and the older -r3, -r2 and first patches are against the Asahi tag asahi-7.1.13-3 (AsahiLinux/linux commit 94fb23346d5), which is what Debian Bananas 7.1.13-3 packages. r4 touches drivers/gpu/drm/apple/ (afk.c, afk.h, audio.c, audio.h, av.c, av.h, dcp.c, dcp-internal.h, iomfb.c, iomfb_template.c) and, new in r4, the RTKit core for the syslog callback of fix #15: drivers/soc/apple/rtkit.c, include/linux/soc/apple/rtkit.h and rust/kernel/soc/apple/rtkit.rs (the Rust ops table must list the new field). The r4 patch was generated from the pristine tag files and verified to reproduce the tested tree byte for byte.

It is the complete set, including my earlier HDMI video fixes (external-display pixel clock, stale-disconnect suppression, DPTX connect retry, HPD re-sampling on resume). The audio fixes depend on that code (fixes #5, #9 and #11 live in the same HPD / retry paths) and the set was only tested as a whole.

cd linux-asahi                      # checked out at asahi-7.1.13-3
git apply --check hdmi-fixes-asahi-7.1.13-3-r4.patch
git apply hdmi-fixes-asahi-7.1.13-3-r4.patch

Module options

All default off; set them in /etc/modprobe.d/appledrm.conf and rebuild the initramfs (update-initramfs -u):

options appledrm hdmi_keep_connected=1
  • hdmi_keep_connected=1: the desktop keeps its layout when the TV is switched off (single-display setups). Recommended together with contrib/hdmi-audio-watch, because the PipeWire sink then survives and only needs its ALSA stream reopened.
  • audio_recover=1: automatic link reset when the audio service stops answering. Costs a few seconds of black picture each time; off here.

contrib

Userspace helpers used on the test machine (all optional):

  • hdmi-audio-watch + hdmi-audio-watch.service (systemd user service): when the HDMI jack goes off→on it suspends and resumes the HDMI sink (pactl suspend-sink), which makes PipeWire close and reopen the ALSA device while the sink keeps its id, so running apps (a browser playing YouTube) just continue. If the sink is missing it falls back to fix-hdmi-audio. It also primes the sink with 2 s of silence and has a watchdog that restarts a RUNNING stream whose hw_ptr stops moving. Edit SINK= if your card's sink has a different name.
  • fix-hdmi-audio: bounces the card profile (off → output:stereo-fallback) and sets the HDMI sink as default; restarts PipeWire first if WirePlumber's probe left only off/pro-audio. Note: the profile bounce recreates the sink, so apps playing at that moment lose their stream.
  • 51-hdmi-no-suspend.conf (WirePlumber, ~/.config/wireplumber/wireplumber.conf.d/): session.suspend-timeout-seconds = 0 for the HDMI sink, so PipeWire doesn't close the device after 5 s of silence (each new stream start risks a "Capabilities changed" firmware restart).
install -m 755 contrib/hdmi-audio-watch contrib/fix-hdmi-audio ~/.local/bin/   # or /usr/local/bin
install -m 644 contrib/hdmi-audio-watch.service ~/.config/systemd/user/
systemctl --user daemon-reload && systemctl --user enable --now hdmi-audio-watch

The service file starts /usr/local/bin/hdmi-audio-watch; adjust ExecStart= if you install elsewhere.


AI assistance

This work was done with AI assistance (Anthropic's Claude), in the open: one instance did source and log investigation on the machine and proposed diffs; a second instance acted as reviewer and caught several defects in the proposals before anything was built (including a use-after-free window, two deadlock hazards, and the STOP_LINK regression above). Every change was reviewed, built, booted and tested by me on real hardware. From r4 on, the fixes were written as specifications from the logs first, implemented by one instance and reviewed by the other before each build.

The Asahi Linux project does not accept LLM-assisted contributions, so these patches are not submitted upstream and live here instead. If you are an Asahi developer: the bug descriptions above are my own observations of your driver's behaviour and you are welcome to fix them however you see fit.

Author

Danny ter Haar (fromport)

Questions: fromport+asahi-hdmi at gmail.com