[PATCH v2] ALSA: control: add ioctl to retrieve full card components

Maciej Strozek posted 1 patch 2 weeks, 2 days ago
include/sound/core.h        |  4 +++-
include/uapi/sound/asound.h | 15 ++++++++++++++-
sound/core/control.c        | 34 +++++++++++++++++++++++++++++++++-
sound/core/control_compat.c |  3 ++-
sound/core/init.c           | 14 +++++++-------
5 files changed, 59 insertions(+), 11 deletions(-)
[PATCH v2] ALSA: control: add ioctl to retrieve full card components
Posted by Maciej Strozek 2 weeks, 2 days ago
The fixed-size components field in SNDRV_CTL_IOCTL_CARD_INFO can be too
small on systems with many audio devices.

Keep the existing struct snd_ctl_card_info ABI intact and add a new ioctl
to retrieve the full components string.

When the legacy components field is truncated, append '>' to indicate
that the full string is available via the new ioctl.

Link: https://github.com/alsa-project/alsa-lib/pull/494
Link: https://github.com/alsa-project/alsa-utils/pull/318
Suggested-by: Jaroslav Kysela <perex@perex.cz>
Suggested-by: Takashi Iwai <tiwai@suse.com>
Signed-off-by: Maciej Strozek <mstrozek@opensource.cirrus.com>
---
Changes for v2:
 - do not modify existing card->components field
 - add a new ioctl and struct to keep the full components string
 - handle the split/trim in snd_ctl_card_info()
---
 include/sound/core.h        |  4 +++-
 include/uapi/sound/asound.h | 15 ++++++++++++++-
 sound/core/control.c        | 34 +++++++++++++++++++++++++++++++++-
 sound/core/control_compat.c |  3 ++-
 sound/core/init.c           | 14 +++++++-------
 5 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/include/sound/core.h b/include/sound/core.h
index 64327e971122..0b16e2cb3f53 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -15,6 +15,7 @@
 #include <linux/stringify.h>
 #include <linux/printk.h>
 #include <linux/xarray.h>
+#include <uapi/sound/asound.h>		/* SNDRV_CTL_COMPONENTS_LEN */

 /* number of supported soundcards */
 #ifdef CONFIG_SND_DYNAMIC_MINORS
@@ -88,7 +89,8 @@ struct snd_card {
 	char irq_descr[32];		/* Interrupt description */
 	char mixername[80];		/* mixer name */
 	char components[128];		/* card components delimited with
-								space */
+								space, truncated to 127 chars */
+	char components_extended[SNDRV_CTL_COMPONENTS_LEN];	/* full card components string */
 	struct module *module;		/* top-level module */

 	void *private_data;		/* private data for soundcard */
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index d3ce75ba938a..5645ea8bb8a4 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -1058,7 +1058,7 @@ struct snd_timer_tread {
  *                                                                          *
  ****************************************************************************/

-#define SNDRV_CTL_VERSION		SNDRV_PROTOCOL_VERSION(2, 0, 9)
+#define SNDRV_CTL_VERSION		SNDRV_PROTOCOL_VERSION(2, 0, 10)

 struct snd_ctl_card_info {
 	int card;			/* card number */
@@ -1072,6 +1072,18 @@ struct snd_ctl_card_info {
 	unsigned char components[128];	/* card components / fine identification, delimited with one space (AC97 etc..) */
 };

+/*
+ * Card components can exceed the fixed 128 bytes in snd_ctl_card_info.
+ * Use SNDRV_CTL_IOCTL_CARD_COMPONENTS to retrieve the full string.
+ */
+#define SNDRV_CTL_COMPONENTS_LEN	512
+
+struct snd_ctl_card_components {
+	int card;			/* card number */
+	unsigned int length;		/* returned length of components string */
+	unsigned char components[SNDRV_CTL_COMPONENTS_LEN];
+};
+
 typedef int __bitwise snd_ctl_elem_type_t;
 #define	SNDRV_CTL_ELEM_TYPE_NONE	((__force snd_ctl_elem_type_t) 0) /* invalid */
 #define	SNDRV_CTL_ELEM_TYPE_BOOLEAN	((__force snd_ctl_elem_type_t) 1) /* boolean type */
@@ -1198,6 +1210,7 @@ struct snd_ctl_tlv {

 #define SNDRV_CTL_IOCTL_PVERSION	_IOR('U', 0x00, int)
 #define SNDRV_CTL_IOCTL_CARD_INFO	_IOR('U', 0x01, struct snd_ctl_card_info)
+#define SNDRV_CTL_IOCTL_CARD_COMPONENTS	_IOWR('U', 0x02, struct snd_ctl_card_components)
 #define SNDRV_CTL_IOCTL_ELEM_LIST	_IOWR('U', 0x10, struct snd_ctl_elem_list)
 #define SNDRV_CTL_IOCTL_ELEM_INFO	_IOWR('U', 0x11, struct snd_ctl_elem_info)
 #define SNDRV_CTL_IOCTL_ELEM_READ	_IOWR('U', 0x12, struct snd_ctl_elem_value)
diff --git a/sound/core/control.c b/sound/core/control.c
index 9c3fd5113a61..0f0d9828aeb1 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -868,10 +868,14 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
 			     unsigned int cmd, void __user *arg)
 {
 	struct snd_ctl_card_info *info __free(kfree) = NULL;
+	ssize_t n;

 	info = kzalloc(sizeof(*info), GFP_KERNEL);
 	if (! info)
 		return -ENOMEM;
+
+	static_assert(sizeof(info->components) >= 2);
+
 	scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
 		info->card = card->number;
 		strscpy(info->id, card->id, sizeof(info->id));
@@ -879,13 +883,39 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
 		strscpy(info->name, card->shortname, sizeof(info->name));
 		strscpy(info->longname, card->longname, sizeof(info->longname));
 		strscpy(info->mixername, card->mixername, sizeof(info->mixername));
-		strscpy(info->components, card->components, sizeof(info->components));
+		n = strscpy(info->components, card->components_extended,
+			    sizeof(info->components));
+		if (n < 0) {
+			info->components[sizeof(info->components) - 2] = '>';
+			info->components[sizeof(info->components) - 1] = '\0';
+		}
 	}
 	if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info)))
 		return -EFAULT;
 	return 0;
 }

+static int snd_ctl_card_components(struct snd_card *card, void __user *arg)
+{
+	struct snd_ctl_card_components *info __free(kfree) = NULL;
+	int copied;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
+		info->card = card->number;
+		copied = strscpy(info->components, card->components_extended,
+				 sizeof(info->components));
+		info->length = copied;
+	}
+
+	if (copy_to_user(arg, info, sizeof(*info)))
+		return -EFAULT;
+	return 0;
+}
+
 static int snd_ctl_elem_list(struct snd_card *card,
 			     struct snd_ctl_elem_list *list)
 {
@@ -1914,6 +1944,8 @@ static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg
 		return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
 	case SNDRV_CTL_IOCTL_CARD_INFO:
 		return snd_ctl_card_info(card, ctl, cmd, argp);
+	case SNDRV_CTL_IOCTL_CARD_COMPONENTS:
+		return snd_ctl_card_components(card, argp);
 	case SNDRV_CTL_IOCTL_ELEM_LIST:
 		return snd_ctl_elem_list_user(card, argp);
 	case SNDRV_CTL_IOCTL_ELEM_INFO:
diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c
index 6459809ed364..edb7b28d8177 100644
--- a/sound/core/control_compat.c
+++ b/sound/core/control_compat.c
@@ -416,7 +416,7 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file,
 		break;
 	}
 	return snd_ctl_elem_add(file, data, replace);
-}
+}

 enum {
 	SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32),
@@ -445,6 +445,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns
 	switch (cmd) {
 	case SNDRV_CTL_IOCTL_PVERSION:
 	case SNDRV_CTL_IOCTL_CARD_INFO:
+	case SNDRV_CTL_IOCTL_CARD_COMPONENTS:
 	case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
 	case SNDRV_CTL_IOCTL_POWER:
 	case SNDRV_CTL_IOCTL_POWER_STATE:
diff --git a/sound/core/init.c b/sound/core/init.c
index c372b3228785..3ec2f08ba765 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -714,7 +714,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src,
 	int len, loops;
 	bool is_default = false;
 	char *id;
-
+
 	copy_valid_id_string(card, src, nid);
 	id = card->id;

@@ -1023,24 +1023,24 @@ int __init snd_card_info_init(void)
  *
  *  Return: Zero otherwise a negative error code.
  */
-
+
 int snd_component_add(struct snd_card *card, const char *component)
 {
 	char *ptr;
 	int len = strlen(component);

-	ptr = strstr(card->components, component);
+	ptr = strstr(card->components_extended, component);
 	if (ptr != NULL) {
 		if (ptr[len] == '\0' || ptr[len] == ' ')	/* already there */
 			return 1;
 	}
-	if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
+	if (strlen(card->components_extended) + 1 + len + 1 > sizeof(card->components_extended)) {
 		snd_BUG();
 		return -ENOMEM;
 	}
-	if (card->components[0] != '\0')
-		strcat(card->components, " ");
-	strcat(card->components, component);
+	if (card->components_extended[0] != '\0')
+		strcat(card->components_extended, " ");
+	strcat(card->components_extended, component);
 	return 0;
 }
 EXPORT_SYMBOL(snd_component_add);
--
2.48.1
Re: [PATCH v2] ALSA: control: add ioctl to retrieve full card components
Posted by Jaroslav Kysela 2 weeks, 2 days ago
On 1/22/26 12:12, Maciej Strozek wrote:
> The fixed-size components field in SNDRV_CTL_IOCTL_CARD_INFO can be too
> small on systems with many audio devices.
> 
> Keep the existing struct snd_ctl_card_info ABI intact and add a new ioctl
> to retrieve the full components string.
> 
> When the legacy components field is truncated, append '>' to indicate
> that the full string is available via the new ioctl.

Thanks for the patch. Comments bellow.

> @@ -88,7 +89,8 @@ struct snd_card {
>   	char irq_descr[32];		/* Interrupt description */
>   	char mixername[80];		/* mixer name */
>   	char components[128];		/* card components delimited with
> -								space */
> +								space, truncated to 127 chars */
> +	char components_extended[SNDRV_CTL_COMPONENTS_LEN];	/* full card components string */

I would remove the original components[128] string and replace it
with dynamically allocated 'char *components_ptr' with 'unsigned int 
components_ptr_alloc' to store allocated size. Then the snd_component_add() 
function may allocate (and resize) dynamically the components_ptr on demand 
(using e.g. 32 byte step). This will also catch all drivers using the original 
struct member when compiled and make the memory usage a little bit happier.

> +/*
> + * Card components can exceed the fixed 128 bytes in snd_ctl_card_info.
> + * Use SNDRV_CTL_IOCTL_CARD_COMPONENTS to retrieve the full string.
> + */
> +#define SNDRV_CTL_COMPONENTS_LEN	512

This define seems not much useful. The sizeof() on the structure member works 
here.

> +struct snd_ctl_card_components {
> +	int card;			/* card number */
> +	unsigned int length;		/* returned length of components string */

... not used ... Zero terminated string is enough IMHO.

> +	unsigned char components[SNDRV_CTL_COMPONENTS_LEN];
> +};

					Jaroslav

-- 
Jaroslav Kysela <perex@perex.cz>
Linux Sound Maintainer; ALSA Project; Red Hat, Inc.
Re: [PATCH v2] ALSA: control: add ioctl to retrieve full card components
Posted by Takashi Iwai 2 weeks, 1 day ago
On Thu, 22 Jan 2026 14:30:04 +0100,
Jaroslav Kysela wrote:
> 
> On 1/22/26 12:12, Maciej Strozek wrote:
> > The fixed-size components field in SNDRV_CTL_IOCTL_CARD_INFO can be too
> > small on systems with many audio devices.
> > 
> > Keep the existing struct snd_ctl_card_info ABI intact and add a new ioctl
> > to retrieve the full components string.
> > 
> > When the legacy components field is truncated, append '>' to indicate
> > that the full string is available via the new ioctl.
> 
> Thanks for the patch. Comments bellow.
> 
> > @@ -88,7 +89,8 @@ struct snd_card {
> >   	char irq_descr[32];		/* Interrupt description */
> >   	char mixername[80];		/* mixer name */
> >   	char components[128];		/* card components delimited with
> > -								space */
> > +								space, truncated to 127 chars */
> > +	char components_extended[SNDRV_CTL_COMPONENTS_LEN];	/* full card components string */
> 
> I would remove the original components[128] string and replace it
> with dynamically allocated 'char *components_ptr' with 'unsigned int
> components_ptr_alloc' to store allocated size. Then the
> snd_component_add() function may allocate (and resize) dynamically the
> components_ptr on demand (using e.g. 32 byte step). This will also
> catch all drivers using the original struct member when compiled and
> make the memory usage a little bit happier.

Agreed that it'd be better to point to the dynamic allocated array.

And, we can basically drop the limit, too.
For example, the ioctl can store the size of the string in return
while filling up to the given length.  So, when user-space gives
snd_ctl_card_components.length = 0, it just stores the actual string
size to this length field.  Then user-space can allocate the needed
buffer and get the actual string.

Or, user-space allocates a large-enough size buffer, set to length,
and check the actually returned length, too.

Of course, we may (should) have some upper-limit internally for a
sanity check, but it doesn't have to be exposed as a part of ABI.


thanks,

Takashi
Re: [PATCH v2] ALSA: control: add ioctl to retrieve full card components
Posted by Maciej Strozek 2 weeks, 1 day ago
W dniu pią, 23.01.2026 o godzinie 13∶15 +0100, użytkownik Takashi Iwai
napisał:
> On Thu, 22 Jan 2026 14:30:04 +0100,
> Jaroslav Kysela wrote:
> > 
> > On 1/22/26 12:12, Maciej Strozek wrote:
> > > The fixed-size components field in SNDRV_CTL_IOCTL_CARD_INFO can
> > > be too
> > > small on systems with many audio devices.
> > > 
> > > Keep the existing struct snd_ctl_card_info ABI intact and add a
> > > new ioctl
> > > to retrieve the full components string.
> > > 
> > > When the legacy components field is truncated, append '>' to
> > > indicate
> > > that the full string is available via the new ioctl.
> > 
> > Thanks for the patch. Comments bellow.
> > 
> > > @@ -88,7 +89,8 @@ struct snd_card {
> > >    char irq_descr[32]; /* Interrupt description */
> > >    char mixername[80]; /* mixer name */
> > >    char components[128]; /* card components delimited with
> > > - space */
> > > + space, truncated to 127 chars */
> > > + char components_extended[SNDRV_CTL_COMPONENTS_LEN]; /* full
> > > card components string */
> > 
> > I would remove the original components[128] string and replace it
> > with dynamically allocated 'char *components_ptr' with 'unsigned
> > int
> > components_ptr_alloc' to store allocated size. Then the
> > snd_component_add() function may allocate (and resize) dynamically
> > the
> > components_ptr on demand (using e.g. 32 byte step). This will also
> > catch all drivers using the original struct member when compiled
> > and
> > make the memory usage a little bit happier.
> 
> Agreed that it'd be better to point to the dynamic allocated array.
> 
> And, we can basically drop the limit, too.
> For example, the ioctl can store the size of the string in return
> while filling up to the given length.  So, when user-space gives
> snd_ctl_card_components.length = 0, it just stores the actual string
> size to this length field.  Then user-space can allocate the needed
> buffer and get the actual string.
> 
> Or, user-space allocates a large-enough size buffer, set to length,
> and check the actually returned length, too.
> 
> Of course, we may (should) have some upper-limit internally for a
> sanity check, but it doesn't have to be exposed as a part of ABI.
> 
> 
> thanks,
> 
> Takashi

Thank you for your reviews, will prepare a v3 with dynamically
allocated array soon.
Do you suggest to simply error out if the internal upper-limit is
exceeded?
-- 
Regards,
Maciej
Re: [PATCH v2] ALSA: control: add ioctl to retrieve full card components
Posted by Takashi Iwai 2 weeks, 1 day ago
On Fri, 23 Jan 2026 15:42:13 +0100,
Maciej Strozek wrote:
> 
> W dniu pią, 23.01.2026 o godzinie 13∶15 +0100, użytkownik Takashi Iwai
> napisał:
> > On Thu, 22 Jan 2026 14:30:04 +0100,
> > Jaroslav Kysela wrote:
> > > 
> > > On 1/22/26 12:12, Maciej Strozek wrote:
> > > > The fixed-size components field in SNDRV_CTL_IOCTL_CARD_INFO can
> > > > be too
> > > > small on systems with many audio devices.
> > > > 
> > > > Keep the existing struct snd_ctl_card_info ABI intact and add a
> > > > new ioctl
> > > > to retrieve the full components string.
> > > > 
> > > > When the legacy components field is truncated, append '>' to
> > > > indicate
> > > > that the full string is available via the new ioctl.
> > > 
> > > Thanks for the patch. Comments bellow.
> > > 
> > > > @@ -88,7 +89,8 @@ struct snd_card {
> > > >    char irq_descr[32]; /* Interrupt description */
> > > >    char mixername[80]; /* mixer name */
> > > >    char components[128]; /* card components delimited with
> > > > - space */
> > > > + space, truncated to 127 chars */
> > > > + char components_extended[SNDRV_CTL_COMPONENTS_LEN]; /* full
> > > > card components string */
> > > 
> > > I would remove the original components[128] string and replace it
> > > with dynamically allocated 'char *components_ptr' with 'unsigned
> > > int
> > > components_ptr_alloc' to store allocated size. Then the
> > > snd_component_add() function may allocate (and resize) dynamically
> > > the
> > > components_ptr on demand (using e.g. 32 byte step). This will also
> > > catch all drivers using the original struct member when compiled
> > > and
> > > make the memory usage a little bit happier.
> > 
> > Agreed that it'd be better to point to the dynamic allocated array.
> > 
> > And, we can basically drop the limit, too.
> > For example, the ioctl can store the size of the string in return
> > while filling up to the given length.  So, when user-space gives
> > snd_ctl_card_components.length = 0, it just stores the actual string
> > size to this length field.  Then user-space can allocate the needed
> > buffer and get the actual string.
> > 
> > Or, user-space allocates a large-enough size buffer, set to length,
> > and check the actually returned length, too.
> > 
> > Of course, we may (should) have some upper-limit internally for a
> > sanity check, but it doesn't have to be exposed as a part of ABI.
> > 
> > 
> > thanks,
> > 
> > Takashi
> 
> Thank you for your reviews, will prepare a v3 with dynamically
> allocated array soon.
> Do you suggest to simply error out if the internal upper-limit is
> exceeded?

I guess yes, but note that the upper limit check would be rather for
adding component strings, not for the ioctl.
Basically we don't need a temporary kmalloc for ioctl, but we can just
copy the contents since the buffer length is given.


thanks,

Takashi