ALSA: control: add support for ENUMERATED user space controls
authorClemens Ladisch <clemens@ladisch.de>
Fri, 7 Oct 2011 20:38:59 +0000 (22:38 +0200)
committerTakashi Iwai <tiwai@suse.de>
Sun, 9 Oct 2011 07:09:11 +0000 (09:09 +0200)
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>
include/sound/asound.h
sound/core/control.c
sound/core/control_compat.c

index 5d6074faa279adabd3f45c7adf792fa578913ebb..a2e4ff5ba9e9f12be58de88d38fdf0645289ca4b 100644 (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 {
        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;
index dc2a44048c850e988126cf384ce0c26d6d10e7d0..978fe1a8e9f0877ece853c788a5ed371f9bdfa77 100644 (file)
@@ -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;
        }
index 426874429a5e088cf50414f6ab1f76dd7f8d1fd2..2bb95a7a8809fed3a042e8796b5431ce2afbee46 100644 (file)
@@ -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;