[PATCH v6 2/2] ALSA: control: add ioctl to retrieve full card components
Maciej Strozek <[email protected]> Mon, 20 Jul 2026 11:35:05 +0100
| Newsgroups | org.alsa-project.alsa-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-sound |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> Suggested-by: Takashi Iwai <[email protected]> Signed-off-by: Maciej Strozek <[email protected]> --- Changes for v6: - take the snd_ioctl_rwsem write lock in snd_component_add() 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/control.h | 3 ++ include/sound/core.h | 4 +-- include/uapi/sound/asound.h | 22 ++++++++++++- sound/core/control.c | 64 +++++++++++++++++++++++++++++++++++-- sound/core/control_compat.c | 1 + sound/core/init.c | 36 ++++++++++++++++++--- 6 files changed, 120 insertions(+), 10 deletions(-) diff --git a/include/sound/control.h b/include/sound/control.h index e07f6b960641..909db0d0485d 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -7,6 +7,7 @@ * Copyright (c) by Jaroslav Kysela <[email protected]> */ +#include <linux/rwsem.h> #include <linux/wait.h> #include <linux/nospec.h> #include <sound/asound.h> @@ -167,6 +168,8 @@ snd_ctl_find_id_mixer(struct snd_card *card, const char *name) int snd_ctl_create(struct snd_card *card); +extern struct rw_semaphore snd_ioctl_rwsem; + int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn); int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn); #ifdef CONFIG_COMPAT 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 1116a40d11ae..78ce7bc936d2 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -38,7 +38,7 @@ struct snd_kctl_ioctl { snd_kctl_ioctl_func_t fioctl; }; -static DECLARE_RWSEM(snd_ioctl_rwsem); +DECLARE_RWSEM(snd_ioctl_rwsem); static DECLARE_RWSEM(snd_ctl_layer_rwsem); static LIST_HEAD(snd_control_ioctls); #ifdef CONFIG_COMPAT @@ -872,23 +872,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) { @@ -1986,6 +2044,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 0372756048cd..19ec68db561b 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -589,6 +589,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 @@ -1035,16 +1038,39 @@ 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; + guard(rwsem_write)(&snd_ioctl_rwsem); + + 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); -- 2.48.1