* [PATCH v2 1/3] ALSA: pcm: Retain components across native minor lookups
2026-10-24 11:41 [PATCH v2 0/3] ALSA: pcm: Retain components across codec teardown Cen Zhang
@ 2026-10-24 11:41 ` Cen Zhang
2026-10-24 11:41 ` [PATCH v2 2/3] ALSA: pcm: oss: Retain the PCM from minor lookup through release Cen Zhang
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Cen Zhang @ 2026-10-24 11:41 UTC (permalink / raw)
To: perex, tiwai
Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming, zzzccc427
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@gmail.com>
---
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
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2 2/3] ALSA: pcm: oss: Retain the PCM from minor lookup through release
2026-10-24 11:41 [PATCH v2 0/3] ALSA: pcm: Retain components across codec teardown Cen Zhang
2026-10-24 11:41 ` [PATCH v2 1/3] ALSA: pcm: Retain components across native minor lookups Cen Zhang
@ 2026-10-24 11:41 ` Cen Zhang
2026-10-24 11:41 ` [PATCH v2 3/3] ALSA: hda: Drain PCM users before cleaning up a failed codec probe Cen Zhang
2026-10-26 8:39 ` [PATCH v2 0/3] ALSA: pcm: Retain components across codec teardown Takashi Iwai
3 siblings, 0 replies; 5+ messages in thread
From: Cen Zhang @ 2026-10-24 11:41 UTC (permalink / raw)
To: perex, tiwai
Cc: linux-sound, linux-kernel, tiwai, baijiaju1990, jjzuming, zzzccc427
An OSS open uses the PCM before acquiring open_mutex and before the HDA
driver's open callback takes a codec PCM reference. In particular,
snd_pcm_oss_look_for_setup() walks the stream's setup list during this
interval. The lookup's card reference does not retain a PCM component
removed by codec-only unbind on a registered card:
OSS open HDA codec unbind
-------- ----------------
lookup OSS minor
retain card reference
snd_pcm_oss_look_for_setup()
disconnect PCM
wait for codec PCM references
snd_device_free()
snd_pcm_free()
use setup list or PCM in open
The OSS module pin retains its code, and proc disconnect drains proc
callbacks; neither retains the PCM for this early OSS open.
Use the PCM core's lookup-and-retain helper for the OSS minor. Hold the
PCM reference through the early setup lookup and transfer it to the file
on success. Drop it on every open failure or after substream release,
before the last card reference can invoke final card destruction.
The final PCM free now waits for that early opener before removing OSS
setup lists. A pending opener which resumes after disconnect is rejected
before entering the driver, and a waiting OSS opener observes the PCM's
component disconnection as well as whole-card shutdown.
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
include/sound/pcm.h | 2 ++
sound/core/oss/pcm_oss.c | 20 +++++++++++++-------
sound/core/pcm.c | 15 +++++++++++++++
3 files changed, 30 insertions(+), 7 deletions(-)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index 8fdb07f2f542..12559c2144cf 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -577,6 +577,8 @@ int snd_pcm_new_internal(struct snd_card *card, const char *id, int device,
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count);
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
+struct snd_pcm *snd_pcm_get_by_oss_minor(unsigned int minor);
+
struct snd_pcm_notify {
int (*n_register) (struct snd_pcm * pcm);
int (*n_disconnect) (struct snd_pcm * pcm);
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
index e8df27d09b97..9f0dae051ecf 100644
--- a/sound/core/oss/pcm_oss.c
+++ b/sound/core/oss/pcm_oss.c
@@ -2497,8 +2497,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file)
nonseekable_open(inode, file);
- pcm = snd_lookup_oss_minor_data(iminor(inode),
- SNDRV_OSS_DEVICE_TYPE_PCM);
+ pcm = snd_pcm_get_by_oss_minor(iminor(inode));
if (pcm == NULL) {
err = -ENODEV;
goto __error1;
@@ -2545,7 +2544,7 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file)
mutex_unlock(&pcm->open_mutex);
schedule();
mutex_lock(&pcm->open_mutex);
- if (pcm->card->shutdown) {
+ if (pcm->card->shutdown || pcm->disconnected) {
err = -ENODEV;
break;
}
@@ -2566,14 +2565,19 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file)
__error2:
snd_card_file_remove(pcm->card, file);
__error1:
- if (pcm)
- snd_card_unref(pcm->card);
+ if (pcm) {
+ struct snd_card *card = pcm->card;
+
+ snd_pcm_put(pcm);
+ snd_card_unref(card);
+ }
return err;
}
static int snd_pcm_oss_release(struct inode *inode, struct file *file)
{
struct snd_pcm *pcm;
+ struct snd_card *card;
struct snd_pcm_substream *substream;
struct snd_pcm_oss_file *pcm_oss_file;
@@ -2584,14 +2588,16 @@ static int snd_pcm_oss_release(struct inode *inode, struct file *file)
if (snd_BUG_ON(!substream))
return -ENXIO;
pcm = substream->pcm;
+ card = pcm->card;
if (!pcm->card->shutdown)
snd_pcm_oss_sync(pcm_oss_file);
mutex_lock(&pcm->open_mutex);
snd_pcm_oss_release_file(pcm_oss_file);
mutex_unlock(&pcm->open_mutex);
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;
}
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 17cd60e8825d..380ce0781342 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -1064,6 +1064,21 @@ struct snd_pcm *snd_pcm_get_by_minor(unsigned int minor, int type)
}
EXPORT_SYMBOL_GPL(snd_pcm_get_by_minor);
+#if IS_ENABLED(CONFIG_SND_PCM_OSS)
+/**
+ * snd_pcm_get_by_oss_minor - look up and retain an OSS PCM component
+ * @minor: OSS minor number
+ *
+ * Returns the same PCM and card references as snd_pcm_get_by_minor().
+ */
+struct snd_pcm *snd_pcm_get_by_oss_minor(unsigned int minor)
+{
+ return pcm_get_from_minor(minor, SNDRV_OSS_DEVICE_TYPE_PCM,
+ snd_lookup_oss_minor_data);
+}
+EXPORT_SYMBOL_GPL(snd_pcm_get_by_oss_minor);
+#endif
+
static ssize_t pcm_class_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread