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 alsa-lib can read the full
string if the original is truncated.

[1] https://lore.kernel.org/all/20260303145815.9930-1-mstrozek@opensource.cirrus.com/

Signed-off-by: Maciej Strozek <mstrozek@opensource.cirrus.com>
This commit is contained in:
Maciej Strozek 2026-02-19 13:46:20 +00:00
parent 75ed5f05ba
commit 22c3701630
4 changed files with 61 additions and 6 deletions

View file

@ -101,7 +101,6 @@
#define _snd_pcm_sw_params snd_pcm_sw_params #define _snd_pcm_sw_params snd_pcm_sw_params
#define _snd_pcm_status snd_pcm_status #define _snd_pcm_status snd_pcm_status
#define _snd_ctl_card_info snd_ctl_card_info
#define _snd_ctl_elem_id snd_ctl_elem_id #define _snd_ctl_elem_id snd_ctl_elem_id
#define _snd_ctl_elem_list snd_ctl_elem_list #define _snd_ctl_elem_list snd_ctl_elem_list
#define _snd_ctl_elem_info snd_ctl_elem_info #define _snd_ctl_elem_info snd_ctl_elem_info
@ -226,6 +225,20 @@
#include "seq_midi_event.h" #include "seq_midi_event.h"
#include "list.h" #include "list.h"
/* V2 structure - increased components string from 128 bytes to 512 bytes */
struct _snd_ctl_card_info {
int card; /* card number */
int pad; /* reserved for future (was type) */
unsigned char id[16]; /* ID of card (user selectable) */
unsigned char driver[16]; /* Driver name */
unsigned char name[32]; /* Short name of soundcard */
unsigned char longname[80]; /* name + info text about soundcard */
unsigned char reserved_[16]; /* reserved for future (was ID of mixer) */
unsigned char mixername[80]; /* visual mixer identification */
char components[512];
};
struct _snd_async_handler { struct _snd_async_handler {
enum { enum {
SND_ASYNC_HANDLER_GENERIC, SND_ASYNC_HANDLER_GENERIC,

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 { struct snd_ctl_card_info {
int card; /* card number */ int card; /* card number */
@ -1058,6 +1058,17 @@ struct snd_ctl_card_info {
unsigned char components[128]; /* card components / fine identification, delimited with one space (AC97 etc..) */ 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.
*
*/
struct snd_ctl_card_components {
int card; /* card number */
unsigned int length; /* returned length of components string for ioctl query */
unsigned char *components; /* user buffer for components string */
};
typedef int __bitwise snd_ctl_elem_type_t; 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_NONE ((snd_ctl_elem_type_t) 0) /* invalid */
#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((snd_ctl_elem_type_t) 1) /* boolean type */ #define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((snd_ctl_elem_type_t) 1) /* boolean type */
@ -1184,6 +1195,7 @@ struct snd_ctl_tlv {
#define SNDRV_CTL_IOCTL_PVERSION _IOR('U', 0x00, int) #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_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_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_INFO _IOWR('U', 0x11, struct snd_ctl_elem_info)
#define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value) #define SNDRV_CTL_IOCTL_ELEM_READ _IOWR('U', 0x12, struct snd_ctl_elem_value)

View file

@ -42,17 +42,17 @@
static int snd_card_load2(const char *control) static int snd_card_load2(const char *control)
{ {
int open_dev; int open_dev;
snd_ctl_card_info_t info; struct snd_ctl_card_info kinfo;
open_dev = snd_open_device(control, O_RDONLY); open_dev = snd_open_device(control, O_RDONLY);
if (open_dev >= 0) { if (open_dev >= 0) {
if (ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO, &info) < 0) { if (ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO, &kinfo) < 0) {
int err = -errno; int err = -errno;
close(open_dev); close(open_dev);
return err; return err;
} }
close(open_dev); close(open_dev);
return info.card; return kinfo.card;
} else { } else {
return -errno; return -errno;
} }

View file

@ -129,10 +129,40 @@ static int snd_ctl_hw_subscribe_events(snd_ctl_t *handle, int subscribe)
static int snd_ctl_hw_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info) static int snd_ctl_hw_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)
{ {
snd_ctl_hw_t *hw = handle->private_data; snd_ctl_hw_t *hw = handle->private_data;
if (ioctl(hw->fd, SNDRV_CTL_IOCTL_CARD_INFO, info) < 0) { struct snd_ctl_card_info kinfo;
struct snd_ctl_card_components comp;
if (ioctl(hw->fd, SNDRV_CTL_IOCTL_CARD_INFO, &kinfo) < 0) {
snd_errornum(CONTROL, "SNDRV_CTL_IOCTL_CARD_INFO failed"); snd_errornum(CONTROL, "SNDRV_CTL_IOCTL_CARD_INFO failed");
return -errno; return -errno;
} }
snd_ctl_card_info_clear(info);
info->card = kinfo.card;
info->pad = kinfo.pad;
snd_strlcpy((char *)info->id, (char *)kinfo.id, sizeof(info->id));
snd_strlcpy((char *)info->driver, (char *)kinfo.driver, sizeof(info->driver));
snd_strlcpy((char *)info->name, (char *)kinfo.name, sizeof(info->name));
snd_strlcpy((char *)info->longname, (char *)kinfo.longname, sizeof(info->longname));
snd_strlcpy((char *)info->reserved_, (char *)kinfo.reserved_, sizeof(info->reserved_));
snd_strlcpy((char *)info->mixername, (char *)kinfo.mixername, sizeof(info->mixername));
if (strnlen((char *)kinfo.components, sizeof(kinfo.components)) == sizeof(kinfo.components) - 1 &&
kinfo.components[sizeof(kinfo.components) - 2] == '>') {
comp.components = (unsigned char *)info->components;
comp.length = sizeof(info->components);
if (ioctl(hw->fd, SNDRV_CTL_IOCTL_CARD_COMPONENTS, &comp) < 0) {
snd_errornum(CONTROL, "SNDRV_CTL_IOCTL_CARD_COMPONENTS failed");
return -errno;
}
if (comp.length != strnlen((char *)comp.components, sizeof(info->components)) + 1 ) {
snd_error(CONTROL, "SNDRV_CTL_IOCTL_CARD_COMPONENTS unexpected length %u, should be %u",
comp.length, strnlen((char *)comp.components, sizeof(info->components)) + 1);
return -errno;
}
} else {
snd_strlcpy(info->components, (char *)kinfo.components, sizeof(info->components));
}
return 0; return 0;
} }