diff options
Diffstat (limited to 'sound/core/control.c')
| -rw-r--r-- | sound/core/control.c | 331 | 
1 files changed, 208 insertions, 123 deletions
diff --git a/sound/core/control.c b/sound/core/control.c index 35324a8e83c8..196a6fe100ca 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -192,36 +192,41 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask,  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; -	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,  				  void *private_data)  { -	struct snd_kcontrol kctl; +	struct snd_kcontrol *kctl; +	unsigned int count;  	unsigned int access; +	int err;  	if (snd_BUG_ON(!ncontrol || !ncontrol->info))  		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) { -		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", -				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); @@ -557,6 +578,7 @@ error:   *   * Finds the control instance with the given id, and activate or   * 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.   */ @@ -586,6 +608,7 @@ int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,  			goto unlock;  		vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;  	} +	snd_ctl_build_ioff(id, kctl, index_offset);  	ret = 1;   unlock:  	up_write(&card->controls_rwsem); @@ -1006,7 +1029,7 @@ static int snd_ctl_elem_unlock(struct snd_ctl_file *file,  struct user_element {  	struct snd_ctl_elem_info info;  	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 */  	void *tlv_data;			/* TLV data */  	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 user_element *ue = kcontrol->private_data; +	unsigned int offset; +	offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);  	*uinfo = ue->info; +	snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); +  	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;  	const char *names;  	unsigned int item; +	unsigned int offset;  	item = uinfo->value.enumerated.item; +	offset = snd_ctl_get_ioff(kcontrol, &uinfo->id);  	*uinfo = ue->info; +	snd_ctl_build_ioff(&uinfo->id, kcontrol, offset);  	item = min(item, uinfo->value.enumerated.items - 1);  	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 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); -	memcpy(&ucontrol->value, ue->elem_data, ue->elem_data_size); +	memcpy(&ucontrol->value, src, size);  	mutex_unlock(&ue->card->user_ctl_lock);  	return 0;  } @@ -1060,11 +1093,14 @@ static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol,  {  	int change;  	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); -	change = memcmp(&ucontrol->value, ue->elem_data, ue->elem_data_size) != 0; +	change = memcmp(&ucontrol->value, dst, size) != 0;  	if (change) -		memcpy(ue->elem_data, &ucontrol->value, ue->elem_data_size); +		memcpy(dst, &ucontrol->value, size);  	mutex_unlock(&ue->card->user_ctl_lock);  	return change;  } @@ -1078,7 +1114,7 @@ static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kcontrol,  	int change = 0;  	void *new_data; -	if (op_flag > 0) { +	if (op_flag == SNDRV_CTL_TLV_OP_WRITE) {  		if (size > 1024 * 128)	/* sane value */  			return -EINVAL; @@ -1161,80 +1197,103 @@ static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol)  static int snd_ctl_elem_add(struct snd_ctl_file *file,  			    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_kcontrol kctl, *_kctl; +	struct snd_kcontrol *kctl; +	unsigned int count;  	unsigned int access;  	long private_size;  	struct user_element *ue; -	int idx, err; +	unsigned int offset; +	int err; -	if (info->count < 1) +	if (!*info->id.name) +		return -EINVAL; +	if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name))  		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) { +		info->id.numid = 0;  		err = snd_ctl_remove_user_ctl(file, &info->id);  		if (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; -	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; -	} -	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; -	} -	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; +	} +	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->info = *info;  	ue->info.access = 0; @@ -1243,23 +1302,36 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file,  	if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {  		err = snd_ctl_elem_init_enum_names(ue);  		if (err < 0) { -			kfree(ue); +			snd_ctl_free_one(kctl);  			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)  		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);  	card->user_ctl_count++; @@ -1272,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 info; +	int err; +  	if (copy_from_user(&info, _info, sizeof(info)))  		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, @@ -1334,9 +1416,12 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,  		goto __kctl_end;  	}  	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;  	    	goto __kctl_end;  	} @@ -1353,7 +1438,7 @@ static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file,  			return 0;  		}  	} else { -		if (op_flag) { +		if (op_flag != SNDRV_CTL_TLV_OP_READ) {  			err = -ENXIO;  			goto __kctl_end;  		}  |