[PATCH v2 1/3] ALSA: pcm: Retain components across native minor lookups
From: Cen Zhang
Date: Sat Oct 24 2026 - 07:41:44 EST
The card reference acquired by snd_lookup_minor_data() does not retain a
PCM component that its owner explicitly frees on a live card. A native
open can obtain the PCM and be delayed before substream attachment or
the driver open callback acquires its own usage reference.
For example, writing a codec name to its HDA driver's sysfs unbind
attribute disconnects and frees that codec's PCMs while the card remains
registered. The codec PCM usage count does not cover an opener that has
not yet entered azx_pcm_open():
Native PCM open HDA codec unbind
--------------- ----------------
look up minor, retain card
retain raw PCM pointer
snd_hda_codec_disconnect_pcms()
disconnect PCM
wait for codec PCM references
codec_release_pcms()
snd_device_free()
snd_pcm_free()
use PCM in substream attachment
Acquire a PCM usage reference while still holding register_mutex across
minor lookup. Transfer it to the file on successful open and release it
after substream cleanup, before dropping the file's card reference.
Mappings also keep this reference through the file they retain. Balance
the temporary references used for PCM file checks and failed opens.
Keep proc rundown in the existing component disconnect path, after
open_mutex has been dropped. The PCM usage wait belongs in final free,
with no registration or open mutex held while users finish.
Close lookup admission and reject new substream attachments on component
disconnect. Final free closes admission even for BUILD-state cleanup,
wakes waiting openers and waits for existing PCM users without holding
register_mutex or open_mutex.
Keep registration under the same lookup gate and roll back earlier
direction minors if a later registration fails. Otherwise a failed
BUILD-state component can leave a minor pointing at freed storage.
Initialize PCM timers only after both direction registrations succeed,
so a failed registration leaves no partially initialized timers to
replace on retry.
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
include/sound/pcm.h | 9 +++++++
sound/core/pcm.c | 59 ++++++++++++++++++++++++++++++++++++++---
sound/core/pcm_native.c | 44 ++++++++++++++++++++----------
3 files changed, 95 insertions(+), 17 deletions(-)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 1cf511f8d880..8fdb07f2f542 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -543,11 +543,13 @@ struct snd_pcm {
struct snd_pcm_str streams[2];
struct mutex open_mutex;
wait_queue_head_t open_wait;
+ struct snd_refcount users; /* minor lookups and open files */
void *private_data;
void (*private_free) (struct snd_pcm *pcm);
bool internal; /* pcm is for internal use only */
bool nonatomic; /* whole PCM operations are in non-atomic context */
bool no_device_suspend; /* don't invoke device PM suspend */
+ bool disconnected; /* no new substream may be attached */
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
struct snd_pcm_oss oss;
#endif
@@ -559,6 +561,13 @@ struct snd_pcm {
extern const struct file_operations snd_pcm_f_ops[2];
+struct snd_pcm *snd_pcm_get_by_minor(unsigned int minor, int type);
+
+static inline void snd_pcm_put(struct snd_pcm *pcm)
+{
+ snd_refcount_put(&pcm->users);
+}
+
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count,
struct snd_pcm **rpcm);
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 064df4cd3b50..17cd60e8825d 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -725,6 +725,7 @@ static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
pcm->internal = internal;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
+ snd_refcount_init(&pcm->users);
INIT_LIST_HEAD(&pcm->list);
if (id)
strscpy(pcm->id, id, sizeof(pcm->id));
@@ -862,6 +863,14 @@ static int snd_pcm_free(struct snd_pcm *pcm)
if (!pcm)
return 0;
if (!pcm->internal) {
+ /* Also close lookup admission for BUILD-state cleanup. */
+ scoped_guard(mutex, ®ister_mutex) {
+ scoped_guard(mutex, &pcm->open_mutex) {
+ pcm->disconnected = true;
+ wake_up(&pcm->open_wait);
+ }
+ }
+ snd_refcount_sync(&pcm->users);
scoped_guard(mutex, ®ister_mutex)
pcm_call_notify(pcm, n_unregister);
}
@@ -897,6 +906,8 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream,
stream != SNDRV_PCM_STREAM_CAPTURE))
return -EINVAL;
*rsubstream = NULL;
+ if (pcm->disconnected)
+ return -ENODEV;
pstr = &pcm->streams[stream];
if (pstr->substream == NULL || pstr->substream_count == 0)
return -ENODEV;
@@ -1015,6 +1026,44 @@ void snd_pcm_detach_substream(struct snd_pcm_substream *substream)
substream->pstr->substream_opened--;
}
+static struct snd_pcm *pcm_get_from_minor(unsigned int minor, int type,
+ void *(*lookup)(unsigned int, int))
+{
+ struct snd_pcm *pcm;
+ struct snd_card *card;
+
+ scoped_guard(mutex, ®ister_mutex) {
+ pcm = lookup(minor, type);
+ if (!pcm)
+ return NULL;
+ if (!pcm->disconnected) {
+ snd_refcount_get(&pcm->users);
+ return pcm;
+ }
+ card = pcm->card;
+ }
+ snd_card_unref(card);
+ return NULL;
+}
+
+/**
+ * snd_pcm_get_by_minor - look up and retain a native PCM component
+ * @minor: native ALSA minor number
+ * @type: playback or capture PCM device type
+ *
+ * The lookup holds the PCM registration mutex through reference acquisition.
+ * The caller must release both the PCM reference with snd_pcm_put() and the
+ * card reference with snd_card_unref(). An open file retains its PCM reference
+ * until release, including any mappings that keep the file alive.
+ *
+ * Return: A referenced PCM, or NULL if the minor is absent or disconnected.
+ */
+struct snd_pcm *snd_pcm_get_by_minor(unsigned int minor, int type)
+{
+ return pcm_get_from_minor(minor, type, snd_lookup_minor_data);
+}
+EXPORT_SYMBOL_GPL(snd_pcm_get_by_minor);
+
static ssize_t pcm_class_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -1081,14 +1130,17 @@ static int snd_pcm_dev_register(struct snd_device *device)
&snd_pcm_f_ops[cidx], pcm,
pcm->streams[cidx].dev);
if (err < 0) {
+ while (--cidx >= 0)
+ if (pcm->streams[cidx].substream)
+ snd_unregister_device(pcm->streams[cidx].dev);
list_del_init(&pcm->list);
return err;
}
-
- for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
- snd_pcm_timer_init(substream);
}
+ for_each_pcm_substream(pcm, cidx, substream)
+ snd_pcm_timer_init(substream);
+
pcm_call_notify(pcm, n_register);
return err;
}
@@ -1101,6 +1153,7 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
guard(mutex)(®ister_mutex);
scoped_guard(mutex, &pcm->open_mutex) {
+ pcm->disconnected = true;
wake_up(&pcm->open_wait);
list_del_init(&pcm->list);
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 35152fae74ca..ba544fb4bc6c 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -2355,17 +2355,20 @@ static bool is_pcm_file(struct file *file)
{
struct inode *inode = file_inode(file);
struct snd_pcm *pcm;
+ struct snd_card *card;
unsigned int minor;
if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major)
return false;
minor = iminor(inode);
- pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
+ pcm = snd_pcm_get_by_minor(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
if (!pcm)
- pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
+ pcm = snd_pcm_get_by_minor(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
if (!pcm)
return false;
- snd_card_unref(pcm->card);
+ card = pcm->card;
+ snd_pcm_put(pcm);
+ snd_card_unref(card);
return true;
}
@@ -2942,11 +2945,16 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file)
nonseekable_open(inode, file);
- pcm = snd_lookup_minor_data(iminor(inode),
- SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
+ pcm = snd_pcm_get_by_minor(iminor(inode),
+ SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
- if (pcm)
- snd_card_unref(pcm->card);
+ if (pcm) {
+ struct snd_card *card = pcm->card;
+
+ if (err < 0)
+ snd_pcm_put(pcm);
+ snd_card_unref(card);
+ }
return err;
}
@@ -2957,11 +2965,16 @@ static int snd_pcm_capture_open(struct inode *inode, struct file *file)
nonseekable_open(inode, file);
- pcm = snd_lookup_minor_data(iminor(inode),
- SNDRV_DEVICE_TYPE_PCM_CAPTURE);
+ pcm = snd_pcm_get_by_minor(iminor(inode),
+ SNDRV_DEVICE_TYPE_PCM_CAPTURE);
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
- if (pcm)
- snd_card_unref(pcm->card);
+ if (pcm) {
+ struct snd_card *card = pcm->card;
+
+ if (err < 0)
+ snd_pcm_put(pcm);
+ snd_card_unref(card);
+ }
return err;
}
@@ -2999,7 +3012,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
mutex_unlock(&pcm->open_mutex);
schedule();
mutex_lock(&pcm->open_mutex);
- if (pcm->card->shutdown) {
+ if (pcm->card->shutdown || pcm->disconnected) {
err = -ENODEV;
break;
}
@@ -3025,6 +3038,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
static int snd_pcm_release(struct inode *inode, struct file *file)
{
struct snd_pcm *pcm;
+ struct snd_card *card;
struct snd_pcm_substream *substream;
struct snd_pcm_file *pcm_file;
@@ -3033,6 +3047,7 @@ static int snd_pcm_release(struct inode *inode, struct file *file)
if (snd_BUG_ON(!substream))
return -ENXIO;
pcm = substream->pcm;
+ card = pcm->card;
/* block until the device gets woken up as it may touch the hardware */
snd_power_wait(pcm->card);
@@ -3042,8 +3057,9 @@ static int snd_pcm_release(struct inode *inode, struct file *file)
kfree(pcm_file);
}
wake_up(&pcm->open_wait);
- module_put(pcm->card->module);
- snd_card_file_remove(pcm->card, file);
+ module_put(card->module);
+ snd_pcm_put(pcm);
+ snd_card_file_remove(card, file);
return 0;
}
--
2.43.0