control: ucm: add ioctl to retrieve full card components

The fixed-size components field in SNDRV_CTL_IOCTL_CARD_INFO can be too
small on systems with many audio devices. The kernel [1] will provide a
new ioctl to read the full string while truncating the original in
card_info if it grows too big. Make sure the code falls back to original
if the new ioctl is not supported.

[1]: https://lore.kernel.org/all/20260122111249.67319-1-mstrozek@opensource.cirrus.com/
Signed-off-by: Maciej Strozek <mstrozek@opensource.cirrus.com>
This commit is contained in:
Maciej Strozek 2026-01-22 09:14:17 +00:00
parent 75ed5f05ba
commit d4cf5da490
17 changed files with 180 additions and 5 deletions

View file

@ -647,6 +647,9 @@ static int ctl_shm_cmd(client_t *client)
case SNDRV_CTL_IOCTL_CARD_INFO:
ctrl->result = snd_ctl_card_info(ctl, &ctrl->u.card_info);
break;
case SNDRV_CTL_IOCTL_CARD_COMPONENTS:
ctrl->result = snd_ctl_card_components(ctl, &ctrl->u.card_components);
break;
case SNDRV_CTL_IOCTL_ELEM_LIST:
{
size_t maxsize = CTL_SHM_DATA_MAXLEN;

View file

@ -123,6 +123,7 @@ typedef struct {
int device;
int subscribe_events;
snd_ctl_card_info_t card_info;
snd_ctl_card_components_t card_components;
snd_ctl_elem_list_t element_list;
snd_ctl_elem_info_t element_info;
snd_ctl_elem_value_t element_read;

View file

@ -84,6 +84,11 @@ typedef struct snd_aes_iec958 {
*/
typedef struct _snd_ctl_card_info snd_ctl_card_info_t;
/**
* \brief CTL card components container.
*/
typedef struct _snd_ctl_card_components snd_ctl_card_components_t;
/** CTL element identifier container */
typedef struct _snd_ctl_elem_id snd_ctl_elem_id_t;
@ -394,6 +399,7 @@ int snd_ctl_poll_descriptors(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int s
int snd_ctl_poll_descriptors_revents(snd_ctl_t *ctl, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);
int snd_ctl_subscribe_events(snd_ctl_t *ctl, int subscribe);
int snd_ctl_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info);
int snd_ctl_card_components(snd_ctl_t *ctl, snd_ctl_card_components_t *components);
int snd_ctl_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list);
int snd_ctl_elem_info(snd_ctl_t *ctl, snd_ctl_elem_info_t *info);
int snd_ctl_elem_read(snd_ctl_t *ctl, snd_ctl_elem_value_t *data);
@ -508,6 +514,22 @@ const char *snd_ctl_card_info_get_longname(const snd_ctl_card_info_t *obj);
const char *snd_ctl_card_info_get_mixername(const snd_ctl_card_info_t *obj);
const char *snd_ctl_card_info_get_components(const snd_ctl_card_info_t *obj);
size_t snd_ctl_card_components_sizeof(void);
/** \hideinitializer
* \brief allocate an invalid #snd_ctl_card_components_t using standard alloca
* \param ptr returned pointer
*/
#define snd_ctl_card_components_alloca(ptr) __snd_alloca(ptr, snd_ctl_card_components)
int snd_ctl_card_components_malloc(snd_ctl_card_components_t **ptr);
void snd_ctl_card_components_free(snd_ctl_card_components_t *obj);
void snd_ctl_card_components_clear(snd_ctl_card_components_t *obj);
void snd_ctl_card_components_copy(snd_ctl_card_components_t *dst, const snd_ctl_card_components_t *src);
int snd_ctl_card_components_get_card(const snd_ctl_card_components_t *obj);
unsigned int snd_ctl_card_components_get_length(const snd_ctl_card_components_t *obj);
const char *snd_ctl_card_components_get_string(const snd_ctl_card_components_t *obj);
size_t snd_ctl_elem_list_sizeof(void);
size_t snd_ctl_event_sizeof(void);
/** \hideinitializer
* \brief allocate an invalid #snd_ctl_event_t using standard alloca

View file

@ -102,6 +102,7 @@
#define _snd_pcm_status snd_pcm_status
#define _snd_ctl_card_info snd_ctl_card_info
#define _snd_ctl_card_components snd_ctl_card_components
#define _snd_ctl_elem_id snd_ctl_elem_id
#define _snd_ctl_elem_list snd_ctl_elem_list
#define _snd_ctl_elem_info snd_ctl_elem_info

View file

@ -75,6 +75,7 @@ typedef struct _sm_class_basic {
snd_ctl_t *ctl;
snd_hctl_t *hctl;
snd_ctl_card_info_t *info;
snd_ctl_card_components_t *card_components;
} sm_class_basic_t;
struct sm_elem_ops {

View file

@ -1044,7 +1044,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 */
@ -1058,6 +1058,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 ((snd_ctl_elem_type_t) 0) /* invalid */
#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((snd_ctl_elem_type_t) 1) /* boolean type */
@ -1184,6 +1196,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)

View file

@ -388,6 +388,19 @@ int snd_ctl_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info)
return ctl->ops->card_info(ctl, info);
}
/**
* \brief Get information about the card components.
* \param ctl The CTL handle.
* \param components The card components information is stored here.
* \return 0 on success, otherwise a negative error code.
*/
int snd_ctl_card_components(snd_ctl_t *ctl, snd_ctl_card_components_t *components)
{
assert(ctl && components);
return ctl->ops->card_components(ctl, components);
}
/**
* \brief Get a list of element identifiers
*
@ -2285,6 +2298,54 @@ const char *snd_ctl_card_info_get_components(const snd_ctl_card_info_t *obj)
return (const char *)obj->components;
}
size_t snd_ctl_card_components_sizeof()
{
return sizeof(snd_ctl_card_components_t);
}
int snd_ctl_card_components_malloc(snd_ctl_card_components_t **ptr)
{
assert(ptr);
*ptr = calloc(1, sizeof(snd_ctl_card_components_t));
if (!*ptr)
return -ENOMEM;
return 0;
}
void snd_ctl_card_components_free(snd_ctl_card_components_t *obj)
{
free(obj);
}
void snd_ctl_card_components_clear(snd_ctl_card_components_t *obj)
{
memset(obj, 0, sizeof(snd_ctl_card_components_t));
}
void snd_ctl_card_components_copy(snd_ctl_card_components_t *dst, const snd_ctl_card_components_t *src)
{
assert(dst && src);
*dst = *src;
}
int snd_ctl_card_components_get_card(const snd_ctl_card_components_t *obj)
{
assert(obj);
return obj->card;
}
unsigned int snd_ctl_card_components_get_length(const snd_ctl_card_components_t *obj)
{
assert(obj);
return obj->length;
}
const char *snd_ctl_card_components_get_string(const snd_ctl_card_components_t *obj)
{
assert(obj);
return (const char *)obj->components;
}
/**
* \brief get size of #snd_ctl_event_t
* \return size in bytes

View file

@ -89,6 +89,15 @@ static int snd_ctl_ext_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)
return 0;
}
static int snd_ctl_ext_card_components(snd_ctl_t *handle, snd_ctl_card_components_t *components)
{
snd_ctl_ext_t *ext = handle->private_data;
memset(components, 0, sizeof(*components));
components->card = ext->card_idx;
return 0;
}
static int snd_ctl_ext_elem_list(snd_ctl_t *handle, snd_ctl_elem_list_t *list)
{
snd_ctl_ext_t *ext = handle->private_data;
@ -469,6 +478,7 @@ static const snd_ctl_ops_t snd_ctl_ext_ops = {
.async = snd_ctl_ext_async,
.subscribe_events = snd_ctl_ext_subscribe_events,
.card_info = snd_ctl_ext_card_info,
.card_components = snd_ctl_ext_card_components,
.element_list = snd_ctl_ext_elem_list,
.element_info = snd_ctl_ext_elem_info,
.element_add = snd_ctl_ext_elem_add,

View file

@ -136,6 +136,16 @@ static int snd_ctl_hw_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)
return 0;
}
static int snd_ctl_hw_card_components(snd_ctl_t *handle, snd_ctl_card_components_t *components)
{
snd_ctl_hw_t *hw = handle->private_data;
if (hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 10))
return -ENXIO;
if (ioctl(hw->fd, SNDRV_CTL_IOCTL_CARD_COMPONENTS, components) < 0)
return -errno;
return 0;
}
static int snd_ctl_hw_elem_list(snd_ctl_t *handle, snd_ctl_elem_list_t *list)
{
snd_ctl_hw_t *hw = handle->private_data;
@ -389,6 +399,7 @@ static const snd_ctl_ops_t snd_ctl_hw_ops = {
.async = snd_ctl_hw_async,
.subscribe_events = snd_ctl_hw_subscribe_events,
.card_info = snd_ctl_hw_card_info,
.card_components = snd_ctl_hw_card_components,
.element_list = snd_ctl_hw_elem_list,
.element_info = snd_ctl_hw_elem_info,
.element_add = snd_ctl_hw_elem_add,

View file

@ -28,6 +28,7 @@ typedef struct _snd_ctl_ops {
int (*async)(snd_ctl_t *handle, int sig, pid_t pid);
int (*subscribe_events)(snd_ctl_t *handle, int subscribe);
int (*card_info)(snd_ctl_t *handle, snd_ctl_card_info_t *info);
int (*card_components)(snd_ctl_t *handle, snd_ctl_card_components_t *components);
int (*element_list)(snd_ctl_t *handle, snd_ctl_elem_list_t *list);
int (*element_info)(snd_ctl_t *handle, snd_ctl_elem_info_t *info);
int (*element_add)(snd_ctl_t *handle, snd_ctl_elem_info_t *info);

View file

@ -466,6 +466,12 @@ static int snd_ctl_remap_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info)
return snd_ctl_card_info(priv->child, info);
}
static int snd_ctl_remap_card_components(snd_ctl_t *ctl, snd_ctl_card_components_t *components)
{
snd_ctl_remap_t *priv = ctl->private_data;
return snd_ctl_card_components(priv->child, components);
}
static int snd_ctl_remap_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
{
snd_ctl_remap_t *priv = ctl->private_data;
@ -1185,6 +1191,7 @@ static const snd_ctl_ops_t snd_ctl_remap_ops = {
.async = snd_ctl_remap_async,
.subscribe_events = snd_ctl_remap_subscribe_events,
.card_info = snd_ctl_remap_card_info,
.card_components = snd_ctl_remap_card_components,
.element_list = snd_ctl_remap_elem_list,
.element_info = snd_ctl_remap_elem_info,
.element_read = snd_ctl_remap_elem_read,

View file

@ -150,6 +150,15 @@ static int snd_ctl_shm_card_info(snd_ctl_t *ctl, snd_ctl_card_info_t *info)
return err;
}
static int snd_ctl_shm_card_components(snd_ctl_t *ctl, snd_ctl_card_components_t *components)
{
snd_ctl_shm_t *shm = ctl->private_data;
volatile snd_ctl_shm_ctrl_t *ctrl = shm->ctrl;
ctrl->cmd = SNDRV_CTL_IOCTL_CARD_COMPONENTS;
ctrl->u.card_components = *components;
return snd_ctl_shm_action(ctl);
}
static int snd_ctl_shm_elem_list(snd_ctl_t *ctl, snd_ctl_elem_list_t *list)
{
snd_ctl_shm_t *shm = ctl->private_data;
@ -391,6 +400,7 @@ static const snd_ctl_ops_t snd_ctl_shm_ops = {
.async = snd_ctl_shm_async,
.subscribe_events = snd_ctl_shm_subscribe_events,
.card_info = snd_ctl_shm_card_info,
.card_components = snd_ctl_shm_card_components,
.element_list = snd_ctl_shm_elem_list,
.element_info = snd_ctl_shm_elem_info,
.element_read = snd_ctl_shm_elem_read,

View file

@ -48,6 +48,7 @@ typedef struct _class_priv {
snd_hctl_t *hctl;
int attach_flag;
snd_ctl_card_info_t *info;
snd_ctl_card_components_t *card_components;
void *dlhandle;
void *private_data;
void (*private_free)(snd_mixer_class_t *class);
@ -161,7 +162,12 @@ static int match(snd_mixer_class_t *class, const char *lib, const char *searchl)
if (searchl == NULL)
return try_open(class, lib);
components = snd_ctl_card_info_get_components(priv->info);
if (priv->card_components)
components = snd_ctl_card_components_get_string(priv->card_components);
else
components = snd_ctl_card_info_get_components(priv->info);
while (*components != '\0') {
if (!strncmp(components, searchl, strlen(searchl)))
return try_open(class, lib);
@ -248,6 +254,8 @@ static void private_free(snd_mixer_class_t *class)
priv->private_free(class);
if (priv->dlhandle)
snd_dlclose(priv->dlhandle);
if (priv->card_components)
snd_ctl_card_components_free(priv->card_components);
if (priv->info)
snd_ctl_card_info_free(priv->info);
if (priv->hctl) {
@ -335,8 +343,15 @@ int snd_mixer_simple_basic_register(snd_mixer_t *mixer,
err = snd_ctl_card_info(priv->ctl, priv->info);
if (err < 0)
goto __error;
if (err >= 0)
err = find_module(class, top);
err = snd_ctl_card_components_malloc(&priv->card_components);
if (err >= 0) {
err = snd_ctl_card_components(priv->ctl, priv->card_components);
if (err < 0) {
snd_ctl_card_components_free(priv->card_components);
priv->card_components = NULL;
}
}
err = find_module(class, top);
if (err >= 0)
err = snd_mixer_attach_hctl(mixer, priv->hctl);
if (err >= 0) {
@ -374,6 +389,7 @@ int snd_mixer_sbasic_info(const snd_mixer_class_t *class, sm_class_basic_t *info
info->ctl = priv->ctl;
info->hctl = priv->hctl;
info->info = priv->info;
info->card_components = priv->card_components;
return 0;
}

View file

@ -121,6 +121,7 @@ struct ctl_list {
struct list_head dev_list;
snd_ctl_t *ctl;
snd_ctl_card_info_t *ctl_info;
snd_ctl_card_components_t *ctl_components;
int slave;
int ucm_group;
};

View file

@ -156,6 +156,8 @@ static char *rval_card_components(snd_use_case_mgr_t *uc_mgr)
ctl_list = uc_mgr_get_master_ctl(uc_mgr);
if (ctl_list == NULL)
return NULL;
if (ctl_list->ctl_components)
return strdup(snd_ctl_card_components_get_string(ctl_list->ctl_components));
return strdup(snd_ctl_card_info_get_components(ctl_list->ctl_info));
}

View file

@ -152,6 +152,8 @@ static void uc_mgr_free_ctl(struct ctl_list *ctl_list)
free(ctl_dev->device);
free(ctl_dev);
}
if (ctl_list->ctl_components)
snd_ctl_card_components_free(ctl_list->ctl_components);
snd_ctl_card_info_free(ctl_list->ctl_info);
free(ctl_list);
}
@ -219,6 +221,14 @@ static int uc_mgr_ctl_add(snd_use_case_mgr_t *uc_mgr,
return -ENOMEM;
}
snd_ctl_card_info_copy(cl->ctl_info, info);
if (snd_ctl_card_components_malloc(&cl->ctl_components) >= 0) {
if (snd_ctl_card_components(ctl, cl->ctl_components) < 0) {
snd_ctl_card_components_free(cl->ctl_components);
cl->ctl_components = NULL;
}
} else {
cl->ctl_components = NULL;
}
cl->slave = slave;
*ctl_list = cl;
} else {

View file

@ -8,11 +8,13 @@ int main(void)
int idx, dev, err;
snd_ctl_t *handle;
snd_ctl_card_info_t *info;
snd_ctl_card_components_t *components;
snd_pcm_info_t *pcminfo;
snd_rawmidi_info_t *rawmidiinfo;
char str[128];
snd_ctl_card_info_alloca(&info);
snd_ctl_card_components_alloca(&components);
snd_pcm_info_alloca(&pcminfo);
snd_rawmidi_info_alloca(&rawmidiinfo);
@ -40,7 +42,10 @@ int main(void)
printf(" name - '%s'\n", snd_ctl_card_info_get_name(info));
printf(" longname - '%s'\n", snd_ctl_card_info_get_longname(info));
printf(" mixername - '%s'\n", snd_ctl_card_info_get_mixername(info));
printf(" components - '%s'\n", snd_ctl_card_info_get_components(info));
if (snd_ctl_card_components(handle, components) >= 0)
printf(" components - '%s'\n", snd_ctl_card_components_get_string(components));
else
printf(" components - '%s'\n", snd_ctl_card_info_get_components(info));
dev = -1;
while (1) {
snd_pcm_sync_id_t sync;