1208 lines · 31.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *  Initialization routines *  Copyright (c) by Jaroslav Kysela <perex@perex.cz> */ #include <linux/init.h>#include <linux/sched.h>#include <linux/module.h>#include <linux/device.h>#include <linux/file.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/ctype.h>#include <linux/pm.h>#include <linux/debugfs.h>#include <linux/completion.h>#include <linux/interrupt.h> #include <sound/core.h>#include <sound/control.h>#include <sound/info.h> /* monitor files for graceful shutdown (hotplug) */struct snd_monitor_file {	struct file *file;	const struct file_operations *disconnected_f_op;	struct list_head shutdown_list;	/* still need to shutdown */	struct list_head list;	/* link of monitor files */}; static DEFINE_SPINLOCK(shutdown_lock);static LIST_HEAD(shutdown_files); static const struct file_operations snd_shutdown_f_ops; /* locked for registering/using */static DECLARE_BITMAP(snd_cards_lock, SNDRV_CARDS);static struct snd_card *snd_cards[SNDRV_CARDS]; static DEFINE_MUTEX(snd_card_mutex); static char *slots[SNDRV_CARDS];module_param_array(slots, charp, NULL, 0444);MODULE_PARM_DESC(slots, "Module names assigned to the slots."); /* return non-zero if the given index is reserved for the given * module via slots option */static int module_slot_match(struct module *module, int idx){	int match = 1;#ifdef CONFIG_MODULES	const char *s1, *s2; 	if (!module || !*module->name || !slots[idx])		return 0; 	s1 = module->name;	s2 = slots[idx];	if (*s2 == '!') {		match = 0; /* negative match */		s2++;	}	/* compare module name strings	 * hyphens are handled as equivalent with underscore	 */	for (;;) {		char c1 = *s1++;		char c2 = *s2++;		if (c1 == '-')			c1 = '_';		if (c2 == '-')			c2 = '_';		if (c1 != c2)			return !match;		if (!c1)			break;	}#endif /* CONFIG_MODULES */	return match;} #if IS_ENABLED(CONFIG_SND_MIXER_OSS)int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag);EXPORT_SYMBOL(snd_mixer_oss_notify_callback);#endif static int check_empty_slot(struct module *module, int slot){	return !slots[slot] || !*slots[slot];} /* return an empty slot number (>= 0) found in the given bitmask @mask. * @mask == -1 == 0xffffffff means: take any free slot up to 32 * when no slot is available, return the original @mask as is. */static int get_slot_from_bitmask(int mask, int (*check)(struct module *, int),				 struct module *module){	int slot; 	for (slot = 0; slot < SNDRV_CARDS; slot++) {		if (slot < 32 && !(mask & (1U << slot)))			continue;		if (!test_bit(slot, snd_cards_lock)) {			if (check(module, slot))				return slot; /* found */		}	}	return mask; /* unchanged */} /* the default release callback set in snd_device_alloc() */static void default_release_alloc(struct device *dev){	kfree(dev);} /** * snd_device_alloc - Allocate and initialize struct device for sound devices * @dev_p: pointer to store the allocated device * @card: card to assign, optional * * For releasing the allocated device, call put_device(). */int snd_device_alloc(struct device **dev_p, struct snd_card *card){	struct device *dev; 	*dev_p = NULL;	dev = kzalloc_obj(*dev);	if (!dev)		return -ENOMEM;	device_initialize(dev);	if (card)		dev->parent = &card->card_dev;	dev->class = &sound_class;	dev->release = default_release_alloc;	*dev_p = dev;	return 0;}EXPORT_SYMBOL_GPL(snd_device_alloc); static int snd_card_init(struct snd_card *card, struct device *parent,			 int idx, const char *xid, struct module *module,			 size_t extra_size);static int snd_card_do_free(struct snd_card *card);static const struct attribute_group card_dev_attr_group; static void release_card_device(struct device *dev){	snd_card_do_free(dev_to_snd_card(dev));} /** *  snd_card_new - create and initialize a soundcard structure *  @parent: the parent device object *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)] *  @xid: card identification (ASCII string) *  @module: top level module for locking *  @extra_size: allocate this extra size after the main soundcard structure *  @card_ret: the pointer to store the created card instance * *  The function allocates snd_card instance via kzalloc with the given *  space for the driver to use freely.  The allocated struct is stored *  in the given card_ret pointer. * *  Return: Zero if successful or a negative error code. */int snd_card_new(struct device *parent, int idx, const char *xid,		    struct module *module, int extra_size,		    struct snd_card **card_ret){	struct snd_card *card;	int err; 	if (snd_BUG_ON(!card_ret))		return -EINVAL;	*card_ret = NULL; 	if (extra_size < 0)		extra_size = 0;	card = kzalloc_flex(*card, private_data_area, extra_size);	if (!card)		return -ENOMEM; 	err = snd_card_init(card, parent, idx, xid, module, extra_size);	if (err < 0)		return err; /* card is freed by error handler */ 	*card_ret = card;	return 0;}EXPORT_SYMBOL(snd_card_new); static void __snd_card_release(struct device *dev, void *data){	snd_card_free(data);} /** * snd_devm_card_new - managed snd_card object creation * @parent: the parent device object * @idx: card index (address) [0 ... (SNDRV_CARDS-1)] * @xid: card identification (ASCII string) * @module: top level module for locking * @extra_size: allocate this extra size after the main soundcard structure * @card_ret: the pointer to store the created card instance * * This function works like snd_card_new() but manages the allocated resource * via devres, i.e. you don't need to free explicitly. * * When a snd_card object is created with this function and registered via * snd_card_register(), the very first devres action to call snd_card_free() * is added automatically.  In that way, the resource disconnection is assured * at first, then released in the expected order. * * If an error happens at the probe before snd_card_register() is called and * there have been other devres resources, you'd need to free the card manually * via snd_card_free() call in the error; otherwise it may lead to UAF due to * devres call orders.  You can use snd_card_free_on_error() helper for * handling it more easily. * * Return: zero if successful, or a negative error code */int snd_devm_card_new(struct device *parent, int idx, const char *xid,		      struct module *module, size_t extra_size,		      struct snd_card **card_ret){	struct snd_card *card;	int err; 	*card_ret = NULL;	card = devres_alloc(__snd_card_release,			    struct_size(card, private_data_area, extra_size),			    GFP_KERNEL);	if (!card)		return -ENOMEM;	card->managed = true;	err = snd_card_init(card, parent, idx, xid, module, extra_size);	if (err < 0) {		devres_free(card); /* in managed mode, we need to free manually */		return err;	} 	devres_add(parent, card);	*card_ret = card;	return 0;}EXPORT_SYMBOL_GPL(snd_devm_card_new); /** * snd_card_free_on_error - a small helper for handling devm probe errors * @dev: the managed device object * @ret: the return code from the probe callback * * This function handles the explicit snd_card_free() call at the error from * the probe callback.  It's just a small helper for simplifying the error * handling with the managed devices. * * Return: zero if successful, or a negative error code */int snd_card_free_on_error(struct device *dev, int ret){	struct snd_card *card; 	if (!ret)		return 0;	card = devres_find(dev, __snd_card_release, NULL, NULL);	if (card)		snd_card_free(card);	return ret;}EXPORT_SYMBOL_GPL(snd_card_free_on_error); static int snd_card_init(struct snd_card *card, struct device *parent,			 int idx, const char *xid, struct module *module,			 size_t extra_size){	int err; 	if (extra_size > 0)		card->private_data = card->private_data_area;	if (xid)		strscpy(card->id, xid, sizeof(card->id));	err = 0;	scoped_guard(mutex, &snd_card_mutex) {		if (idx < 0) /* first check the matching module-name slot */			idx = get_slot_from_bitmask(idx, module_slot_match, module);		if (idx < 0) /* if not matched, assign an empty slot */			idx = get_slot_from_bitmask(idx, check_empty_slot, module);		if (idx < 0)			err = -ENODEV;		else if (idx < snd_ecards_limit) {			if (test_bit(idx, snd_cards_lock))				err = -EBUSY;	/* invalid */		} else if (idx >= SNDRV_CARDS)			err = -ENODEV;		if (!err) {			set_bit(idx, snd_cards_lock);		/* lock it */			if (idx >= snd_ecards_limit)				snd_ecards_limit = idx + 1; /* increase the limit */		}	}	if (err < 0) {		dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n",			idx, snd_ecards_limit - 1, err);		if (!card->managed)			kfree(card); /* manually free here, as no destructor called */		return err;	}	card->dev = parent;	card->number = idx;	WARN_ON(IS_MODULE(CONFIG_SND) && !module);	card->module = module;	INIT_LIST_HEAD(&card->devices);	init_rwsem(&card->controls_rwsem);	rwlock_init(&card->controls_rwlock);	INIT_LIST_HEAD(&card->controls);	INIT_LIST_HEAD(&card->ctl_files);#ifdef CONFIG_SND_CTL_FAST_LOOKUP	xa_init(&card->ctl_numids);	xa_init(&card->ctl_hash);#endif	spin_lock_init(&card->files_lock);	INIT_LIST_HEAD(&card->files_list);	mutex_init(&card->memory_mutex);#ifdef CONFIG_PM	init_waitqueue_head(&card->power_sleep);	snd_refcount_init(&card->power_ref);#endif	init_waitqueue_head(&card->remove_sleep);	card->sync_irq = -1; 	device_initialize(&card->card_dev);	card->card_dev.parent = parent;	card->card_dev.class = &sound_class;	card->card_dev.release = release_card_device;	card->card_dev.groups = card->dev_groups;	card->dev_groups[0] = &card_dev_attr_group;	err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);	if (err < 0)		goto __error; 	snprintf(card->irq_descr, sizeof(card->irq_descr), "%s:%s",		 dev_driver_string(card->dev), dev_name(&card->card_dev)); 	/* the control interface cannot be accessed from the user space until */	/* snd_cards_bitmask and snd_cards are set with snd_card_register */	err = snd_ctl_create(card);	if (err < 0) {		dev_err(parent, "unable to register control minors\n");		goto __error;	}	err = snd_info_card_create(card);	if (err < 0) {		dev_err(parent, "unable to create card info\n");		goto __error_ctl;	} #ifdef CONFIG_SND_DEBUG	card->debugfs_root = debugfs_create_dir(dev_name(&card->card_dev),						sound_debugfs_root);#endif#ifdef CONFIG_SND_CTL_DEBUG	card->value_buf = kmalloc_obj(*card->value_buf);	if (!card->value_buf)		return -ENOMEM;#endif	return 0;       __error_ctl:	snd_device_free_all(card);      __error:	put_device(&card->card_dev);  	return err;} /** * snd_card_ref - Get the card object from the index * @idx: the card index * * Returns a card object corresponding to the given index or NULL if not found. * Release the object via snd_card_unref(). * * Return: a card object or NULL */struct snd_card *snd_card_ref(int idx){	struct snd_card *card; 	guard(mutex)(&snd_card_mutex);	card = snd_cards[idx];	if (card)		get_device(&card->card_dev);	return card;}EXPORT_SYMBOL_GPL(snd_card_ref); /* return non-zero if a card is already locked */int snd_card_locked(int card){	guard(mutex)(&snd_card_mutex);	return test_bit(card, snd_cards_lock);} static loff_t snd_disconnect_llseek(struct file *file, loff_t offset, int orig){	return -ENODEV;} static ssize_t snd_disconnect_read(struct file *file, char __user *buf,				   size_t count, loff_t *offset){	return -ENODEV;} static ssize_t snd_disconnect_write(struct file *file, const char __user *buf,				    size_t count, loff_t *offset){	return -ENODEV;} static int snd_disconnect_release(struct inode *inode, struct file *file){	struct snd_monitor_file *df = NULL, *_df; 	scoped_guard(spinlock, &shutdown_lock) {		list_for_each_entry(_df, &shutdown_files, shutdown_list) {			if (_df->file == file) {				df = _df;				list_del_init(&df->shutdown_list);				break;			}		}	} 	if (likely(df)) {		if ((file->f_flags & FASYNC) && df->disconnected_f_op->fasync)			df->disconnected_f_op->fasync(-1, file, 0);		return df->disconnected_f_op->release(inode, file);	} 	panic("%s(%p, %p) failed!", __func__, inode, file);} static __poll_t snd_disconnect_poll(struct file * file, poll_table * wait){	return EPOLLERR | EPOLLNVAL;} static long snd_disconnect_ioctl(struct file *file,				 unsigned int cmd, unsigned long arg){	return -ENODEV;} static int snd_disconnect_mmap(struct file *file, struct vm_area_struct *vma){	return -ENODEV;} static int snd_disconnect_fasync(int fd, struct file *file, int on){	return -ENODEV;} static const struct file_operations snd_shutdown_f_ops = {	.owner		=	THIS_MODULE,	.llseek		=	snd_disconnect_llseek,	.read		=	snd_disconnect_read,	.write		=	snd_disconnect_write,	.release	=	snd_disconnect_release,	.poll		=	snd_disconnect_poll,	.unlocked_ioctl	=	snd_disconnect_ioctl,#ifdef CONFIG_COMPAT	.compat_ioctl	=	snd_disconnect_ioctl,#endif	.mmap		=	snd_disconnect_mmap,	.fasync		=	snd_disconnect_fasync}; /** *  snd_card_disconnect - disconnect all APIs from the file-operations (user space) *  @card: soundcard structure * *  Disconnects all APIs from the file-operations (user space). * *  Return: Zero, otherwise a negative error code. * *  Note: The current implementation replaces all active file->f_op with special *        dummy file operations (they do nothing except release). */void snd_card_disconnect(struct snd_card *card){	struct snd_monitor_file *mfile; 	if (!card)		return;

Lines 1–500 of 1208.