ALSA: control: add support for ENUMERATED user space controls

Handling of user control elements was implemented for all types except
ENUMERATED.  This type will be needed for the device-specific mixers of
upcoming FireWire drivers.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Clemens Ladisch 2011-10-07 22:38:59 +02:00 committed by Takashi Iwai
parent f92766bc89
commit 8d448162bd
3 changed files with 83 additions and 5 deletions

View File

@ -706,7 +706,7 @@ struct snd_timer_tread {
* * * *
****************************************************************************/ ****************************************************************************/
#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 6) #define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 7)
struct snd_ctl_card_info { struct snd_ctl_card_info {
int card; /* card number */ int card; /* card number */
@ -803,6 +803,8 @@ struct snd_ctl_elem_info {
unsigned int items; /* R: number of items */ unsigned int items; /* R: number of items */
unsigned int item; /* W: item number */ unsigned int item; /* W: item number */
char name[64]; /* R: value name */ char name[64]; /* R: value name */
__u64 names_ptr; /* W: names list (ELEM_ADD only) */
unsigned int names_length;
} enumerated; } enumerated;
unsigned char reserved[128]; unsigned char reserved[128];
} value; } value;

View File

@ -989,7 +989,6 @@ struct user_element {
void *tlv_data; /* TLV data */ void *tlv_data; /* TLV data */
unsigned long tlv_data_size; /* TLV data size */ unsigned long tlv_data_size; /* TLV data size */
void *priv_data; /* private data (like strings for enumerated type) */ void *priv_data; /* private data (like strings for enumerated type) */
unsigned long priv_data_size; /* size of private data in bytes */
}; };
static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
@ -1001,6 +1000,28 @@ static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
return 0; return 0;
} }
static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct user_element *ue = kcontrol->private_data;
const char *names;
unsigned int item;
item = uinfo->value.enumerated.item;
*uinfo = ue->info;
item = min(item, uinfo->value.enumerated.items - 1);
uinfo->value.enumerated.item = item;
names = ue->priv_data;
for (; item > 0; --item)
names += strlen(names) + 1;
strcpy(uinfo->value.enumerated.name, names);
return 0;
}
static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
@ -1055,11 +1076,46 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,
return change; return change;
} }
static int snd_ctl_elem_init_enum_names(struct user_element *ue)
{
char *names, *p;
size_t buf_len, name_len;
unsigned int i;
if (ue->info.value.enumerated.names_length > 64 * 1024)
return -EINVAL;
names = memdup_user(
(const void __user *)ue->info.value.enumerated.names_ptr,
ue->info.value.enumerated.names_length);
if (IS_ERR(names))
return PTR_ERR(names);
/* check that there are enough valid names */
buf_len = ue->info.value.enumerated.names_length;
p = names;
for (i = 0; i < ue->info.value.enumerated.items; ++i) {
name_len = strnlen(p, buf_len);
if (name_len == 0 || name_len >= 64 || name_len == buf_len) {
kfree(names);
return -EINVAL;
}
p += name_len + 1;
buf_len -= name_len + 1;
}
ue->priv_data = names;
ue->info.value.enumerated.names_ptr = 0;
return 0;
}
static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
{ {
struct user_element *ue = kcontrol->private_data; struct user_element *ue = kcontrol->private_data;
if (ue->tlv_data)
kfree(ue->tlv_data); kfree(ue->tlv_data);
kfree(ue->priv_data);
kfree(ue); kfree(ue);
} }
@ -1101,7 +1157,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
memcpy(&kctl.id, &info->id, sizeof(info->id)); memcpy(&kctl.id, &info->id, sizeof(info->id));
kctl.count = info->owner ? info->owner : 1; kctl.count = info->owner ? info->owner : 1;
access |= SNDRV_CTL_ELEM_ACCESS_USER; access |= SNDRV_CTL_ELEM_ACCESS_USER;
kctl.info = snd_ctl_elem_user_info; if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
kctl.info = snd_ctl_elem_user_enum_info;
else
kctl.info = snd_ctl_elem_user_info;
if (access & SNDRV_CTL_ELEM_ACCESS_READ) if (access & SNDRV_CTL_ELEM_ACCESS_READ)
kctl.get = snd_ctl_elem_user_get; kctl.get = snd_ctl_elem_user_get;
if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) if (access & SNDRV_CTL_ELEM_ACCESS_WRITE)
@ -1122,6 +1181,11 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
if (info->count > 64) if (info->count > 64)
return -EINVAL; return -EINVAL;
break; break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
private_size = sizeof(unsigned int);
if (info->count > 128 || info->value.enumerated.items == 0)
return -EINVAL;
break;
case SNDRV_CTL_ELEM_TYPE_BYTES: case SNDRV_CTL_ELEM_TYPE_BYTES:
private_size = sizeof(unsigned char); private_size = sizeof(unsigned char);
if (info->count > 512) if (info->count > 512)
@ -1143,9 +1207,17 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
ue->info.access = 0; ue->info.access = 0;
ue->elem_data = (char *)ue + sizeof(*ue); ue->elem_data = (char *)ue + sizeof(*ue);
ue->elem_data_size = private_size; ue->elem_data_size = private_size;
if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
err = snd_ctl_elem_init_enum_names(ue);
if (err < 0) {
kfree(ue);
return err;
}
}
kctl.private_free = snd_ctl_elem_user_free; kctl.private_free = snd_ctl_elem_user_free;
_kctl = snd_ctl_new(&kctl, access); _kctl = snd_ctl_new(&kctl, access);
if (_kctl == NULL) { if (_kctl == NULL) {
kfree(ue->priv_data);
kfree(ue); kfree(ue);
return -ENOMEM; return -ENOMEM;
} }

View File

@ -83,6 +83,8 @@ struct snd_ctl_elem_info32 {
u32 items; u32 items;
u32 item; u32 item;
char name[64]; char name[64];
u64 names_ptr;
u32 names_length;
} enumerated; } enumerated;
unsigned char reserved[128]; unsigned char reserved[128];
} value; } value;
@ -372,6 +374,8 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file,
&data32->value.enumerated, &data32->value.enumerated,
sizeof(data->value.enumerated))) sizeof(data->value.enumerated)))
goto error; goto error;
data->value.enumerated.names_ptr =
(uintptr_t)compat_ptr(data->value.enumerated.names_ptr);
break; break;
default: default:
break; break;