From: Maciej Strozek <mstrozek@opensource.cirrus.com>
To: Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org,
patches@opensource.cirrus.com, alsa-devel@alsa-project.org,
Maciej Strozek <mstrozek@opensource.cirrus.com>
Subject: [PATCH v4 2/2] ALSA: control: add ioctl to retrieve full card components
Date: Thu, 30 Apr 2026 12:03:01 +0100 [thread overview]
Message-ID: <20260430110302.3745917-2-mstrozek@opensource.cirrus.com> (raw)
In-Reply-To: <20260430110302.3745917-1-mstrozek@opensource.cirrus.com>
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 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 | 21 ++++++++++++++-
sound/core/control.c | 52 ++++++++++++++++++++++++++++++++++++-
sound/core/control_compat.c | 27 +++++++++++++++++++
sound/core/init.c | 34 ++++++++++++++++++++----
5 files changed, 129 insertions(+), 9 deletions(-)
diff --git a/include/sound/core.h b/include/sound/core.h
index 4093ec82a0a12..8a6a0e86da5e5 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -87,8 +87,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 d3ce75ba938a8..a6b695a798f07 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,24 @@ 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 {
+ unsigned int type; /* SND_CTL_CARD_BTYPE_* */
+ unsigned int data_allocated; /* size of @data buffer in bytes */
+ unsigned int data_len; /* in/out: actual data length in bytes */
+ unsigned char *data; /* user buffer */
+};
+
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 +1216,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 5e51857635e62..9a3772f1126d8 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -876,9 +876,13 @@ 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) {
info->card = card->number;
strscpy(info->id, card->id, sizeof(info->id));
@@ -886,13 +890,57 @@ 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, 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) {
+ data_len = strlen(card->components) + 1;
+
+ if (!info->data || info->data_allocated == 0)
+ break;
+
+ if (info->data_allocated < data_len)
+ return -ENOMEM;
+
+ if (copy_to_user(info->data, card->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)
{
@@ -1992,6 +2040,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 4ad571087ff59..9077482d67440 100644
--- a/sound/core/control_compat.c
+++ b/sound/core/control_compat.c
@@ -19,6 +19,30 @@ struct snd_ctl_elem_list32 {
unsigned char reserved[50];
} /* don't set packed attribute here */;
+struct snd_ctl_card_bytes32 {
+ u32 type;
+ u32 data_allocated;
+ u32 data_len;
+ u32 data;
+};
+
+static int snd_ctl_card_bytes_compat(struct snd_card *card,
+ struct snd_ctl_card_bytes32 __user *data32)
+{
+ struct snd_ctl_card_bytes data = {};
+ compat_caddr_t ptr;
+
+ /* type, data_allocated, data_len */
+ if (copy_from_user(&data, data32, 3 * sizeof(u32)))
+ return -EFAULT;
+ /* data */
+ if (get_user(ptr, &data32->data))
+ return -EFAULT;
+ data.data = compat_ptr(ptr);
+
+ return snd_ctl_card_bytes(card, &data, &data32->data_len);
+}
+
static int snd_ctl_elem_list_compat(struct snd_card *card,
struct snd_ctl_elem_list32 __user *data32)
{
@@ -426,6 +450,7 @@ enum {
SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32),
SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32),
SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32),
+ SNDRV_CTL_IOCTL_CARD_BYTES32 = _IOWR('U', 0x02, struct snd_ctl_card_bytes32),
#ifdef CONFIG_X86_X32_ABI
SNDRV_CTL_IOCTL_ELEM_READ_X32 = _IOWR('U', 0x12, struct snd_ctl_elem_value_x32),
SNDRV_CTL_IOCTL_ELEM_WRITE_X32 = _IOWR('U', 0x13, struct snd_ctl_elem_value_x32),
@@ -456,6 +481,8 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns
case SNDRV_CTL_IOCTL_TLV_WRITE:
case SNDRV_CTL_IOCTL_TLV_COMMAND:
return snd_ctl_ioctl(file, cmd, (unsigned long)argp);
+ case SNDRV_CTL_IOCTL_CARD_BYTES32:
+ return snd_ctl_card_bytes_compat(ctl->card, argp);
case SNDRV_CTL_IOCTL_ELEM_LIST32:
return snd_ctl_elem_list_compat(ctl->card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO32:
diff --git a/sound/core/init.c b/sound/core/init.c
index 593c05895e118..a683803bcf96a 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);
--
2.47.3
prev parent reply other threads:[~2026-04-30 11:03 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-30 11:03 [PATCH v4 1/2] ALSA: control: tidy up whitespaces Maciej Strozek
2026-04-30 11:03 ` Maciej Strozek [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=20260430110302.3745917-2-mstrozek@opensource.cirrus.com \
--to=mstrozek@opensource.cirrus.com \
--cc=alsa-devel@alsa-project.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--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