[PATCH v3 2/5] ALSA: core: Add the card file while looking up a minor

From: Cen Zhang

Date: Fri Oct 30 2026 - 04:04:36 EST


An open looks up its device with snd_lookup_minor_data(), which takes a
card reference, and adds its file to the card with snd_card_file_add()
only afterwards. The card reference does not keep alive a device that
its driver frees while the card stays registered, as the HD-audio codec
remove does with its PCMs. A remove that waits for the files of the card
can miss an open between the lookup and snd_card_file_add().

Add snd_lookup_minor_data_file() and snd_lookup_oss_minor_data_file(),
which add the file to the card while the minor lock is still held, and
record the device data of the minor with the file. After the minor has
been unregistered, an open either has its file on the card already or
does not find the device.

Add snd_card_file_sync() to wait until the files opened for a device are
gone, and wake its waiters in snd_card_file_remove(). Waiting for the
files of one device instead of all files of the card matters, because
the card files include the control device, which a sound server keeps
open for as long as it runs.

Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
include/sound/core.h | 7 +++++
sound/core/init.c | 61 +++++++++++++++++++++++++++++++++++++-----
sound/core/sound.c | 34 +++++++++++++++++++++++
sound/core/sound_oss.c | 21 +++++++++++++++
4 files changed, 117 insertions(+), 6 deletions(-)

diff --git a/include/sound/core.h b/include/sound/core.h
index d6584967fd49..4cecf04204f4 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -271,12 +271,16 @@ int snd_register_device(int type, struct snd_card *card, int dev,
void *private_data, struct device *device);
int snd_unregister_device(struct device *dev);
void *snd_lookup_minor_data(unsigned int minor, int type);
+void *snd_lookup_minor_data_file(unsigned int minor, int type,
+ struct file *file);

#ifdef CONFIG_SND_OSSEMUL
int snd_register_oss_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops, void *private_data);
int snd_unregister_oss_device(int type, struct snd_card *card, int dev);
void *snd_lookup_oss_minor_data(unsigned int minor, int type);
+void *snd_lookup_oss_minor_data_file(unsigned int minor, int type,
+ struct file *file);
#endif

int snd_minor_info_init(void);
@@ -323,7 +327,10 @@ int snd_card_add_dev_attr(struct snd_card *card,
const struct attribute_group *group);
int snd_component_add(struct snd_card *card, const char *component);
int snd_card_file_add(struct snd_card *card, struct file *file);
+int snd_card_file_add_data(struct snd_card *card, struct file *file,
+ const void *data);
int snd_card_file_remove(struct snd_card *card, struct file *file);
+void snd_card_file_sync(struct snd_card *card, const void *data);

struct snd_card *snd_card_ref(int card);

diff --git a/sound/core/init.c b/sound/core/init.c
index d518cb838777..ee660dc4098b 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -24,6 +24,7 @@
/* monitor files for graceful shutdown (hotplug) */
struct snd_monitor_file {
struct file *file;
+ const void *data; /* device data from the minor lookup, or NULL */
const struct file_operations *disconnected_f_op;
struct list_head shutdown_list; /* still need to shutdown */
struct list_head list; /* link of monitor files */
@@ -1075,17 +1076,18 @@ int snd_component_add(struct snd_card *card, const char *component)
EXPORT_SYMBOL(snd_component_add);

/**
- * snd_card_file_add - add the file to the file list of the card
+ * snd_card_file_add_data - add the file of a device to the file list
* @card: soundcard structure
* @file: file pointer
+ * @data: the device data the file is opened for
*
- * This function adds the file to the file linked-list of the card.
- * This linked-list is used to keep tracking the connection state,
- * and to avoid the release of busy resources by hotplug.
+ * Like snd_card_file_add(), but also records @data for
+ * snd_card_file_sync().
*
* Return: zero or a negative error code.
*/
-int snd_card_file_add(struct snd_card *card, struct file *file)
+int snd_card_file_add_data(struct snd_card *card, struct file *file,
+ const void *data)
{
struct snd_monitor_file *mfile;

@@ -1093,6 +1095,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file)
if (mfile == NULL)
return -ENOMEM;
mfile->file = file;
+ mfile->data = data;
mfile->disconnected_f_op = NULL;
INIT_LIST_HEAD(&mfile->shutdown_list);
guard(spinlock)(&card->files_lock);
@@ -1104,6 +1107,22 @@ int snd_card_file_add(struct snd_card *card, struct file *file)
get_device(&card->card_dev);
return 0;
}
+
+/**
+ * snd_card_file_add - add the file to the file list of the card
+ * @card: soundcard structure
+ * @file: file pointer
+ *
+ * This function adds the file to the file linked-list of the card.
+ * This linked-list is used to keep tracking the connection state,
+ * and to avoid the release of busy resources by hotplug.
+ *
+ * Return: zero or a negative error code.
+ */
+int snd_card_file_add(struct snd_card *card, struct file *file)
+{
+ return snd_card_file_add_data(card, file, NULL);
+}
EXPORT_SYMBOL(snd_card_file_add);

/**
@@ -1135,7 +1154,7 @@ int snd_card_file_remove(struct snd_card *card, struct file *file)
break;
}
}
- if (list_empty(&card->files_list))
+ if (list_empty(&card->files_list) || (found && found->data))
wake_up_all(&card->remove_sleep);
}
if (!found) {
@@ -1148,6 +1167,36 @@ int snd_card_file_remove(struct snd_card *card, struct file *file)
}
EXPORT_SYMBOL(snd_card_file_remove);

+static bool snd_card_has_data_file(struct snd_card *card, const void *data)
+{
+ struct snd_monitor_file *mfile;
+
+ list_for_each_entry(mfile, &card->files_list, list) {
+ if (mfile->data == data)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * snd_card_file_sync - wait until the files opened for a device are closed
+ * @card: soundcard structure
+ * @data: the device data registered for the minors of the device
+ *
+ * Waits until every file added for @data by snd_lookup_minor_data_file()
+ * or snd_lookup_oss_minor_data_file() has been removed from @card. The
+ * minors of the device must already be unregistered, so that no new file
+ * can be added for it.
+ */
+void snd_card_file_sync(struct snd_card *card, const void *data)
+{
+ guard(spinlock_irq)(&card->files_lock);
+ wait_event_lock_irq(card->remove_sleep,
+ !snd_card_has_data_file(card, data),
+ card->files_lock);
+}
+EXPORT_SYMBOL_GPL(snd_card_file_sync);
+
#ifdef CONFIG_PM
/**
* snd_power_ref_and_wait - wait until the card gets powered up
diff --git a/sound/core/sound.c b/sound/core/sound.c
index 24f40763a20c..94334cbc02b5 100644
--- a/sound/core/sound.c
+++ b/sound/core/sound.c
@@ -115,6 +115,40 @@ void *snd_lookup_minor_data(unsigned int minor, int type)
}
EXPORT_SYMBOL(snd_lookup_minor_data);

+/**
+ * snd_lookup_minor_data_file - get user data of a device and add the file
+ * @minor: the minor number
+ * @type: device type (SNDRV_DEVICE_TYPE_XXX)
+ * @file: the file being opened
+ *
+ * Like snd_lookup_minor_data(), but instead of taking a reference to the
+ * card, adds @file to the file list of the card while the minor is still
+ * registered, and records the user data for snd_card_file_sync(). The
+ * caller must call snd_card_file_remove() when it is done with the file.
+ *
+ * Return: The user data pointer if the specified device is found, %NULL if
+ * it is not found, or an error pointer if the file cannot be added.
+ */
+void *snd_lookup_minor_data_file(unsigned int minor, int type,
+ struct file *file)
+{
+ struct snd_minor *mreg;
+ int err;
+
+ if (minor >= ARRAY_SIZE(snd_minors))
+ return NULL;
+ guard(mutex)(&sound_mutex);
+ mreg = snd_minors[minor];
+ if (!mreg || mreg->type != type || !mreg->private_data ||
+ !mreg->card_ptr)
+ return NULL;
+ err = snd_card_file_add_data(mreg->card_ptr, file, mreg->private_data);
+ if (err < 0)
+ return ERR_PTR(err);
+ return mreg->private_data;
+}
+EXPORT_SYMBOL(snd_lookup_minor_data_file);
+
#ifdef CONFIG_MODULES
static struct snd_minor *autoload_device(unsigned int minor)
{
diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c
index 7eb49f2cada5..2f0bc481a46d 100644
--- a/sound/core/sound_oss.c
+++ b/sound/core/sound_oss.c
@@ -41,6 +41,27 @@ void *snd_lookup_oss_minor_data(unsigned int minor, int type)
}
EXPORT_SYMBOL(snd_lookup_oss_minor_data);

+/* Like snd_lookup_minor_data_file(), for OSS minors */
+void *snd_lookup_oss_minor_data_file(unsigned int minor, int type,
+ struct file *file)
+{
+ struct snd_minor *mreg;
+ int err;
+
+ if (minor >= ARRAY_SIZE(snd_oss_minors))
+ return NULL;
+ guard(mutex)(&sound_oss_mutex);
+ mreg = snd_oss_minors[minor];
+ if (!mreg || mreg->type != type || !mreg->private_data ||
+ !mreg->card_ptr)
+ return NULL;
+ err = snd_card_file_add_data(mreg->card_ptr, file, mreg->private_data);
+ if (err < 0)
+ return ERR_PTR(err);
+ return mreg->private_data;
+}
+EXPORT_SYMBOL(snd_lookup_oss_minor_data_file);
+
static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev)
{
int minor;
--
2.43.0