2585 lines · 68.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *  Routines for driver control interface *  Copyright (c) by Jaroslav Kysela <perex@perex.cz> */ #include <linux/threads.h>#include <linux/interrupt.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/vmalloc.h>#include <linux/time.h>#include <linux/mm.h>#include <linux/math64.h>#include <linux/sched/signal.h>#include <sound/core.h>#include <sound/minors.h>#include <sound/info.h>#include <sound/control.h> #ifdef CONFIG_SND_CTL_DEBUG#define CREATE_TRACE_POINTS#include "control_trace.h"#else#define trace_snd_ctl_put(card, kctl, iname, expected, actual)#endif // Max allocation size for user controls.static int max_user_ctl_alloc_size = 8 * 1024 * 1024;module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444);MODULE_PARM_DESC(max_user_ctl_alloc_size, "Max allocation size for user controls"); #define MAX_CONTROL_COUNT	1028 struct snd_kctl_ioctl {	struct list_head list;		/* list of all ioctls */	snd_kctl_ioctl_func_t fioctl;}; DECLARE_RWSEM(snd_ioctl_rwsem);static DECLARE_RWSEM(snd_ctl_layer_rwsem);static LIST_HEAD(snd_control_ioctls);#ifdef CONFIG_COMPATstatic LIST_HEAD(snd_control_compat_ioctls);#endifstatic struct snd_ctl_layer_ops *snd_ctl_layer; static int snd_ctl_remove_locked(struct snd_card *card,				 struct snd_kcontrol *kcontrol); static int snd_ctl_open(struct inode *inode, struct file *file){	struct snd_card *card;	struct snd_ctl_file *ctl;	int i, err; 	stream_open(inode, file); 	card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL);	if (!card) {		err = -ENODEV;		goto __error1;	}	err = snd_card_file_add(card, file);	if (err < 0)		goto __error1;	if (!try_module_get(card->module)) {		err = -ENODEV;		goto __error2;	}	ctl = kzalloc_obj(*ctl);	if (ctl == NULL) {		err = -ENOMEM;		goto __error;	}	INIT_LIST_HEAD(&ctl->events);	init_waitqueue_head(&ctl->change_sleep);	spin_lock_init(&ctl->read_lock);	ctl->card = card;	for (i = 0; i < SND_CTL_SUBDEV_ITEMS; i++)		ctl->preferred_subdevice[i] = -1;	ctl->pid = get_pid(task_pid(current));	file->private_data = ctl;	scoped_guard(write_lock_irqsave, &card->controls_rwlock)		list_add_tail(&ctl->list, &card->ctl_files);	snd_card_unref(card);	return 0;       __error:	module_put(card->module);      __error2:	snd_card_file_remove(card, file);      __error1:	if (card)		snd_card_unref(card);      	return err;} static void snd_ctl_empty_read_queue(struct snd_ctl_file * ctl){	struct snd_kctl_event *cread; 	guard(spinlock_irqsave)(&ctl->read_lock);	while (!list_empty(&ctl->events)) {		cread = snd_kctl_event(ctl->events.next);		list_del(&cread->list);		kfree(cread);	}} static int snd_ctl_release(struct inode *inode, struct file *file){	struct snd_card *card;	struct snd_ctl_file *ctl;	struct snd_kcontrol *control;	unsigned int idx; 	ctl = file->private_data;	file->private_data = NULL;	card = ctl->card; 	scoped_guard(write_lock_irqsave, &card->controls_rwlock)		list_del(&ctl->list); 	scoped_guard(rwsem_write, &card->controls_rwsem) {		list_for_each_entry(control, &card->controls, list)			for (idx = 0; idx < control->count; idx++)				if (control->vd[idx].owner == ctl)					control->vd[idx].owner = NULL;	} 	snd_fasync_free(ctl->fasync);	snd_ctl_empty_read_queue(ctl);	put_pid(ctl->pid);	kfree(ctl);	module_put(card->module);	snd_card_file_remove(card, file);	return 0;} /** * snd_ctl_notify - Send notification to user-space for a control change * @card: the card to send notification * @mask: the event mask, SNDRV_CTL_EVENT_* * @id: the ctl element id to send notification * * This function adds an event record with the given id and mask, appends * to the list and wakes up the user-space for notification.  This can be * called in the atomic context. */void snd_ctl_notify(struct snd_card *card, unsigned int mask,		    struct snd_ctl_elem_id *id){	struct snd_ctl_file *ctl;	struct snd_kctl_event *ev; 	if (snd_BUG_ON(!card || !id))		return;	if (card->shutdown)		return; 	guard(read_lock_irqsave)(&card->controls_rwlock);#if IS_ENABLED(CONFIG_SND_MIXER_OSS)	card->mixer_oss_change_count++;#endif	list_for_each_entry(ctl, &card->ctl_files, list) {		if (!ctl->subscribed)			continue;		scoped_guard(spinlock, &ctl->read_lock) {			list_for_each_entry(ev, &ctl->events, list) {				if (ev->id.numid == id->numid) {					ev->mask |= mask;					goto _found;				}			}			ev = kzalloc_obj(*ev, GFP_ATOMIC);			if (ev) {				ev->id = *id;				ev->mask = mask;				list_add_tail(&ev->list, &ctl->events);			} else {				dev_err(card->dev, "No memory available to allocate event\n");			}_found:			wake_up(&ctl->change_sleep);		}		snd_kill_fasync(ctl->fasync, SIGIO, POLL_IN);	}}EXPORT_SYMBOL(snd_ctl_notify); /** * snd_ctl_notify_one - Send notification to user-space for a control change * @card: the card to send notification * @mask: the event mask, SNDRV_CTL_EVENT_* * @kctl: the pointer with the control instance * @ioff: the additional offset to the control index * * This function calls snd_ctl_notify() and does additional jobs * like LED state changes. */void snd_ctl_notify_one(struct snd_card *card, unsigned int mask,			struct snd_kcontrol *kctl, unsigned int ioff){	struct snd_ctl_elem_id id = kctl->id;	struct snd_ctl_layer_ops *lops; 	id.index += ioff;	id.numid += ioff;	snd_ctl_notify(card, mask, &id);	guard(rwsem_read)(&snd_ctl_layer_rwsem);	for (lops = snd_ctl_layer; lops; lops = lops->next)		lops->lnotify(card, mask, kctl, ioff);}EXPORT_SYMBOL(snd_ctl_notify_one); /** * 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 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: 0 on success, error code on failure */static int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count,		       unsigned int access, struct snd_ctl_file *file){	unsigned int idx; 	if (count == 0 || count > MAX_CONTROL_COUNT)		return -EINVAL; 	*kctl = kzalloc_flex(**kctl, vd, count);	if (!*kctl)		return -ENOMEM; 	(*kctl)->count = count;	for (idx = 0; idx < count; idx++) {		(*kctl)->vd[idx].access = access;		(*kctl)->vd[idx].owner = file;	} 	return 0;} /** * snd_ctl_new1 - create a control instance from the template * @ncontrol: the initialization record * @private_data: the private data to set * * Allocates a new struct snd_kcontrol instance and initialize from the given * template.  When the access field of ncontrol is 0, it's assumed as * READWRITE access. When the count field is 0, it's assumes as one. * * Return: The pointer of the newly generated instance, or %NULL on failure. */struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,				  void *private_data){	struct snd_kcontrol *kctl;	unsigned int count;	unsigned int access;	int err; 	if (snd_BUG_ON(!ncontrol || !ncontrol->info))		return NULL; 	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 |		   SNDRV_CTL_ELEM_ACCESS_LED_MASK |		   SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK); 	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) {		strscpy(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);	}	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); /** * snd_ctl_free_one - release the control instance * @kcontrol: the control instance * * Releases the control instance created via snd_ctl_new() * or snd_ctl_new1(). * Don't call this after the control was added to the card. */void snd_ctl_free_one(struct snd_kcontrol *kcontrol){	if (kcontrol) {		if (kcontrol->private_free)			kcontrol->private_free(kcontrol);		kfree(kcontrol);	}}EXPORT_SYMBOL(snd_ctl_free_one); static bool snd_ctl_remove_numid_conflict(struct snd_card *card,					  unsigned int count){	struct snd_kcontrol *kctl; 	/* Make sure that the ids assigned to the control do not wrap around */	if (card->last_numid >= UINT_MAX - count)		card->last_numid = 0; 	list_for_each_entry(kctl, &card->controls, list) {		if (kctl->id.numid < card->last_numid + 1 + count &&		    kctl->id.numid + kctl->count > card->last_numid + 1) {		    	card->last_numid = kctl->id.numid + kctl->count - 1;			return true;		}	}	return false;} static int snd_ctl_find_hole(struct snd_card *card, unsigned int count){	unsigned int iter = 100000; 	while (snd_ctl_remove_numid_conflict(card, count)) {		if (--iter == 0) {			/* this situation is very unlikely */			dev_err(card->dev, "unable to allocate new control numid\n");			return -ENOMEM;		}	}	return 0;} /* check whether the given id is contained in the given kctl */static bool elem_id_matches(const struct snd_kcontrol *kctl,			    const struct snd_ctl_elem_id *id){	return kctl->id.iface == id->iface &&		kctl->id.device == id->device &&		kctl->id.subdevice == id->subdevice &&		!strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) &&		kctl->id.index <= id->index &&		kctl->id.index + kctl->count > id->index;} #ifdef CONFIG_SND_CTL_FAST_LOOKUP/* Compute a hash key for the corresponding ctl id * It's for the name lookup, hence the numid is excluded. * The hash key is bound in LONG_MAX to be used for Xarray key. */#define MULTIPLIER	37static unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id){	int i;	unsigned long h; 	h = id->iface;	h = MULTIPLIER * h + id->device;	h = MULTIPLIER * h + id->subdevice;	for (i = 0; i < SNDRV_CTL_ELEM_ID_NAME_MAXLEN && id->name[i]; i++)		h = MULTIPLIER * h + id->name[i];	h = MULTIPLIER * h + id->index;	h &= LONG_MAX;	return h;} /* add hash entries to numid and ctl xarray tables */static void add_hash_entries(struct snd_card *card,			     struct snd_kcontrol *kcontrol){	struct snd_ctl_elem_id id = kcontrol->id;	int i; 	xa_store_range(&card->ctl_numids, kcontrol->id.numid,		       kcontrol->id.numid + kcontrol->count - 1,		       kcontrol, GFP_KERNEL); 	for (i = 0; i < kcontrol->count; i++) {		id.index = kcontrol->id.index + i;		if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id),			      kcontrol, GFP_KERNEL)) {			/* skip hash for this entry, noting we had collision */			card->ctl_hash_collision = true;			dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n",				id.iface, id.name, id.index);		}	}} /* remove hash entries that have been added */static void remove_hash_entries(struct snd_card *card,				struct snd_kcontrol *kcontrol){	struct snd_ctl_elem_id id = kcontrol->id;	struct snd_kcontrol *matched;	unsigned long h;	int i; 	for (i = 0; i < kcontrol->count; i++) {		xa_erase(&card->ctl_numids, id.numid);		h = get_ctl_id_hash(&id);		matched = xa_load(&card->ctl_hash, h);		if (matched && (matched == kcontrol ||				elem_id_matches(matched, &id)))			xa_erase(&card->ctl_hash, h);		id.index++;		id.numid++;	}}#else /* CONFIG_SND_CTL_FAST_LOOKUP */static inline void add_hash_entries(struct snd_card *card,				    struct snd_kcontrol *kcontrol){}static inline void remove_hash_entries(struct snd_card *card,				       struct snd_kcontrol *kcontrol){}#endif /* CONFIG_SND_CTL_FAST_LOOKUP */ enum snd_ctl_add_mode {	CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE,}; /* add/replace a new kcontrol object; call with card->controls_rwsem locked */static int __snd_ctl_add_replace(struct snd_card *card,				 struct snd_kcontrol *kcontrol,				 enum snd_ctl_add_mode mode){	struct snd_ctl_elem_id id;	unsigned int idx;	struct snd_kcontrol *old;	int err; 	lockdep_assert_held_write(&card->controls_rwsem); 	id = kcontrol->id;	if (id.index > UINT_MAX - kcontrol->count)		return -EINVAL; 	old = snd_ctl_find_id(card, &id);	if (!old) {		if (mode == CTL_REPLACE)			return -EINVAL;	} else {		if (mode == CTL_ADD_EXCLUSIVE) {			dev_err(card->dev,				"control %i:%i:%i:%s:%i is already present\n",				id.iface, id.device, id.subdevice, id.name,				id.index);			return -EBUSY;		} 		err = snd_ctl_remove_locked(card, old);		if (err < 0)			return err;	} 	if (snd_ctl_find_hole(card, kcontrol->count) < 0)		return -ENOMEM; 	scoped_guard(write_lock_irq, &card->controls_rwlock) {		list_add_tail(&kcontrol->list, &card->controls);		card->controls_count += kcontrol->count;		kcontrol->id.numid = card->last_numid + 1;

Lines 1–500 of 2585.