- Shell 100%
|
|
||
|---|---|---|
| contrib | ||
| count-cycles | ||
| hdmi-fixes-asahi-7.1.13-3-r2.patch | ||
| hdmi-fixes-asahi-7.1.13-3-r3.patch | ||
| hdmi-fixes-asahi-7.1.13-3-r4.patch | ||
| hdmi-fixes-asahi-7.1.13-3.patch | ||
| README.md | ||
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-3with 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:
- 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)
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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".
- 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.
- "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.
- 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.
- 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.
- 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).
- 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.
- 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.
- 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).
- A screaming diary. One of the debug messages was printed ~1500 times per second while music played. Quiet now.
- 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
AppleDCPDPTXRemotePorttimeouts 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 rawaplay/speaker-testhas to be restarted. PipeWire does not reopen the device by itself after this (re-tested with r4);contrib/hdmi-audio-watchdoes 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 secondsystemctl suspendwith 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 sinerunning 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(notdmesg: 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 0x29stay 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,0plays again. - Remaining timeouts are exclusively the three
AppleDCPDPTXRemotePortlink-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 byno 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 outover 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, thendcp_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 (withcontrib/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 withcontrib/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 tofix-hdmi-audio. It also primes the sink with 2 s of silence and has a watchdog that restarts a RUNNING stream whosehw_ptrstops moving. EditSINK=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 onlyoff/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 = 0for 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