ALSA: compress: stop active streams on disconnect

Track open compressed streams per device so disconnect can stop
active streams and wake waiters before snd_unregister_device().

This aligns compressed stream teardown with PCM disconnect behavior
and prevents active userspace streams from running into unregister
races.

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
Reviewed-by: Liam Girdwood <liam.r.girdwood@intel.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20261007084955.1256-4-peter.ujfalusi@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Peter Ujfalusi 2026-10-07 11:49:30 +03:00 committed by Mark Brown
parent fd7f98db9c
commit b04c9229ee
2 changed files with 41 additions and 9 deletions

View File

@ -188,6 +188,7 @@ struct snd_compr_ops {
* @card: sound card pointer
* @direction: Playback or capture direction
* @lock: device lock
* @open_list: list of open compress files
* @device: device id
* @use_pause_in_draining: allow pause in draining, true when set
*/
@ -199,6 +200,7 @@ struct snd_compr {
struct snd_card *card;
unsigned int direction;
struct mutex lock;
struct list_head open_list;
int device;
bool use_pause_in_draining;
#ifdef CONFIG_SND_VERBOSE_PROCFS

View File

@ -42,6 +42,7 @@
#endif
struct snd_compr_file {
struct list_head list;
unsigned long caps;
struct snd_compr_stream stream;
};
@ -124,6 +125,7 @@ static int snd_compr_open(struct inode *inode, struct file *f)
ret = -ENOMEM;
goto __error;
}
INIT_LIST_HEAD(&data->list);
INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work);
@ -143,8 +145,11 @@ static int snd_compr_open(struct inode *inode, struct file *f)
#endif
data->stream.runtime = runtime;
f->private_data = (void *)data;
scoped_guard(mutex, &compr->lock)
scoped_guard(mutex, &compr->lock) {
ret = compr->ops->open(&data->stream);
if (!ret)
list_add_tail(&data->list, &compr->open_list);
}
__error:
if (ret) {
@ -165,14 +170,19 @@ static int snd_compr_free(struct inode *inode, struct file *f)
cancel_delayed_work_sync(&data->stream.error_work);
switch (runtime->state) {
case SNDRV_PCM_STATE_RUNNING:
case SNDRV_PCM_STATE_DRAINING:
case SNDRV_PCM_STATE_PAUSED:
data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP);
break;
default:
break;
scoped_guard(mutex, &compr->lock) {
if (!list_empty(&data->list))
list_del_init(&data->list);
switch (runtime->state) {
case SNDRV_PCM_STATE_RUNNING:
case SNDRV_PCM_STATE_DRAINING:
case SNDRV_PCM_STATE_PAUSED:
data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP);
break;
default:
break;
}
}
snd_compr_task_free_all(&data->stream);
@ -1450,8 +1460,27 @@ static int snd_compress_dev_register(struct snd_device *device)
static int snd_compress_dev_disconnect(struct snd_device *device)
{
struct snd_compr *compr;
struct snd_compr_file *data;
compr = device->device_data;
scoped_guard(mutex, &compr->lock) {
list_for_each_entry(data, &compr->open_list, list) {
switch (data->stream.runtime->state) {
case SNDRV_PCM_STATE_RUNNING:
case SNDRV_PCM_STATE_DRAINING:
case SNDRV_PCM_STATE_PAUSED:
data->stream.ops->trigger(&data->stream,
SNDRV_PCM_TRIGGER_STOP);
break;
default:
break;
}
data->stream.runtime->state = SNDRV_PCM_STATE_DISCONNECTED;
wake_up(&data->stream.runtime->sleep);
}
}
snd_unregister_device(compr->dev);
return 0;
}
@ -1559,6 +1588,7 @@ int snd_compress_new(struct snd_card *card, int device,
compr->device = device;
compr->direction = dirn;
mutex_init(&compr->lock);
INIT_LIST_HEAD(&compr->open_list);
snd_compress_set_id(compr, id);