diff options
Diffstat (limited to '')
-rw-r--r-- | sound/core/init.c | 272 |
1 files changed, 218 insertions, 54 deletions
diff --git a/sound/core/init.c b/sound/core/init.c index b02a99766351..5377f94eb211 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -13,7 +13,9 @@ #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> @@ -132,6 +134,9 @@ void snd_device_initialize(struct device *dev, struct snd_card *card) } EXPORT_SYMBOL_GPL(snd_device_initialize); +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; @@ -149,8 +154,6 @@ static void release_card_device(struct device *dev) * @extra_size: allocate this extra size after the main soundcard structure * @card_ret: the pointer to store the created card instance * - * Creates and initializes a soundcard structure. - * * 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. @@ -173,10 +176,108 @@ int snd_card_new(struct device *parent, int idx, const char *xid, card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); 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, sizeof(*card) + 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; +#ifdef CONFIG_SND_DEBUG + char name[8]; +#endif + if (extra_size > 0) card->private_data = (char *)card + sizeof(struct snd_card); if (xid) - strlcpy(card->id, xid, sizeof(card->id)); + strscpy(card->id, xid, sizeof(card->id)); err = 0; mutex_lock(&snd_card_mutex); if (idx < 0) /* first check the matching module-name slot */ @@ -194,7 +295,8 @@ int snd_card_new(struct device *parent, int idx, const char *xid, mutex_unlock(&snd_card_mutex); dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n", idx, snd_ecards_limit - 1, err); - kfree(card); + if (!card->managed) + kfree(card); /* manually free here, as no destructor called */ return err; } set_bit(idx, snd_cards_lock); /* lock it */ @@ -203,17 +305,26 @@ int snd_card_new(struct device *parent, int idx, const char *xid, mutex_unlock(&snd_card_mutex); card->dev = parent; card->number = idx; +#ifdef MODULE + WARN_ON(!module); card->module = module; +#endif INIT_LIST_HEAD(&card->devices); init_rwsem(&card->controls_rwsem); rwlock_init(&card->ctl_files_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); + init_waitqueue_head(&card->power_ref_sleep); + atomic_set(&card->power_ref, 0); #endif init_waitqueue_head(&card->remove_sleep); card->sync_irq = -1; @@ -243,7 +354,11 @@ int snd_card_new(struct device *parent, int idx, const char *xid, dev_err(parent, "unable to create card info\n"); goto __error_ctl; } - *card_ret = card; + +#ifdef CONFIG_SND_DEBUG + sprintf(name, "card%d", idx); + card->debugfs_root = debugfs_create_dir(name, sound_debugfs_root); +#endif return 0; __error_ctl: @@ -252,7 +367,6 @@ int snd_card_new(struct device *parent, int idx, const char *xid, put_device(&card->card_dev); return err; } -EXPORT_SYMBOL(snd_card_new); /** * snd_card_ref - Get the card object from the index @@ -260,6 +374,8 @@ EXPORT_SYMBOL(snd_card_new); * * 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) { @@ -386,10 +502,8 @@ int snd_card_disconnect(struct snd_card *card) return 0; } card->shutdown = 1; - spin_unlock(&card->files_lock); /* replace file->f_op with special dummy operations */ - spin_lock(&card->files_lock); list_for_each_entry(mfile, &card->files_list, list) { /* it's critical part, use endless loop */ /* we have no room to fail */ @@ -415,6 +529,9 @@ int snd_card_disconnect(struct snd_card *card) /* notify all devices that we are disconnected */ snd_device_disconnect_all(card); + if (card->sync_irq > 0) + synchronize_irq(card->sync_irq); + snd_info_card_disconnect(card); if (card->registered) { device_del(&card->card_dev); @@ -429,6 +546,7 @@ int snd_card_disconnect(struct snd_card *card) #ifdef CONFIG_PM wake_up(&card->power_sleep); + snd_power_sync_ref(card); #endif return 0; } @@ -465,6 +583,7 @@ EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); static int snd_card_do_free(struct snd_card *card) { + card->releasing = true; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) if (snd_mixer_oss_notify_callback) snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); @@ -476,9 +595,14 @@ static int snd_card_do_free(struct snd_card *card) dev_warn(card->dev, "unable to free card info\n"); /* Not fatal error */ } +#ifdef CONFIG_SND_DEBUG + debugfs_remove(card->debugfs_root); + card->debugfs_root = NULL; +#endif if (card->release_completion) complete(card->release_completion); - kfree(card); + if (!card->managed) + kfree(card); return 0; } @@ -490,6 +614,8 @@ static int snd_card_do_free(struct snd_card *card) * resource immediately, but tries to disconnect at first. When the card * is still in use, the function returns before freeing the resources. * The card resources will be freed when the refcount gets to zero. + * + * Return: zero if successful, or a negative error code */ int snd_card_free_when_closed(struct snd_card *card) { @@ -516,16 +642,25 @@ EXPORT_SYMBOL(snd_card_free_when_closed); */ int snd_card_free(struct snd_card *card) { - struct completion released; + DECLARE_COMPLETION_ONSTACK(released); int ret; - init_completion(&released); + /* The call of snd_card_free() is allowed from various code paths; + * a manual call from the driver and the call via devres_free, and + * we need to avoid double-free. Moreover, the release via devres + * may call snd_card_free() twice due to its nature, we need to have + * the check here at the beginning. + */ + if (card->releasing) + return 0; + card->release_completion = &released; ret = snd_card_free_when_closed(card); if (ret) return ret; /* wait, until all devices are ready for the free operation */ wait_for_completion(&released); + return 0; } EXPORT_SYMBOL(snd_card_free); @@ -623,7 +758,7 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, /* last resort... */ dev_err(card->dev, "unable to set card id (%s)\n", id); if (card->proc_root->name) - strlcpy(card->id, card->proc_root->name, sizeof(card->id)); + strscpy(card->id, card->proc_root->name, sizeof(card->id)); } /** @@ -645,17 +780,15 @@ void snd_card_set_id(struct snd_card *card, const char *nid) } EXPORT_SYMBOL(snd_card_set_id); -static ssize_t -card_id_show_attr(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t id_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct snd_card *card = container_of(dev, struct snd_card, card_dev); - return scnprintf(buf, PAGE_SIZE, "%s\n", card->id); + return sysfs_emit(buf, "%s\n", card->id); } -static ssize_t -card_id_store_attr(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t id_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct snd_card *card = container_of(dev, struct snd_card, card_dev); char buf1[sizeof(card->id)]; @@ -683,17 +816,16 @@ card_id_store_attr(struct device *dev, struct device_attribute *attr, return count; } -static DEVICE_ATTR(id, 0644, card_id_show_attr, card_id_store_attr); +static DEVICE_ATTR_RW(id); -static ssize_t -card_number_show_attr(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t number_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct snd_card *card = container_of(dev, struct snd_card, card_dev); - return scnprintf(buf, PAGE_SIZE, "%i\n", card->number); + return sysfs_emit(buf, "%i\n", card->number); } -static DEVICE_ATTR(number, 0444, card_number_show_attr, NULL); +static DEVICE_ATTR_RO(number); static struct attribute *card_dev_attrs[] = { &dev_attr_id.attr, @@ -709,6 +841,8 @@ static const struct attribute_group card_dev_attr_group = { * snd_card_add_dev_attr - Append a new sysfs attribute group to card * @card: card instance * @group: attribute group to append + * + * Return: zero if successful, or a negative error code */ int snd_card_add_dev_attr(struct snd_card *card, const struct attribute_group *group) @@ -728,6 +862,11 @@ int snd_card_add_dev_attr(struct snd_card *card, } EXPORT_SYMBOL_GPL(snd_card_add_dev_attr); +static void trigger_card_free(void *data) +{ + snd_card_free(data); +} + /** * snd_card_register - register the soundcard * @card: soundcard structure @@ -751,9 +890,19 @@ int snd_card_register(struct snd_card *card) if (err < 0) return err; card->registered = true; + } else { + if (card->managed) + devm_remove_action(card->dev, trigger_card_free, card); } - if ((err = snd_device_register_all(card)) < 0) + if (card->managed) { + err = devm_add_action(card->dev, trigger_card_free, card); + if (err < 0) + return err; + } + + err = snd_device_register_all(card); + if (err < 0) return err; mutex_lock(&snd_card_mutex); if (snd_cards[card->number]) { @@ -796,7 +945,8 @@ static void snd_card_info_read(struct snd_info_entry *entry, for (idx = count = 0; idx < SNDRV_CARDS; idx++) { mutex_lock(&snd_card_mutex); - if ((card = snd_cards[idx]) != NULL) { + card = snd_cards[idx]; + if (card) { count++; snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n", idx, @@ -820,7 +970,8 @@ void snd_card_info_read_oss(struct snd_info_buffer *buffer) for (idx = count = 0; idx < SNDRV_CARDS; idx++) { mutex_lock(&snd_card_mutex); - if ((card = snd_cards[idx]) != NULL) { + card = snd_cards[idx]; + if (card) { count++; snd_iprintf(buffer, "%s\n", card->longname); } @@ -842,7 +993,8 @@ static void snd_card_module_info_read(struct snd_info_entry *entry, for (idx = 0; idx < SNDRV_CARDS; idx++) { mutex_lock(&snd_card_mutex); - if ((card = snd_cards[idx]) != NULL) + card = snd_cards[idx]; + if (card) snd_iprintf(buffer, "%2i %s\n", idx, card->module->name); mutex_unlock(&snd_card_mutex); @@ -985,36 +1137,48 @@ EXPORT_SYMBOL(snd_card_file_remove); #ifdef CONFIG_PM /** - * snd_power_wait - wait until the power-state is changed. - * @card: soundcard structure - * @power_state: expected power state + * snd_power_ref_and_wait - wait until the card gets powered up + * @card: soundcard structure + * + * Take the power_ref reference count of the given card, and + * wait until the card gets powered up to SNDRV_CTL_POWER_D0 state. + * The refcount is down again while sleeping until power-up, hence this + * function can be used for syncing the floating control ops accesses, + * typically around calling control ops. * - * Waits until the power-state is changed. + * The caller needs to pull down the refcount via snd_power_unref() later + * no matter whether the error is returned from this function or not. * - * Return: Zero if successful, or a negative error code. + * Return: Zero if successful, or a negative error code. */ -int snd_power_wait(struct snd_card *card, unsigned int power_state) +int snd_power_ref_and_wait(struct snd_card *card) { - wait_queue_entry_t wait; - int result = 0; - - /* fastpath */ - if (snd_power_get_state(card) == power_state) + snd_power_ref(card); + if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0) return 0; - init_waitqueue_entry(&wait, current); - add_wait_queue(&card->power_sleep, &wait); - while (1) { - if (card->shutdown) { - result = -ENODEV; - break; - } - if (snd_power_get_state(card) == power_state) - break; - set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(30 * HZ); - } - remove_wait_queue(&card->power_sleep, &wait); - return result; + wait_event_cmd(card->power_sleep, + card->shutdown || + snd_power_get_state(card) == SNDRV_CTL_POWER_D0, + snd_power_unref(card), snd_power_ref(card)); + return card->shutdown ? -ENODEV : 0; +} +EXPORT_SYMBOL_GPL(snd_power_ref_and_wait); + +/** + * snd_power_wait - wait until the card gets powered up (old form) + * @card: soundcard structure + * + * Wait until the card gets powered up to SNDRV_CTL_POWER_D0 state. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_power_wait(struct snd_card *card) +{ + int ret; + + ret = snd_power_ref_and_wait(card); + snd_power_unref(card); + return ret; } EXPORT_SYMBOL(snd_power_wait); #endif /* CONFIG_PM */ |