summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClemens Ladisch <clemens@ladisch.de>2011-10-07 22:38:59 +0200
committerTakashi Iwai <tiwai@suse.de>2011-10-09 09:09:11 +0200
commit8d448162bda5ae3b5ecb26fe50c8fbbeae99faa4 (patch)
tree5a325ffbc56a6260d933c1997b5f36ba1f3ab165
parentf92766bc8948f978a838a5607bea95804c8dfdfe (diff)
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>
-rw-r--r--include/sound/asound.h4
-rw-r--r--sound/core/control.c80
-rw-r--r--sound/core/control_compat.c4
3 files changed, 83 insertions, 5 deletions
diff --git a/include/sound/asound.h b/include/sound/asound.h
index 5d6074faa279..a2e4ff5ba9e9 100644
--- a/include/sound/asound.h
+++ b/include/sound/asound.h
@@ -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 {
int card; /* card number */
@@ -803,6 +803,8 @@ struct snd_ctl_elem_info {
unsigned int items; /* R: number of items */
unsigned int item; /* W: item number */
char name[64]; /* R: value name */
+ __u64 names_ptr; /* W: names list (ELEM_ADD only) */
+ unsigned int names_length;
} enumerated;
unsigned char reserved[128];
} value;
diff --git a/sound/core/control.c b/sound/core/control.c
index dc2a44048c85..978fe1a8e9f0 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -989,7 +989,6 @@ struct user_element {
void *tlv_data; /* TLV data */
unsigned long tlv_data_size; /* TLV data size */
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,
@@ -1001,6 +1000,28 @@ static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
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,
struct snd_ctl_elem_value *ucontrol)
{
@@ -1055,11 +1076,46 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,
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)
{
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);
}
@@ -1101,7 +1157,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
memcpy(&kctl.id, &info->id, sizeof(info->id));
kctl.count = info->owner ? info->owner : 1;
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)
kctl.get = snd_ctl_elem_user_get;
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)
return -EINVAL;
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:
private_size = sizeof(unsigned char);
if (info->count > 512)
@@ -1143,9 +1207,17 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
ue->info.access = 0;
ue->elem_data = (char *)ue + sizeof(*ue);
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 = snd_ctl_new(&kctl, access);
if (_kctl == NULL) {
+ kfree(ue->priv_data);
kfree(ue);
return -ENOMEM;
}
diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c
index 426874429a5e..2bb95a7a8809 100644
--- a/sound/core/control_compat.c
+++ b/sound/core/control_compat.c
@@ -83,6 +83,8 @@ struct snd_ctl_elem_info32 {
u32 items;
u32 item;
char name[64];
+ u64 names_ptr;
+ u32 names_length;
} enumerated;
unsigned char reserved[128];
} value;
@@ -372,6 +374,8 @@ static int snd_ctl_elem_add_compat(struct snd_ctl_file *file,
&data32->value.enumerated,
sizeof(data->value.enumerated)))
goto error;
+ data->value.enumerated.names_ptr =
+ (uintptr_t)compat_ptr(data->value.enumerated.names_ptr);
break;
default:
break;