|
@@ -192,36 +192,41 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask,
|
|
EXPORT_SYMBOL(snd_ctl_notify);
|
|
EXPORT_SYMBOL(snd_ctl_notify);
|
|
|
|
|
|
/**
|
|
/**
|
|
- * snd_ctl_new - create a control instance from the template
|
|
|
|
- * @control: the control template
|
|
|
|
- * @access: the default control access
|
|
|
|
|
|
+ * snd_ctl_new - create a new control instance with some elements
|
|
|
|
+ * @kctl: the pointer to store new control instance
|
|
|
|
+ * @count: the number of elements in this control
|
|
|
|
+ * @access: the default access flags for elements in this control
|
|
|
|
+ * @file: given when locking these elements
|
|
*
|
|
*
|
|
- * Allocates a new struct snd_kcontrol instance and copies the given template
|
|
|
|
- * to the new instance. It does not copy volatile data (access).
|
|
|
|
|
|
+ * Allocates a memory object for a new control instance. The instance has
|
|
|
|
+ * elements as many as the given number (@count). Each element has given
|
|
|
|
+ * access permissions (@access). Each element is locked when @file is given.
|
|
*
|
|
*
|
|
- * Return: The pointer of the new instance, or %NULL on failure.
|
|
|
|
|
|
+ * Return: 0 on success, error code on failure
|
|
*/
|
|
*/
|
|
-static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,
|
|
|
|
- unsigned int access)
|
|
|
|
|
|
+static int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count,
|
|
|
|
+ unsigned int access, struct snd_ctl_file *file)
|
|
{
|
|
{
|
|
- struct snd_kcontrol *kctl;
|
|
|
|
|
|
+ unsigned int size;
|
|
unsigned int idx;
|
|
unsigned int idx;
|
|
|
|
|
|
- if (snd_BUG_ON(!control || !control->count))
|
|
|
|
- return NULL;
|
|
|
|
|
|
+ if (count == 0 || count > MAX_CONTROL_COUNT)
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
|
- if (control->count > MAX_CONTROL_COUNT)
|
|
|
|
- return NULL;
|
|
|
|
|
|
+ size = sizeof(struct snd_kcontrol);
|
|
|
|
+ size += sizeof(struct snd_kcontrol_volatile) * count;
|
|
|
|
|
|
- kctl = kzalloc(sizeof(*kctl) + sizeof(struct snd_kcontrol_volatile) * control->count, GFP_KERNEL);
|
|
|
|
- if (kctl == NULL) {
|
|
|
|
- pr_err("ALSA: Cannot allocate control instance\n");
|
|
|
|
- return NULL;
|
|
|
|
|
|
+ *kctl = kzalloc(size, GFP_KERNEL);
|
|
|
|
+ if (!*kctl)
|
|
|
|
+ return -ENOMEM;
|
|
|
|
+
|
|
|
|
+ for (idx = 0; idx < count; idx++) {
|
|
|
|
+ (*kctl)->vd[idx].access = access;
|
|
|
|
+ (*kctl)->vd[idx].owner = file;
|
|
}
|
|
}
|
|
- *kctl = *control;
|
|
|
|
- for (idx = 0; idx < kctl->count; idx++)
|
|
|
|
- kctl->vd[idx].access = access;
|
|
|
|
- return kctl;
|
|
|
|
|
|
+ (*kctl)->count = count;
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -238,37 +243,53 @@ static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,
|
|
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
|
|
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
|
|
void *private_data)
|
|
void *private_data)
|
|
{
|
|
{
|
|
- struct snd_kcontrol kctl;
|
|
|
|
|
|
+ struct snd_kcontrol *kctl;
|
|
|
|
+ unsigned int count;
|
|
unsigned int access;
|
|
unsigned int access;
|
|
|
|
+ int err;
|
|
|
|
|
|
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
|
|
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
|
|
return NULL;
|
|
return NULL;
|
|
- memset(&kctl, 0, sizeof(kctl));
|
|
|
|
- kctl.id.iface = ncontrol->iface;
|
|
|
|
- kctl.id.device = ncontrol->device;
|
|
|
|
- kctl.id.subdevice = ncontrol->subdevice;
|
|
|
|
|
|
+
|
|
|
|
+ count = ncontrol->count;
|
|
|
|
+ if (count == 0)
|
|
|
|
+ count = 1;
|
|
|
|
+
|
|
|
|
+ access = ncontrol->access;
|
|
|
|
+ if (access == 0)
|
|
|
|
+ access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
+ access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_INACTIVE |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
|
|
|
|
+
|
|
|
|
+ err = snd_ctl_new(&kctl, count, access, NULL);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ return NULL;
|
|
|
|
+
|
|
|
|
+ /* The 'numid' member is decided when calling snd_ctl_add(). */
|
|
|
|
+ kctl->id.iface = ncontrol->iface;
|
|
|
|
+ kctl->id.device = ncontrol->device;
|
|
|
|
+ kctl->id.subdevice = ncontrol->subdevice;
|
|
if (ncontrol->name) {
|
|
if (ncontrol->name) {
|
|
- strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name));
|
|
|
|
- if (strcmp(ncontrol->name, kctl.id.name) != 0)
|
|
|
|
|
|
+ strlcpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
|
|
|
|
+ if (strcmp(ncontrol->name, kctl->id.name) != 0)
|
|
pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
|
|
pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
|
|
- ncontrol->name, kctl.id.name);
|
|
|
|
|
|
+ ncontrol->name, kctl->id.name);
|
|
}
|
|
}
|
|
- kctl.id.index = ncontrol->index;
|
|
|
|
- kctl.count = ncontrol->count ? ncontrol->count : 1;
|
|
|
|
- access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
|
|
|
- (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_VOLATILE|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
|
|
|
|
- kctl.info = ncontrol->info;
|
|
|
|
- kctl.get = ncontrol->get;
|
|
|
|
- kctl.put = ncontrol->put;
|
|
|
|
- kctl.tlv.p = ncontrol->tlv.p;
|
|
|
|
- kctl.private_value = ncontrol->private_value;
|
|
|
|
- kctl.private_data = private_data;
|
|
|
|
- return snd_ctl_new(&kctl, access);
|
|
|
|
|
|
+ kctl->id.index = ncontrol->index;
|
|
|
|
+
|
|
|
|
+ kctl->info = ncontrol->info;
|
|
|
|
+ kctl->get = ncontrol->get;
|
|
|
|
+ kctl->put = ncontrol->put;
|
|
|
|
+ kctl->tlv.p = ncontrol->tlv.p;
|
|
|
|
+
|
|
|
|
+ kctl->private_value = ncontrol->private_value;
|
|
|
|
+ kctl->private_data = private_data;
|
|
|
|
+
|
|
|
|
+ return kctl;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(snd_ctl_new1);
|
|
EXPORT_SYMBOL(snd_ctl_new1);
|
|
|
|
|
|
@@ -557,6 +578,7 @@ error:
|
|
*
|
|
*
|
|
* Finds the control instance with the given id, and activate or
|
|
* Finds the control instance with the given id, and activate or
|
|
* inactivate the control together with notification, if changed.
|
|
* inactivate the control together with notification, if changed.
|
|
|
|
+ * The given ID data is filled with full information.
|
|
*
|
|
*
|
|
* Return: 0 if unchanged, 1 if changed, or a negative error code on failure.
|
|
* Return: 0 if unchanged, 1 if changed, or a negative error code on failure.
|
|
*/
|
|
*/
|
|
@@ -586,6 +608,7 @@ int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
|
|
goto unlock;
|
|
goto unlock;
|
|
vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
|
vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
|
|
}
|
|
}
|
|
|
|
+ snd_ctl_build_ioff(id, kctl, index_offset);
|
|
ret = 1;
|
|
ret = 1;
|
|
unlock:
|
|
unlock:
|
|
up_write(&card->controls_rwsem);
|
|
up_write(&card->controls_rwsem);
|
|
@@ -1006,7 +1029,7 @@ static int snd_ctl_elem_unlock(struct snd_ctl_file *file,
|
|
struct user_element {
|
|
struct user_element {
|
|
struct snd_ctl_elem_info info;
|
|
struct snd_ctl_elem_info info;
|
|
struct snd_card *card;
|
|
struct snd_card *card;
|
|
- void *elem_data; /* element data */
|
|
|
|
|
|
+ char *elem_data; /* element data */
|
|
unsigned long elem_data_size; /* size of element data in bytes */
|
|
unsigned long elem_data_size; /* size of element data in bytes */
|
|
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 */
|
|
@@ -1017,8 +1040,12 @@ static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
{
|
|
struct user_element *ue = kcontrol->private_data;
|
|
struct user_element *ue = kcontrol->private_data;
|
|
|
|
+ unsigned int offset;
|
|
|
|
|
|
|
|
+ offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);
|
|
*uinfo = ue->info;
|
|
*uinfo = ue->info;
|
|
|
|
+ snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);
|
|
|
|
+
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1028,10 +1055,13 @@ static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol,
|
|
struct user_element *ue = kcontrol->private_data;
|
|
struct user_element *ue = kcontrol->private_data;
|
|
const char *names;
|
|
const char *names;
|
|
unsigned int item;
|
|
unsigned int item;
|
|
|
|
+ unsigned int offset;
|
|
|
|
|
|
item = uinfo->value.enumerated.item;
|
|
item = uinfo->value.enumerated.item;
|
|
|
|
|
|
|
|
+ offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);
|
|
*uinfo = ue->info;
|
|
*uinfo = ue->info;
|
|
|
|
+ snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);
|
|
|
|
|
|
item = min(item, uinfo->value.enumerated.items - 1);
|
|
item = min(item, uinfo->value.enumerated.items - 1);
|
|
uinfo->value.enumerated.item = item;
|
|
uinfo->value.enumerated.item = item;
|
|
@@ -1048,9 +1078,12 @@ static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
{
|
|
struct user_element *ue = kcontrol->private_data;
|
|
struct user_element *ue = kcontrol->private_data;
|
|
|
|
+ unsigned int size = ue->elem_data_size;
|
|
|
|
+ char *src = ue->elem_data +
|
|
|
|
+ snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size;
|
|
|
|
|
|
mutex_lock(&ue->card->user_ctl_lock);
|
|
mutex_lock(&ue->card->user_ctl_lock);
|
|
- memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size);
|
|
|
|
|
|
+ memcpy(&ucontrol->value, src, size);
|
|
mutex_unlock(&ue->card->user_ctl_lock);
|
|
mutex_unlock(&ue->card->user_ctl_lock);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -1060,11 +1093,14 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol,
|
|
{
|
|
{
|
|
int change;
|
|
int change;
|
|
struct user_element *ue = kcontrol->private_data;
|
|
struct user_element *ue = kcontrol->private_data;
|
|
|
|
+ unsigned int size = ue->elem_data_size;
|
|
|
|
+ char *dst = ue->elem_data +
|
|
|
|
+ snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size;
|
|
|
|
|
|
mutex_lock(&ue->card->user_ctl_lock);
|
|
mutex_lock(&ue->card->user_ctl_lock);
|
|
- change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0;
|
|
|
|
|
|
+ change = memcmp(&ucontrol->value, dst, size) != 0;
|
|
if (change)
|
|
if (change)
|
|
- memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size);
|
|
|
|
|
|
+ memcpy(dst, &ucontrol->value, size);
|
|
mutex_unlock(&ue->card->user_ctl_lock);
|
|
mutex_unlock(&ue->card->user_ctl_lock);
|
|
return change;
|
|
return change;
|
|
}
|
|
}
|
|
@@ -1078,7 +1114,7 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,
|
|
int change = 0;
|
|
int change = 0;
|
|
void *new_data;
|
|
void *new_data;
|
|
|
|
|
|
- if (op_flag > 0) {
|
|
|
|
|
|
+ if (op_flag == SNDRV_CTL_TLV_OP_WRITE) {
|
|
if (size > 1024 * 128) /* sane value */
|
|
if (size > 1024 * 128) /* sane value */
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
|
|
@@ -1161,84 +1197,103 @@ static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)
|
|
static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
|
static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
|
struct snd_ctl_elem_info *info, int replace)
|
|
struct snd_ctl_elem_info *info, int replace)
|
|
{
|
|
{
|
|
|
|
+ /* The capacity of struct snd_ctl_elem_value.value.*/
|
|
|
|
+ static const unsigned int value_sizes[] = {
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long),
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long),
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int),
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char),
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958),
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long),
|
|
|
|
+ };
|
|
|
|
+ static const unsigned int max_value_counts[] = {
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128,
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128,
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128,
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_BYTES] = 512,
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_IEC958] = 1,
|
|
|
|
+ [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64,
|
|
|
|
+ };
|
|
struct snd_card *card = file->card;
|
|
struct snd_card *card = file->card;
|
|
- struct snd_kcontrol kctl, *_kctl;
|
|
|
|
|
|
+ struct snd_kcontrol *kctl;
|
|
|
|
+ unsigned int count;
|
|
unsigned int access;
|
|
unsigned int access;
|
|
long private_size;
|
|
long private_size;
|
|
struct user_element *ue;
|
|
struct user_element *ue;
|
|
- int idx, err;
|
|
|
|
|
|
+ unsigned int offset;
|
|
|
|
+ int err;
|
|
|
|
|
|
- if (info->count < 1)
|
|
|
|
- return -EINVAL;
|
|
|
|
if (!*info->id.name)
|
|
if (!*info->id.name)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name))
|
|
if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name))
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
- access = info->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
|
|
|
|
- (info->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_INACTIVE|
|
|
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE));
|
|
|
|
- info->id.numid = 0;
|
|
|
|
- memset(&kctl, 0, sizeof(kctl));
|
|
|
|
|
|
|
|
|
|
+ /* Delete a control to replace them if needed. */
|
|
if (replace) {
|
|
if (replace) {
|
|
|
|
+ info->id.numid = 0;
|
|
err = snd_ctl_remove_user_ctl(file, &info->id);
|
|
err = snd_ctl_remove_user_ctl(file, &info->id);
|
|
if (err)
|
|
if (err)
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
- if (card->user_ctl_count >= MAX_USER_CONTROLS)
|
|
|
|
|
|
+ /*
|
|
|
|
+ * The number of userspace controls are counted control by control,
|
|
|
|
+ * not element by element.
|
|
|
|
+ */
|
|
|
|
+ if (card->user_ctl_count + 1 > MAX_USER_CONTROLS)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
- memcpy(&kctl.id, &info->id, sizeof(info->id));
|
|
|
|
- kctl.count = info->owner ? info->owner : 1;
|
|
|
|
- access |= SNDRV_CTL_ELEM_ACCESS_USER;
|
|
|
|
- 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)
|
|
|
|
- kctl.put = snd_ctl_elem_user_put;
|
|
|
|
- if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
|
|
|
|
- kctl.tlv.c = snd_ctl_elem_user_tlv;
|
|
|
|
|
|
+ /* Check the number of elements for this userspace control. */
|
|
|
|
+ count = info->owner;
|
|
|
|
+ if (count == 0)
|
|
|
|
+ count = 1;
|
|
|
|
+
|
|
|
|
+ /* Arrange access permissions if needed. */
|
|
|
|
+ access = info->access;
|
|
|
|
+ if (access == 0)
|
|
|
|
+ access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
|
|
|
+ access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_INACTIVE |
|
|
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE);
|
|
|
|
+ if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)
|
|
access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
|
|
access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
|
|
- }
|
|
|
|
- switch (info->type) {
|
|
|
|
- case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
|
|
|
- case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
|
|
|
- private_size = sizeof(long);
|
|
|
|
- if (info->count > 128)
|
|
|
|
- return -EINVAL;
|
|
|
|
- break;
|
|
|
|
- case SNDRV_CTL_ELEM_TYPE_INTEGER64:
|
|
|
|
- private_size = sizeof(long long);
|
|
|
|
- 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)
|
|
|
|
- return -EINVAL;
|
|
|
|
- break;
|
|
|
|
- case SNDRV_CTL_ELEM_TYPE_IEC958:
|
|
|
|
- private_size = sizeof(struct snd_aes_iec958);
|
|
|
|
- if (info->count != 1)
|
|
|
|
- return -EINVAL;
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
|
|
+ access |= SNDRV_CTL_ELEM_ACCESS_USER;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Check information and calculate the size of data specific to
|
|
|
|
+ * this userspace control.
|
|
|
|
+ */
|
|
|
|
+ if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
|
|
|
|
+ info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
- }
|
|
|
|
- private_size *= info->count;
|
|
|
|
- ue = kzalloc(sizeof(struct user_element) + private_size, GFP_KERNEL);
|
|
|
|
- if (ue == NULL)
|
|
|
|
|
|
+ if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
|
|
|
|
+ info->value.enumerated.items == 0)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ if (info->count < 1 ||
|
|
|
|
+ info->count > max_value_counts[info->type])
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ private_size = value_sizes[info->type] * info->count;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Keep memory object for this userspace control. After passing this
|
|
|
|
+ * code block, the instance should be freed by snd_ctl_free_one().
|
|
|
|
+ *
|
|
|
|
+ * Note that these elements in this control are locked.
|
|
|
|
+ */
|
|
|
|
+ err = snd_ctl_new(&kctl, count, access, file);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ return err;
|
|
|
|
+ memcpy(&kctl->id, &info->id, sizeof(kctl->id));
|
|
|
|
+ kctl->private_data = kzalloc(sizeof(struct user_element) + private_size * count,
|
|
|
|
+ GFP_KERNEL);
|
|
|
|
+ if (kctl->private_data == NULL) {
|
|
|
|
+ kfree(kctl);
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
+ }
|
|
|
|
+ kctl->private_free = snd_ctl_elem_user_free;
|
|
|
|
+
|
|
|
|
+ /* Set private data for this userspace control. */
|
|
|
|
+ ue = (struct user_element *)kctl->private_data;
|
|
ue->card = card;
|
|
ue->card = card;
|
|
ue->info = *info;
|
|
ue->info = *info;
|
|
ue->info.access = 0;
|
|
ue->info.access = 0;
|
|
@@ -1247,23 +1302,36 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,
|
|
if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
|
|
if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
|
|
err = snd_ctl_elem_init_enum_names(ue);
|
|
err = snd_ctl_elem_init_enum_names(ue);
|
|
if (err < 0) {
|
|
if (err < 0) {
|
|
- kfree(ue);
|
|
|
|
|
|
+ snd_ctl_free_one(kctl);
|
|
return err;
|
|
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;
|
|
|
|
- }
|
|
|
|
- _kctl->private_data = ue;
|
|
|
|
- for (idx = 0; idx < _kctl->count; idx++)
|
|
|
|
- _kctl->vd[idx].owner = file;
|
|
|
|
- err = snd_ctl_add(card, _kctl);
|
|
|
|
|
|
+
|
|
|
|
+ /* Set callback functions. */
|
|
|
|
+ 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)
|
|
|
|
+ kctl->put = snd_ctl_elem_user_put;
|
|
|
|
+ if (access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE)
|
|
|
|
+ kctl->tlv.c = snd_ctl_elem_user_tlv;
|
|
|
|
+
|
|
|
|
+ /* This function manage to free the instance on failure. */
|
|
|
|
+ err = snd_ctl_add(card, kctl);
|
|
if (err < 0)
|
|
if (err < 0)
|
|
return err;
|
|
return err;
|
|
|
|
+ offset = snd_ctl_get_ioff(kctl, &info->id);
|
|
|
|
+ snd_ctl_build_ioff(&info->id, kctl, offset);
|
|
|
|
+ /*
|
|
|
|
+ * Here we cannot fill any field for the number of elements added by
|
|
|
|
+ * this operation because there're no specific fields. The usage of
|
|
|
|
+ * 'owner' field for this purpose may cause any bugs to userspace
|
|
|
|
+ * applications because the field originally means PID of a process
|
|
|
|
+ * which locks the element.
|
|
|
|
+ */
|
|
|
|
|
|
down_write(&card->controls_rwsem);
|
|
down_write(&card->controls_rwsem);
|
|
card->user_ctl_count++;
|
|
card->user_ctl_count++;
|
|
@@ -1276,9 +1344,19 @@ static int snd_ctl_elem_add_user(struct snd_ctl_file *file,
|
|
struct snd_ctl_elem_info __user *_info, int replace)
|
|
struct snd_ctl_elem_info __user *_info, int replace)
|
|
{
|
|
{
|
|
struct snd_ctl_elem_info info;
|
|
struct snd_ctl_elem_info info;
|
|
|
|
+ int err;
|
|
|
|
+
|
|
if (copy_from_user(&info, _info, sizeof(info)))
|
|
if (copy_from_user(&info, _info, sizeof(info)))
|
|
return -EFAULT;
|
|
return -EFAULT;
|
|
- return snd_ctl_elem_add(file, &info, replace);
|
|
|
|
|
|
+ err = snd_ctl_elem_add(file, &info, replace);
|
|
|
|
+ if (err < 0)
|
|
|
|
+ return err;
|
|
|
|
+ if (copy_to_user(_info, &info, sizeof(info))) {
|
|
|
|
+ snd_ctl_remove_user_ctl(file, &info.id);
|
|
|
|
+ return -EFAULT;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int snd_ctl_elem_remove(struct snd_ctl_file *file,
|
|
static int snd_ctl_elem_remove(struct snd_ctl_file *file,
|
|
@@ -1338,9 +1416,12 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
|
|
goto __kctl_end;
|
|
goto __kctl_end;
|
|
}
|
|
}
|
|
vd = &kctl->vd[tlv.numid - kctl->id.numid];
|
|
vd = &kctl->vd[tlv.numid - kctl->id.numid];
|
|
- if ((op_flag == 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) ||
|
|
|
|
- (op_flag > 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) ||
|
|
|
|
- (op_flag < 0 && (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) {
|
|
|
|
|
|
+ if ((op_flag == SNDRV_CTL_TLV_OP_READ &&
|
|
|
|
+ (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) == 0) ||
|
|
|
|
+ (op_flag == SNDRV_CTL_TLV_OP_WRITE &&
|
|
|
|
+ (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) == 0) ||
|
|
|
|
+ (op_flag == SNDRV_CTL_TLV_OP_CMD &&
|
|
|
|
+ (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND) == 0)) {
|
|
err = -ENXIO;
|
|
err = -ENXIO;
|
|
goto __kctl_end;
|
|
goto __kctl_end;
|
|
}
|
|
}
|
|
@@ -1357,7 +1438,7 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- if (op_flag) {
|
|
|
|
|
|
+ if (op_flag != SNDRV_CTL_TLV_OP_READ) {
|
|
err = -ENXIO;
|
|
err = -ENXIO;
|
|
goto __kctl_end;
|
|
goto __kctl_end;
|
|
}
|
|
}
|