From: Takashi Iwai <tiwai@suse.de>
To: Maciej Strozek <mstrozek@opensource.cirrus.com>
Cc: Takashi Iwai <tiwai@suse.com>, Jaroslav Kysela <perex@perex.cz>,
linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
alsa-devel@alsa-project.org, patches@opensource.cirrus.com
Subject: Re: [PATCH v5 2/2] ALSA: control: add ioctl to retrieve full card components
Date: Wed, 15 Jul 2026 16:51:58 +0200 [thread overview]
Message-ID: <87mrvswbjl.wl-tiwai@suse.de> (raw)
In-Reply-To: <20260706141457.51736-2-mstrozek@opensource.cirrus.com>
On Mon, 06 Jul 2026 16:14:57 +0200,
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 SNDRV_CTL_IOCTL_CARD_BYTES that carries a variable-length payload
> selected by a type discriminator. The first defined type
> SND_CTL_CARD_BTYPE_COMPONENTS returns the full components string. The
> ioctl is designed to be reused for other variable-length card payloads
> in the future.
>
> The user-space caller may set data_allocated == 0 (or data == NULL) to
> query the required length; otherwise the kernel copies the payload into
> the user buffer and writes back the actual length in data_len.
>
> When the legacy components field in struct snd_ctl_card_info is
> truncated, '>' is written just before the NUL terminator to signal to
> user-space that the full string is available via the new ioctl.
>
> card->components is now dynamically allocated and grown in 32 byte
> increments via krealloc(), capped at 512 bytes.
>
> Link: https://github.com/alsa-project/alsa-lib/pull/494
> 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 v5:
> - use __u64 and u64_to_user_ptr() instead of 32bit compat layer
> - handle a NULL card->components
> Changes for v4:
> - replaced snd_ctl_card_components with snd_ctl_card_bytes for possible
> future extensions
> - support query mode (data_allocated == 0 or data == NULL)
> - added compat 32-bit
> - renamed card->components_ptr / components_ptr_alloc_size to
> card->components / components_alloc_size
> Changes for v3:
> - change components field to a dynamic array resizable in 32 byte
> increments
> - removed SNDRV_CTL_COMPONENTS_LEN define
> - sanity check if 'components' requests more than 512 bytes
> - added a commit to clean up trailing whitespaces
> - alsa-utils link no longer needed
> 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 | 22 ++++++++++++-
> sound/core/control.c | 62 ++++++++++++++++++++++++++++++++++++-
> sound/core/control_compat.c | 1 +
> sound/core/init.c | 34 +++++++++++++++++---
> 5 files changed, 114 insertions(+), 9 deletions(-)
>
> diff --git a/include/sound/core.h b/include/sound/core.h
> index 404785b7d885..2ca24ac7e37f 100644
> --- a/include/sound/core.h
> +++ b/include/sound/core.h
> @@ -108,8 +108,8 @@ struct snd_card {
> char longname[80]; /* name of this soundcard */
> char irq_descr[32]; /* Interrupt description */
> char mixername[80]; /* mixer name */
> - char components[128]; /* card components delimited with
> - space */
> + char *components; /* card components, space-delimited */
> + unsigned int components_alloc_size; /* current allocation size of components */
> 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..500599213f93 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,25 @@ 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_BYTES with type SND_CTL_CARD_BTYPE_COMPONENTS
> + * to retrieve the full string.
> + */
> +
> +/* Type values for struct snd_ctl_card_bytes::type */
> +enum {
> + SND_CTL_CARD_BTYPE_COMPONENTS = 1, /* full card components string */
> +};
> +
> +struct snd_ctl_card_bytes {
> + __u32 type; /* SND_CTL_CARD_BTYPE_* */
> + __u32 data_allocated; /* size of @data buffer in bytes */
> + __u32 data_len; /* in/out: actual data length in bytes */
> + __u32 reserved; /* explicit pad */
> + __u64 data; /* user buffer (pointer stored as __u64) */
> +};
> +
> 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 +1217,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_BYTES _IOWR('U', 0x02, struct snd_ctl_card_bytes)
> #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 7a8dc506221e..5e9f68c96f4b 100644
> --- a/sound/core/control.c
> +++ b/sound/core/control.c
> @@ -876,23 +876,81 @@ static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl,
> {
> struct snd_ctl_card_info *info __free(kfree) =
> kzalloc(sizeof(*info), GFP_KERNEL);
> + ssize_t n;
>
> if (! info)
> return -ENOMEM;
> +
> + static_assert(sizeof(info->components) >= 2);
> +
> scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
> + const char *components = card->components;
> +
> + if (!components)
> + components = "";
> +
> info->card = card->number;
> strscpy(info->id, card->id, sizeof(info->id));
> strscpy(info->driver, card->driver, sizeof(info->driver));
> 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, components, sizeof(info->components));
> + if (n < 0) // mark the truncation with '>' before NULL terminator
> + info->components[sizeof(info->components) - 2] = '>';
> }
> if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info)))
> return -EFAULT;
> return 0;
> }
>
> +static int snd_ctl_card_bytes(struct snd_card *card,
> + struct snd_ctl_card_bytes *info,
> + unsigned int __user *data_len_out)
> +{
> + unsigned int data_len;
> +
> + switch (info->type) {
> + case SND_CTL_CARD_BTYPE_COMPONENTS:
> + scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
> + const char *components = card->components;
> +
> + if (!components)
> + components = "";
> +
> + data_len = strlen(components) + 1;
> +
> + if (!info->data || info->data_allocated == 0)
> + break;
> +
> + if (info->data_allocated < data_len)
> + return -ENOMEM;
> +
> + if (copy_to_user(u64_to_user_ptr(info->data), components, data_len))
> + return -EFAULT;
> + }
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (put_user(data_len, data_len_out))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int snd_ctl_card_bytes_user(struct snd_card *card,
> + struct snd_ctl_card_bytes __user *_info)
> +{
> + struct snd_ctl_card_bytes info;
> +
> + if (copy_from_user(&info, _info, sizeof(info)))
> + return -EFAULT;
> +
> + return snd_ctl_card_bytes(card, &info, &_info->data_len);
> +}
> +
> static int snd_ctl_elem_list(struct snd_card *card,
> struct snd_ctl_elem_list *list)
> {
> @@ -1990,6 +2048,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_BYTES:
> + return snd_ctl_card_bytes_user(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 4ad571087ff5..f14d9f5e94be 100644
> --- a/sound/core/control_compat.c
> +++ b/sound/core/control_compat.c
> @@ -446,6 +446,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_BYTES:
> 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 1ff340ee6884..15a8209469c7 100644
> --- a/sound/core/init.c
> +++ b/sound/core/init.c
> @@ -590,6 +590,9 @@ static int snd_card_do_free(struct snd_card *card)
> snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
> #endif
> snd_device_free_all(card);
> + kfree(card->components);
> + card->components = NULL;
> + card->components_alloc_size = 0;
> if (card->private_free)
> card->private_free(card);
> #ifdef CONFIG_SND_CTL_DEBUG
> @@ -1036,16 +1039,37 @@ int snd_component_add(struct snd_card *card, const char *component)
> {
> char *ptr;
> int len = strlen(component);
> + unsigned int cur_len, need_len;
>
> - ptr = strstr(card->components, component);
> - if (ptr != NULL) {
> - if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
> - return 1;
> + if (card->components) {
> + ptr = strstr(card->components, component);
> + if (ptr) {
> + if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
> + return 1;
> + }
> + cur_len = strlen(card->components) + 1;
> + } else {
> + cur_len = 0;
> }
> - if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
> +
> + need_len = cur_len + len + 1;
> + if (need_len > 512) {
> snd_BUG();
> return -ENOMEM;
> }
> +
> + if (need_len > card->components_alloc_size) {
> + unsigned int new_alloc = roundup(need_len, 32);
> +
> + ptr = krealloc(card->components, new_alloc, GFP_KERNEL);
> + if (!ptr)
> + return -ENOMEM;
> + if (!card->components)
> + ptr[0] = '\0';
> + card->components = ptr;
> + card->components_alloc_size = new_alloc;
> + }
> +
> if (card->components[0] != '\0')
> strcat(card->components, " ");
> strcat(card->components, component);
I believe we need to protect snd_component_add() against the racy
ioctl? Otherwise the existing guard in your patch makes no sense.
thanks,
Takashi
prev parent reply other threads:[~2026-07-15 14:52 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 14:14 [PATCH v5 1/2] ALSA: control: tidy up whitespaces Maciej Strozek
2026-07-06 14:14 ` [PATCH v5 2/2] ALSA: control: add ioctl to retrieve full card components Maciej Strozek
2026-07-15 14:51 ` Takashi Iwai [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87mrvswbjl.wl-tiwai@suse.de \
--to=tiwai@suse.de \
--cc=alsa-devel@alsa-project.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=mstrozek@opensource.cirrus.com \
--cc=patches@opensource.cirrus.com \
--cc=perex@perex.cz \
--cc=tiwai@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox