mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ALSA: control: 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. 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> Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://patch.msgid.link/20260720103505.1860399-2-mstrozek@opensource.cirrus.com
This commit is contained in:
parent
eaf46ee965
commit
86cac980c9
|
|
@ -7,6 +7,7 @@
|
||||||
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/rwsem.h>
|
||||||
#include <linux/wait.h>
|
#include <linux/wait.h>
|
||||||
#include <linux/nospec.h>
|
#include <linux/nospec.h>
|
||||||
#include <sound/asound.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);
|
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_register_ioctl(snd_kctl_ioctl_func_t fcn);
|
||||||
int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn);
|
int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn);
|
||||||
#ifdef CONFIG_COMPAT
|
#ifdef CONFIG_COMPAT
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,8 @@ struct snd_card {
|
||||||
char longname[80]; /* name of this soundcard */
|
char longname[80]; /* name of this soundcard */
|
||||||
char irq_descr[32]; /* Interrupt description */
|
char irq_descr[32]; /* Interrupt description */
|
||||||
char mixername[80]; /* mixer name */
|
char mixername[80]; /* mixer name */
|
||||||
char components[128]; /* card components delimited with
|
char *components; /* card components, space-delimited */
|
||||||
space */
|
unsigned int components_alloc_size; /* current allocation size of components */
|
||||||
struct module *module; /* top-level module */
|
struct module *module; /* top-level module */
|
||||||
|
|
||||||
void *private_data; /* private data for soundcard */
|
void *private_data; /* private data for soundcard */
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
struct snd_ctl_card_info {
|
||||||
int card; /* card number */
|
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..) */
|
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;
|
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_NONE ((__force snd_ctl_elem_type_t) 0) /* invalid */
|
||||||
#define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) /* boolean type */
|
#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_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_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_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)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ struct snd_kctl_ioctl {
|
||||||
snd_kctl_ioctl_func_t fioctl;
|
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 DECLARE_RWSEM(snd_ctl_layer_rwsem);
|
||||||
static LIST_HEAD(snd_control_ioctls);
|
static LIST_HEAD(snd_control_ioctls);
|
||||||
#ifdef CONFIG_COMPAT
|
#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) =
|
struct snd_ctl_card_info *info __free(kfree) =
|
||||||
kzalloc(sizeof(*info), GFP_KERNEL);
|
kzalloc(sizeof(*info), GFP_KERNEL);
|
||||||
|
ssize_t n;
|
||||||
|
|
||||||
if (! info)
|
if (! info)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
static_assert(sizeof(info->components) >= 2);
|
||||||
|
|
||||||
scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
|
scoped_guard(rwsem_read, &snd_ioctl_rwsem) {
|
||||||
|
const char *components = card->components;
|
||||||
|
|
||||||
|
if (!components)
|
||||||
|
components = "";
|
||||||
|
|
||||||
info->card = card->number;
|
info->card = card->number;
|
||||||
strscpy(info->id, card->id, sizeof(info->id));
|
strscpy(info->id, card->id, sizeof(info->id));
|
||||||
strscpy(info->driver, card->driver, sizeof(info->driver));
|
strscpy(info->driver, card->driver, sizeof(info->driver));
|
||||||
strscpy(info->name, card->shortname, sizeof(info->name));
|
strscpy(info->name, card->shortname, sizeof(info->name));
|
||||||
strscpy(info->longname, card->longname, sizeof(info->longname));
|
strscpy(info->longname, card->longname, sizeof(info->longname));
|
||||||
strscpy(info->mixername, card->mixername, sizeof(info->mixername));
|
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)))
|
if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info)))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
return 0;
|
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,
|
static int snd_ctl_elem_list(struct snd_card *card,
|
||||||
struct snd_ctl_elem_list *list)
|
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;
|
return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
|
||||||
case SNDRV_CTL_IOCTL_CARD_INFO:
|
case SNDRV_CTL_IOCTL_CARD_INFO:
|
||||||
return snd_ctl_card_info(card, ctl, cmd, argp);
|
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:
|
case SNDRV_CTL_IOCTL_ELEM_LIST:
|
||||||
return snd_ctl_elem_list_user(card, argp);
|
return snd_ctl_elem_list_user(card, argp);
|
||||||
case SNDRV_CTL_IOCTL_ELEM_INFO:
|
case SNDRV_CTL_IOCTL_ELEM_INFO:
|
||||||
|
|
|
||||||
|
|
@ -446,6 +446,7 @@ static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, uns
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case SNDRV_CTL_IOCTL_PVERSION:
|
case SNDRV_CTL_IOCTL_PVERSION:
|
||||||
case SNDRV_CTL_IOCTL_CARD_INFO:
|
case SNDRV_CTL_IOCTL_CARD_INFO:
|
||||||
|
case SNDRV_CTL_IOCTL_CARD_BYTES:
|
||||||
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
||||||
case SNDRV_CTL_IOCTL_POWER:
|
case SNDRV_CTL_IOCTL_POWER:
|
||||||
case SNDRV_CTL_IOCTL_POWER_STATE:
|
case SNDRV_CTL_IOCTL_POWER_STATE:
|
||||||
|
|
|
||||||
|
|
@ -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);
|
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE);
|
||||||
#endif
|
#endif
|
||||||
snd_device_free_all(card);
|
snd_device_free_all(card);
|
||||||
|
kfree(card->components);
|
||||||
|
card->components = NULL;
|
||||||
|
card->components_alloc_size = 0;
|
||||||
if (card->private_free)
|
if (card->private_free)
|
||||||
card->private_free(card);
|
card->private_free(card);
|
||||||
#ifdef CONFIG_SND_CTL_DEBUG
|
#ifdef CONFIG_SND_CTL_DEBUG
|
||||||
|
|
@ -1035,16 +1038,39 @@ int snd_component_add(struct snd_card *card, const char *component)
|
||||||
{
|
{
|
||||||
char *ptr;
|
char *ptr;
|
||||||
int len = strlen(component);
|
int len = strlen(component);
|
||||||
|
unsigned int cur_len, need_len;
|
||||||
|
|
||||||
ptr = strstr(card->components, component);
|
guard(rwsem_write)(&snd_ioctl_rwsem);
|
||||||
if (ptr != NULL) {
|
|
||||||
if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */
|
if (card->components) {
|
||||||
return 1;
|
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();
|
snd_BUG();
|
||||||
return -ENOMEM;
|
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')
|
if (card->components[0] != '\0')
|
||||||
strcat(card->components, " ");
|
strcat(card->components, " ");
|
||||||
strcat(card->components, component);
|
strcat(card->components, component);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user