FAILED: patch "[PATCH] ALSA: aloop: Don't constrain playback params in notify mode" failed to apply to 6.12-stable tree
Subsystems (2)
Sasha Levin
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x ded106036984a9dd766e7e1451b02e2d15c81438
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '[email protected]' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From ded106036984a9dd766e7e1451b02e2d15c81438 Mon Sep 17 00:00:00 2001
From: Henrik Enquist <[email protected]>
Date: Sat, 3 Oct 2026 15:26:11 +0200
Subject: [PATCH] ALSA: aloop: Don't constrain playback params in notify mode
With notify enabled (the pcm_notify module parameter or the "PCM Notify" control), the playback side of a cable may change format, rate and channels while a capture is running. loopback_check_format() then stops the capture and notifies the "PCM Slave" controls, so that the capture application can reopen with the new parameters. For this reason loopback_open() still gives a notify mode playback the unconstrained loopback_pcm_hardware.
Since commit 898dfe4687f4 ("ALSA: aloop: Fix racy hw constraints adjustment") the hw rules refine against the shared cable->hw instead of the substream's own runtime->hw. This pins the playback side to the parameters of an open capture whatever the notify setting is. A player probing the device sees only the capture's rate, format and channels, and the format change path in loopback_check_format() can't be reached by a new playback.
Skip the format, rate and channels rules for the playback substream when notify is enabled. Skip the period bytes rule as well: with a sound timer source, params_change() already takes the period size from a notify mode playback, and keeping the old capture's period bytes leaves a playback at another rate with a period that doesn't match the timer. The capture side stays constrained to the cable parameters.
The capture stop this makes reachable again was hardened by commit 826af7fa62e3 ("ALSA: aloop: Fix racy access at PCM trigger") and commit e5c33cdc6f40 ("ALSA: aloop: Fix peer runtime UAF during format-change stop"), which need to be applied first when backporting.
Reproducer, with notify enabled on the cable:
arecord -D hw:Loopback,1,0 -r 44100 -f S32_LE -c 2 /dev/null & aplay -D hw:Loopback,0,0 96k.wav
Before: aplay is forced to 44100 Hz and the capture keeps running.
After: aplay runs at 96000 Hz, the capture is stopped and "PCM Slave Rate" reports 96000.
--- sound/drivers/aloop.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
sound/drivers/aloop.c +20 -0
@@ -1073,6 +1073,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream)return !substream->stream; } +/* +* In notify mode the playback side may change format, rate, channels and, +* with a sound timer, the period size. A running capture that no longer +* matches is stopped by loopback_check_format(). +* call in loopback->cable_lock +*/ +static bool loopback_playback_unconstrained (struct loopback_pcm *dpcm) +{ +return dpcm->substream->stream ==SNDRV_PCM_STREAM_PLAYBACK && +get_notify (dpcm); +} +static int rule_format (struct snd_pcm_hw_params *params,struct snd_pcm_hw_rule *rule) {
@@ -1082,6 +1094,8 @@ static int rule_format(struct snd_pcm_hw_params *params,snd_mask_none (&m); scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; m.bits[0 ] = (u_int32_t )cable->hw.formats; m.bits[1 ] = (u_int32_t)(cable->hw.formats >>32 ); }
@@ -1096,6 +1110,8 @@ static int rule_rate(struct snd_pcm_hw_params *params,struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; t.min = cable->hw.rate_min; t.max = cable->hw.rate_max; }
@@ -1112,6 +1128,8 @@ static int rule_channels(struct snd_pcm_hw_params *params,struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; t.min = cable->hw.channels_min; t.max = cable->hw.channels_max; }
@@ -1128,6 +1146,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params,struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; t.min = cable->hw.period_bytes_min; t.max = cable->hw.period_bytes_max; }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 1/2 ALSA: aloop: Use guard() for mutex locks
From: Takashi Iwai <[email protected]>
[ Upstream commit ebd9b6c91d4e345b7ded4b56567c46267a45d961 ]
Replace the manual mutex lock/unlock pairs with guard() for code simplification.
Only code refactoring, and no behavior change.
Stable-dep-of: ded106036984 ("ALSA: aloop: Don't constrain playback params in notify mode")
--- sound/drivers/aloop.c | 83 +++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 50 deletions(-)
sound/drivers/aloop.c +33 -50
@@ -532,13 +532,12 @@ static int loopback_prepare(struct snd_pcm_substream *substream) dpcm->pcm_salign = salign; dpcm->pcm_period_size =frames_to_bytes (runtime, runtime->period_size); -mutex_lock (&dpcm->loopback->cable_lock); +guard (mutex)(&dpcm->loopback->cable_lock);if (!(cable->valid & ~(1 << substream->stream)) || (get_setup (dpcm)->notify && substream->stream ==SNDRV_PCM_STREAM_PLAYBACK ))params_change (substream); cable->valid |=1 << substream->stream; -mutex_unlock (&dpcm->loopback->cable_lock);return 0 ; }
@@ -979,9 +978,8 @@ static int loopback_hw_free(struct snd_pcm_substream *substream)struct loopback_pcm *dpcm = runtime->private_data;struct loopback_cable *cable = dpcm->cable; -mutex_lock (&dpcm->loopback->cable_lock); +guard (mutex)(&dpcm->loopback->cable_lock); cable->valid &= ~(1 << substream->stream); -mutex_unlock (&dpcm->loopback->cable_lock);return 0 ; }
@@ -1001,10 +999,10 @@ static int rule_format(struct snd_pcm_hw_params *params,struct snd_mask m;snd_mask_none (&m); -mutex_lock (&dpcm->loopback->cable_lock); - m.bits[0 ] = (u_int32_t )cable->hw.formats; - m.bits[1 ] = (u_int32_t)(cable->hw.formats >>32 ); -mutex_unlock (&dpcm->loopback->cable_lock); + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + m.bits[0 ] = (u_int32_t )cable->hw.formats; + m.bits[1 ] = (u_int32_t)(cable->hw.formats >>32 ); + }return snd_mask_refine (hw_param_mask (params, rule->var), &m); }
@@ -1015,10 +1013,10 @@ static int rule_rate(struct snd_pcm_hw_params *params,struct loopback_cable *cable = dpcm->cable;struct snd_interval t; -mutex_lock (&dpcm->loopback->cable_lock); - t.min = cable->hw.rate_min; - t.max = cable->hw.rate_max; -mutex_unlock (&dpcm->loopback->cable_lock); + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + t.min = cable->hw.rate_min; + t.max = cable->hw.rate_max; + } t.openmin = t.openmax =0 ; t.integer =0 ;return snd_interval_refine (hw_param_interval (params, rule->var), &t);
@@ -1031,10 +1029,10 @@ static int rule_channels(struct snd_pcm_hw_params *params,struct loopback_cable *cable = dpcm->cable;struct snd_interval t; -mutex_lock (&dpcm->loopback->cable_lock); - t.min = cable->hw.channels_min; - t.max = cable->hw.channels_max; -mutex_unlock (&dpcm->loopback->cable_lock); + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + t.min = cable->hw.channels_min; + t.max = cable->hw.channels_max; + } t.openmin = t.openmax =0 ; t.integer =0 ;return snd_interval_refine (hw_param_interval (params, rule->var), &t);
@@ -1047,10 +1045,10 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params,struct loopback_cable *cable = dpcm->cable;struct snd_interval t; -mutex_lock (&dpcm->loopback->cable_lock); - t.min = cable->hw.period_bytes_min; - t.max = cable->hw.period_bytes_max; -mutex_unlock (&dpcm->loopback->cable_lock); + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + t.min = cable->hw.period_bytes_min; + t.max = cable->hw.period_bytes_max; + } t.openmin =0 ; t.openmax =0 ; t.integer =0 ;
@@ -1264,12 +1262,10 @@ static int loopback_open(struct snd_pcm_substream *substream)int err =0 ;int dev =get_cable_index (substream); -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); dpcm =kzalloc (sizeof (*dpcm),GFP_KERNEL ); -if (!dpcm) { - err = -ENOMEM ; - goto unlock; - } +if (!dpcm) +return -ENOMEM ; dpcm->loopback = loopback; dpcm->substream = substream;
@@ -1354,7 +1350,6 @@ static int loopback_open(struct snd_pcm_substream *substream)free_cable (substream);kfree (dpcm); } -mutex_unlock (&loopback->cable_lock);return err; }
@@ -1366,9 +1361,8 @@ static int loopback_close(struct snd_pcm_substream *substream)if (dpcm->cable->ops->close_substream) err = dpcm->cable->ops->close_substream (dpcm); -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock);free_cable (substream); -mutex_unlock (&loopback->cable_lock);return err; }
@@ -1419,11 +1413,10 @@ static int loopback_rate_shift_get(struct snd_kcontrol *kcontrol, {struct loopback *loopback =snd_kcontrol_chip (kcontrol); -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); ucontrol->value.integer.value[0 ] = loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].rate_shift; -mutex_unlock (&loopback->cable_lock);return 0 ; }
@@ -1439,14 +1432,13 @@ static int loopback_rate_shift_put(struct snd_kcontrol *kcontrol, val =80000 ;if (val >120000 ) val =120000 ; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock);if (val != loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].rate_shift) { loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].rate_shift = val; change =1 ; } -mutex_unlock (&loopback->cable_lock);return change; }
@@ -1455,11 +1447,10 @@ static int loopback_notify_get(struct snd_kcontrol *kcontrol, {struct loopback *loopback =snd_kcontrol_chip (kcontrol); -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); ucontrol->value.integer.value[0 ] = loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].notify; -mutex_unlock (&loopback->cable_lock);return 0 ; }
@@ -1471,14 +1462,13 @@ static int loopback_notify_put(struct snd_kcontrol *kcontrol,int change =0 ; val = ucontrol->value.integer.value[0 ] ?1 :0 ; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock);if (val != loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].notify) { loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].notify = val; change =1 ; } -mutex_unlock (&loopback->cable_lock);return change; }
@@ -1490,14 +1480,13 @@ static int loopback_active_get(struct snd_kcontrol *kcontrol,unsigned int val =0 ; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); cable = loopback->cables[kcontrol->id.subdevice][kcontrol->id.device ^1 ];if (cable !=NULL ) {unsigned int running = cable->running ^ cable->pause; val = (running & (1 <<SNDRV_PCM_STREAM_PLAYBACK )) ?1 :0 ; } -mutex_unlock (&loopback->cable_lock); ucontrol->value.integer.value[0 ] = val;return 0 ; }
@@ -1540,11 +1529,10 @@ static int loopback_rate_get(struct snd_kcontrol *kcontrol, {struct loopback *loopback =snd_kcontrol_chip (kcontrol); -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); ucontrol->value.integer.value[0 ] = loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].rate; -mutex_unlock (&loopback->cable_lock);return 0 ; }
@@ -1564,11 +1552,10 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, {struct loopback *loopback =snd_kcontrol_chip (kcontrol); -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); ucontrol->value.integer.value[0 ] = loopback->setup[kcontrol->id.subdevice] [kcontrol->id.device].channels; -mutex_unlock (&loopback->cable_lock);return 0 ; }
@@ -1586,12 +1573,11 @@ static int loopback_access_get(struct snd_kcontrol *kcontrol,struct loopback *loopback =snd_kcontrol_chip (kcontrol);snd_pcm_access_t access; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); access = loopback->setup[kcontrol->id.subdevice][kcontrol->id.device].access; ucontrol->value.enumerated.item[0 ] = !is_access_interleaved (access); -mutex_unlock (&loopback->cable_lock);return 0 ; }
@@ -1759,12 +1745,11 @@ static void print_cable_info(struct snd_info_entry *entry,struct loopback *loopback = entry->private_data;int sub, num; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock); num = entry->name[strlen (entry->name)-1 ]; num = num =='0' ?0 :1 ;for (sub =0 ; sub <MAX_PCM_SUBSTREAMS ; sub++)print_substream_info (buffer, loopback, sub, num); -mutex_unlock (&loopback->cable_lock); }static int loopback_cable_proc_new (struct loopback *loopback,int cidx)
@@ -1793,10 +1778,9 @@ static void print_timer_source_info(struct snd_info_entry *entry, {struct loopback *loopback = entry->private_data; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock);snd_iprintf (buffer,"%s\n" , loopback->timer_source ? loopback->timer_source :"" ); -mutex_unlock (&loopback->cable_lock); } static void change_timer_source_info(struct snd_info_entry *entry,
@@ -1805,10 +1789,9 @@ static void change_timer_source_info(struct snd_info_entry *entry,struct loopback *loopback = entry->private_data;char line[64 ]; -mutex_lock (&loopback->cable_lock); +guard (mutex)(&loopback->cable_lock);if (!snd_info_get_line (buffer, line,sizeof (line)))loopback_set_timer_source (loopback,strim (line)); -mutex_unlock (&loopback->cable_lock); }static int loopback_timer_source_proc_new (struct loopback *loopback)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 ALSA: aloop: Don't constrain playback params in notify mode
From: Henrik Enquist <[email protected]>
[ Upstream commit ded106036984a9dd766e7e1451b02e2d15c81438 ]
With notify enabled (the pcm_notify module parameter or the "PCM Notify" control), the playback side of a cable may change format, rate and channels while a capture is running. loopback_check_format() then stops the capture and notifies the "PCM Slave" controls, so that the capture application can reopen with the new parameters. For this reason loopback_open() still gives a notify mode playback the unconstrained loopback_pcm_hardware.
Since commit 898dfe4687f4 ("ALSA: aloop: Fix racy hw constraints adjustment") the hw rules refine against the shared cable->hw instead of the substream's own runtime->hw. This pins the playback side to the parameters of an open capture whatever the notify setting is. A player probing the device sees only the capture's rate, format and channels, and the format change path in loopback_check_format() can't be reached by a new playback.
Skip the format, rate and channels rules for the playback substream when notify is enabled. Skip the period bytes rule as well: with a sound timer source, params_change() already takes the period size from a notify mode playback, and keeping the old capture's period bytes leaves a playback at another rate with a period that doesn't match the timer. The capture side stays constrained to the cable parameters.
The capture stop this makes reachable again was hardened by commit 826af7fa62e3 ("ALSA: aloop: Fix racy access at PCM trigger") and commit e5c33cdc6f40 ("ALSA: aloop: Fix peer runtime UAF during format-change stop"), which need to be applied first when backporting.
Reproducer, with notify enabled on the cable:
arecord -D hw:Loopback,1,0 -r 44100 -f S32_LE -c 2 /dev/null & aplay -D hw:Loopback,0,0 96k.wav
Before: aplay is forced to 44100 Hz and the capture keeps running.
After: aplay runs at 96000 Hz, the capture is stopped and "PCM Slave Rate" reports 96000.
--- sound/drivers/aloop.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
sound/drivers/aloop.c +20 -0
@@ -991,6 +991,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream)return !substream->stream; } +/* +* In notify mode the playback side may change format, rate, channels and, +* with a sound timer, the period size. A running capture that no longer +* matches is stopped by loopback_check_format(). +* call in loopback->cable_lock +*/ +static bool loopback_playback_unconstrained (struct loopback_pcm *dpcm) +{ +return dpcm->substream->stream ==SNDRV_PCM_STREAM_PLAYBACK && +get_notify (dpcm); +} +static int rule_format (struct snd_pcm_hw_params *params,struct snd_pcm_hw_rule *rule) {
@@ -1000,6 +1012,8 @@ static int rule_format(struct snd_pcm_hw_params *params,snd_mask_none (&m); scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; m.bits[0 ] = (u_int32_t )cable->hw.formats; m.bits[1 ] = (u_int32_t)(cable->hw.formats >>32 ); }
@@ -1014,6 +1028,8 @@ static int rule_rate(struct snd_pcm_hw_params *params,struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; t.min = cable->hw.rate_min; t.max = cable->hw.rate_max; }
@@ -1030,6 +1046,8 @@ static int rule_channels(struct snd_pcm_hw_params *params,struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; t.min = cable->hw.channels_min; t.max = cable->hw.channels_max; }
@@ -1046,6 +1064,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params,struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { +if (loopback_playback_unconstrained (dpcm)) +return 0 ; t.min = cable->hw.period_bytes_min; t.max = cable->hw.period_bytes_max; }
Signature · 1 line
2.53.0