From 78908b51d0bc28e80c11e4d134dddfc13d3e8bc5 Mon Sep 17 00:00:00 2001 From: Vakarux12 Date: Mon, 29 Jun 2026 20:33:04 +0300 Subject: [PATCH 1/2] audio/pcm: skip period_elapsed for streams that are not started The T2 chip continuously sends timestamp events for all registered audio devices regardless of whether an ALSA stream is open. Previously, aaudio_handle_stream_timestamp() would call snd_pcm_period_elapsed() for every substream that had a PCM, even when no audio was playing. On PREEMPT(full) kernels this is fatal: the BCE DMA IRQ handler runs with bottom halves disabled, so any call chain that reaches schedule() triggers BUG: scheduling while atomic. The specific path is: bce_handle_dma_irq aaudio_handle_cmd_timestamp aaudio_handle_stream_timestamp snd_pcm_period_elapsed <- called on stopped stream snd_pcm_update_state <- ALSA detects XRUN, tries to stop snd_pcm_do_stop aaudio_pcm_trigger(STOP) aaudio_cmd_stop_io __aaudio_send_cmd_sync wait_for_completion_timeout <- sleeps, crash Fix this by returning early when stream->started is 0. This matches the existing guard in aaudio_pcm_pointer() which already handles the same condition. --- audio/pcm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/audio/pcm.c b/audio/pcm.c index a7e85d4..ae0bd56 100644 --- a/audio/pcm.c +++ b/audio/pcm.c @@ -285,6 +285,10 @@ static void aaudio_handle_stream_timestamp(struct snd_pcm_substream *substream, stream = aaudio_pcm_stream(substream); snd_pcm_stream_lock_irqsave(substream, flags); + if (!stream->started) { + snd_pcm_stream_unlock_irqrestore(substream, flags); + return; + } stream->remote_timestamp = timestamp; if (stream->waiting_for_first_ts) { stream->waiting_for_first_ts = false; From 218ead96bba5da717309d0296458c05834f13905 Mon Sep 17 00:00:00 2001 From: Vakarux12 Date: Mon, 29 Jun 2026 22:07:24 +0300 Subject: [PATCH 2/2] audio/pcm: defer stop_io to workqueue to fix deadlock on PREEMPT kernels aaudio_cmd_stop_io blocks waiting for a T2 firmware reply. When called from PCM trigger(STOP), it can block PipeWire's main thread while the RT data thread holds a lock the main thread needs, causing a deadlock. It can also be called from atomic context (BCE DMA BH handler via snd_pcm_period_elapsed -> snd_pcm_do_stop) causing scheduling-while-atomic. Always defer the stop via schedule_work so trigger(STOP) returns immediately. In trigger(START), use cancel_work_sync to cancel any pending (not yet running) stop before starting, avoiding a start/stop race without blocking callers unnecessarily. --- audio/audio.h | 1 + audio/pcm.c | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/audio/audio.h b/audio/audio.h index ebbfaa1..a7ec777 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -83,6 +83,7 @@ struct aaudio_subdevice { bool is_pcm; struct snd_pcm *pcm; struct snd_jack *jack; + struct work_struct stop_work; }; struct aaudio_alsa_pcm_id_mapping { const char *name; diff --git a/audio/pcm.c b/audio/pcm.c index ae0bd56..e1fa3d8 100644 --- a/audio/pcm.c +++ b/audio/pcm.c @@ -1,5 +1,6 @@ #include "pcm.h" #include "audio.h" +#include static u64 aaudio_get_alsa_fmtbit(struct aaudio_apple_description *desc) { @@ -178,23 +179,29 @@ static void aaudio_pcm_start(struct snd_pcm_substream *substream) pr_debug("aaudio: Started the audio device in %lluns\n", ktime_to_ns(time_end - time_start)); } +static void aaudio_pcm_stop_work(struct work_struct *work) +{ + struct aaudio_subdevice *sdev = container_of(work, struct aaudio_subdevice, stop_work); + aaudio_cmd_stop_io(sdev->a, sdev->dev_id); +} + static int aaudio_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct aaudio_subdevice *sdev = snd_pcm_substream_chip(substream); struct aaudio_stream *stream = aaudio_pcm_stream(substream); pr_debug("aaudio_pcm_trigger %x\n", cmd); - /* We only supports triggers on the #0 buffer */ if (substream->number != 0) return 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: + cancel_work_sync(&sdev->stop_work); aaudio_pcm_start(substream); stream->started = 1; break; case SNDRV_PCM_TRIGGER_STOP: - aaudio_cmd_stop_io(sdev->a, sdev->dev_id); stream->started = 0; + schedule_work(&sdev->stop_work); break; default: return -EINVAL; @@ -265,6 +272,7 @@ int aaudio_create_pcm(struct aaudio_subdevice *sdev) } if (!id_mapping->name) sdev->alsa_id = sdev->a->next_alsa_id++; + INIT_WORK(&sdev->stop_work, aaudio_pcm_stop_work); err = snd_pcm_new(sdev->a->card, sdev->uid, sdev->alsa_id, (int) sdev->out_stream_cnt, (int) sdev->in_stream_cnt, &pcm); if (err < 0)