From 651779aa252f0aab47137e74d6f549a3b62d6b25 Mon Sep 17 00:00:00 2001 From: nekolyanich Date: Sun, 11 Oct 2026 18:01:28 +0300 Subject: [PATCH 1/3] drm: apple: audio: ask for up to 256 KiB of audio elements, as macOS 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: cb24343242a7 ("gpu: drm: apple: Add DCP audio driver") Assisted-by: Claude:claude-opus-5-5 checkpatch Signed-off-by: nekolyanich --- drivers/gpu/drm/apple/afk.c | 4 ++-- drivers/gpu/drm/apple/audio.c | 21 ++++++++++++++++++--- drivers/gpu/drm/apple/av.c | 4 ++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index ff1f0f57afd9f..1e25586426c18 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -1190,7 +1190,7 @@ int afk_service_call_timeout(struct apple_epic_service *service, u16 group, if (!service) return -ENODEV; - bfr = kzalloc(bfr_len, GFP_KERNEL); + bfr = kvzalloc(bfr_len, GFP_KERNEL); if (!bfr) return -ENOMEM; @@ -1249,7 +1249,7 @@ int afk_service_call_timeout(struct apple_epic_service *service, u16 group, } out: - kfree(bfr); + kvfree(bfr); return ret; } diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index ac55f1f30f6ac..dd1f4b965a16e 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,7 +32,11 @@ #include "audio.h" #include "parser.h" -#define DCPAUD_ELEMENTS_MAXSIZE 16384 +/* + * As macOS 13 asks for. Elements that do not fit come back cut off at the + * buffer's end, with a used size of 0. + */ +#define DCPAUD_ELEMENTS_MAXSIZE SZ_256K #define DCPAUD_PRODUCTATTRS_MAXSIZE 1024 struct dcp_audio { @@ -736,18 +742,27 @@ static const struct component_ops dcpaud_comp_ops = { .unbind = dcpaud_comp_unbind, }; +static void dcpaud_free_elements(void *elements) +{ + kvfree(elements); +} + static int dcpaud_probe(struct platform_device *pdev) { struct dcp_audio *dcpaud; + int ret; dcpaud = devm_kzalloc(&pdev->dev, sizeof(*dcpaud), GFP_KERNEL); if (!dcpaud) return -ENOMEM; - dcpaud->elements = devm_kzalloc(&pdev->dev, DCPAUD_ELEMENTS_MAXSIZE, - GFP_KERNEL); + dcpaud->elements = kvzalloc(DCPAUD_ELEMENTS_MAXSIZE, GFP_KERNEL); if (!dcpaud->elements) return -ENOMEM; + ret = devm_add_action_or_reset(&pdev->dev, dcpaud_free_elements, + dcpaud->elements); + if (ret) + return ret; dcpaud->productattrs = devm_kzalloc(&pdev->dev, DCPAUD_PRODUCTATTRS_MAXSIZE, GFP_KERNEL); diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c index 563a02bb4e551..9708b2b0235a6 100644 --- a/drivers/gpu/drm/apple/av.c +++ b/drivers/gpu/drm/apple/av.c @@ -197,10 +197,10 @@ dcp_audiosrv_osobject_call(struct apple_epic_service *service, u16 group, } __attribute__((packed)) *hdr; static_assert(sizeof(*hdr) == 48); size_t bfr_len = output_maxsize + sizeof(*hdr); - void *bfr __free(kfree) = NULL; + void *bfr __free(kvfree) = NULL; int ret; - bfr = kzalloc(bfr_len, GFP_KERNEL); + bfr = kvzalloc(bfr_len, GFP_KERNEL); if (!bfr) return -ENOMEM; From 79bc315bfd79af4ea053300b726e4570272e29c2 Mon Sep 17 00:00:00 2001 From: nekolyanich Date: Sun, 11 Oct 2026 18:01:29 +0300 Subject: [PATCH 2/3] 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. 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: f25cb501c5fc ("drm: apple: audio: Implement runtime PM support") Assisted-by: Claude:claude-opus-5-5 checkpatch Signed-off-by: nekolyanich --- drivers/gpu/drm/apple/audio.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index dd1f4b965a16e..33d5ea3bac8a2 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -58,6 +58,8 @@ struct dcp_audio { struct snd_pcm_chmap_elem selected_chmap; struct dcp_sound_cookie selected_cookie; + /* the stream holds a runtime PM reference */ + bool pm_held; void *elements; void *productattrs; @@ -424,7 +426,10 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) if (!dcpaud_connection_up(dcpaud)) return -ENXIO; - WARN_ON(pm_runtime_get_sync(dcpaud->dev) < 0); + if (!dcpaud->pm_held) { + WARN_ON(pm_runtime_get_sync(dcpaud->dev) < 0); + dcpaud->pm_held = true; + } ret = dcp_audiosrv_startlink(dcpaud->dcp_dev, &dcpaud->selected_cookie); if (ret < 0) @@ -451,8 +456,15 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: ret = dcp_audiosrv_stoplink(dcpaud->dcp_dev); - pm_runtime_mark_last_busy(dcpaud->dev); - __pm_runtime_put_autosuspend(dcpaud->dev); + /* + * A failed START is undone with a STOP, also when it refused + * before taking the reference. + */ + if (dcpaud->pm_held) { + dcpaud->pm_held = false; + pm_runtime_mark_last_busy(dcpaud->dev); + __pm_runtime_put_autosuspend(dcpaud->dev); + } if (ret < 0) return ret; break; From e2230a4db1b83eb2f2575f550684e6ee1748fca0 Mon Sep 17 00:00:00 2001 From: nekolyanich Date: Sun, 11 Oct 2026 18:01:42 +0300 Subject: [PATCH 3/3] drm: apple: audio: drop the runtime PM reference only after the DMA stopped 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: f25cb501c5fc ("drm: apple: audio: Implement runtime PM support") Assisted-by: Claude:claude-opus-5-5 checkpatch Signed-off-by: nekolyanich --- drivers/gpu/drm/apple/audio.c | 43 +++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c index 33d5ea3bac8a2..406e6c8bca1ee 100644 --- a/drivers/gpu/drm/apple/audio.c +++ b/drivers/gpu/drm/apple/audio.c @@ -58,7 +58,7 @@ struct dcp_audio { struct snd_pcm_chmap_elem selected_chmap; struct dcp_sound_cookie selected_cookie; - /* the stream holds a runtime PM reference */ + /* the stream holds a runtime PM reference, put in sync_stop */ bool pm_held; void *elements; void *productattrs; @@ -455,16 +455,8 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: + /* the runtime PM reference goes in sync_stop */ ret = dcp_audiosrv_stoplink(dcpaud->dcp_dev); - /* - * A failed START is undone with a STOP, also when it refused - * before taking the reference. - */ - if (dcpaud->pm_held) { - dcpaud->pm_held = false; - pm_runtime_mark_last_busy(dcpaud->dev); - __pm_runtime_put_autosuspend(dcpaud->dev); - } if (ret < 0) return ret; break; @@ -473,6 +465,36 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd) return 0; } +/* + * ALSA calls this once a stream has stopped (STOP, SUSPEND, or the STOP + * that undoes a failed START), before the next hw_params, prepare or + * hw_free, at close, at system suspend and at disconnect, outside the + * stream lock. The DMA terminate is asynchronous: putting the runtime PM + * reference in trigger STOP powered the DPA's domain (ps_dpa*) down while + * SIO still ran DMA towards it, and SIO crashed ("Shim DMA unavailable", + * the TERMINATE timing out after it). Wait for the terminate first. + */ +static int dcp_pcm_sync_stop(struct snd_pcm_substream *substream) +{ + struct dcp_audio *dcpaud = substream->pcm->private_data; + bool held; + + snd_dmaengine_pcm_sync_stop(substream); + + /* START sets it under the stream lock */ + snd_pcm_stream_lock_irq(substream); + held = dcpaud->pm_held; + dcpaud->pm_held = false; + snd_pcm_stream_unlock_irq(substream); + + if (held) { + pm_runtime_mark_last_busy(dcpaud->dev); + __pm_runtime_put_autosuspend(dcpaud->dev); + } + + return 0; +} + struct snd_pcm_ops dcp_playback_ops = { .open = dcp_pcm_open, .close = dcp_pcm_close, @@ -480,6 +502,7 @@ struct snd_pcm_ops dcp_playback_ops = { .hw_free = dcp_pcm_hw_free, .prepare = dcp_pcm_prepare, .trigger = dcp_pcm_trigger, + .sync_stop = dcp_pcm_sync_stop, .pointer = snd_dmaengine_pcm_pointer, };