@@ -156,7 +156,7 @@ int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn);
#endif
int snd_ctl_request_layer(const char *module_name);
-void snd_ctl_register_layer(struct snd_ctl_layer_ops *lops);
+int snd_ctl_register_layer(struct snd_ctl_layer_ops *lops);
void snd_ctl_disconnect_layer(struct snd_ctl_layer_ops *lops);
int snd_ctl_get_preferred_subdevice(struct snd_card *card, int type);
@@ -75,6 +75,14 @@ struct snd_device {
#define snd_device(n) list_entry(n, struct snd_device, list)
+/* control layer ops */
+struct snd_ctl_layer_ops;
+
+struct snd_ctl_lops_list {
+ struct snd_ctl_layer_ops *lops;
+ struct list_head list;
+};
+
/* main structure for soundcard */
struct snd_card {
@@ -104,6 +112,7 @@ struct snd_card {
size_t user_ctl_alloc_size; // current memory allocation by user controls.
struct list_head controls; /* all controls for this card */
struct list_head ctl_files; /* active control files */
+ struct list_head lops_list; /* list of associated control layer ops */
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
struct xarray ctl_numids; /* hash table for numids */
struct xarray ctl_hash; /* hash table for ctl id matching */
@@ -202,15 +202,13 @@ 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;
+ struct snd_ctl_lops_list *llist;
id.index += ioff;
id.numid += ioff;
snd_ctl_notify(card, mask, &id);
- down_read(&snd_ctl_layer_rwsem);
- for (lops = snd_ctl_layer; lops; lops = lops->next)
- lops->lnotify(card, mask, kctl, ioff);
- up_read(&snd_ctl_layer_rwsem);
+ list_for_each_entry(llist, &card->lops_list, list)
+ llist->lops->lnotify(card, mask, kctl, ioff);
}
EXPORT_SYMBOL(snd_ctl_notify_one);
@@ -710,11 +708,8 @@ int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
}
snd_ctl_build_ioff(id, kctl, index_offset);
- downgrade_write(&card->controls_rwsem);
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, index_offset);
- up_read(&card->controls_rwsem);
- return 1;
-
+ ret = 1;
unlock:
up_write(&card->controls_rwsem);
return ret;
@@ -1283,16 +1278,16 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
down_write(&card->controls_rwsem);
kctl = snd_ctl_find_id(card, &control->id);
if (kctl == NULL) {
- up_write(&card->controls_rwsem);
- return -ENOENT;
+ result = -ENOENT;
+ goto unlock;
}
index_offset = snd_ctl_get_ioff(kctl, &control->id);
vd = &kctl->vd[index_offset];
if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL ||
(file && vd->owner && vd->owner != file)) {
- up_write(&card->controls_rwsem);
- return -EPERM;
+ result = -EPERM;
+ goto unlock;
}
snd_ctl_build_ioff(&control->id, kctl, index_offset);
@@ -1311,20 +1306,15 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
if (!result)
result = kctl->put(kctl, control);
snd_power_unref(card);
- if (result < 0) {
- up_write(&card->controls_rwsem);
- return result;
- }
+ if (result < 0)
+ goto unlock;
- if (result > 0) {
- downgrade_write(&card->controls_rwsem);
+ if (result > 0)
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, index_offset);
- up_read(&card->controls_rwsem);
- } else {
- up_write(&card->controls_rwsem);
- }
-
- return 0;
+ result = 0;
+ unlock:
+ up_write(&card->controls_rwsem);
+ return result;
}
static int snd_ctl_elem_write_user(struct snd_ctl_file *file,
@@ -2225,6 +2215,58 @@ int snd_ctl_request_layer(const char *module_name)
}
EXPORT_SYMBOL_GPL(snd_ctl_request_layer);
+/* register a control layer to the given card */
+static int snd_ctl_card_register_lops(struct snd_card *card,
+ struct snd_ctl_layer_ops *lops)
+{
+ struct snd_ctl_lops_list *llist;
+
+ llist = kmalloc(sizeof(*llist), GFP_KERNEL);
+ if (!llist)
+ return -ENOMEM;
+ llist->lops = lops;
+ down_read(&card->controls_rwsem);
+ list_add_tail(&llist->list, &card->lops_list);
+ lops->lregister(card);
+ up_read(&card->controls_rwsem);
+ return 0;
+}
+
+static void delete_lops_list(struct snd_card *card,
+ struct snd_ctl_lops_list *llist)
+{
+ list_del(&llist->list);
+ llist->lops->ldisconnect(card);
+ kfree(llist);
+}
+
+/* disconnect a control layer from the given card */
+static void snd_ctl_card_disconnect_lops(struct snd_card *card,
+ struct snd_ctl_layer_ops *lops)
+{
+ struct snd_ctl_lops_list *llist;
+
+ down_read(&card->controls_rwsem);
+ list_for_each_entry(llist, &card->lops_list, list) {
+ if (llist->lops == lops) {
+ delete_lops_list(card, llist);
+ break;
+ }
+ }
+ up_read(&card->controls_rwsem);
+}
+
+/* disconnect all control layers from the given card */
+static void snd_ctl_card_disconnect_all_lops(struct snd_card *card)
+{
+ struct snd_ctl_lops_list *llist, *next;
+
+ down_read(&card->controls_rwsem);
+ list_for_each_entry_safe(llist, next, &card->lops_list, list)
+ delete_lops_list(card, llist);
+ up_read(&card->controls_rwsem);
+}
+
/**
* snd_ctl_register_layer - register new control layer
* @lops: operation structure
@@ -2232,10 +2274,10 @@ EXPORT_SYMBOL_GPL(snd_ctl_request_layer);
* The new layer can track all control elements and do additional
* operations on top (like audio LED handling).
*/
-void snd_ctl_register_layer(struct snd_ctl_layer_ops *lops)
+int snd_ctl_register_layer(struct snd_ctl_layer_ops *lops)
{
struct snd_card *card;
- int card_number;
+ int ret, card_number;
down_write(&snd_ctl_layer_rwsem);
lops->next = snd_ctl_layer;
@@ -2244,12 +2286,17 @@ void snd_ctl_register_layer(struct snd_ctl_layer_ops *lops)
for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {
card = snd_card_ref(card_number);
if (card) {
- down_read(&card->controls_rwsem);
- lops->lregister(card);
- up_read(&card->controls_rwsem);
+ ret = snd_ctl_card_register_lops(card, lops);
+ if (ret < 0)
+ goto error;
snd_card_unref(card);
}
}
+ return 0;
+
+ error:
+ snd_ctl_disconnect_layer(lops);
+ return ret;
}
EXPORT_SYMBOL_GPL(snd_ctl_register_layer);
@@ -2264,6 +2311,16 @@ EXPORT_SYMBOL_GPL(snd_ctl_register_layer);
void snd_ctl_disconnect_layer(struct snd_ctl_layer_ops *lops)
{
struct snd_ctl_layer_ops *lops2, *prev_lops2;
+ struct snd_card *card;
+ int card_number;
+
+ for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {
+ card = snd_card_ref(card_number);
+ if (card) {
+ snd_ctl_card_disconnect_lops(card, lops);
+ snd_card_unref(card);
+ }
+ }
down_write(&snd_ctl_layer_rwsem);
for (lops2 = snd_ctl_layer, prev_lops2 = NULL; lops2; lops2 = lops2->next) {
@@ -2310,13 +2367,16 @@ static int snd_ctl_dev_register(struct snd_device *device)
&snd_ctl_f_ops, card, &card->ctl_dev);
if (err < 0)
return err;
- down_read(&card->controls_rwsem);
down_read(&snd_ctl_layer_rwsem);
- for (lops = snd_ctl_layer; lops; lops = lops->next)
- lops->lregister(card);
+ for (lops = snd_ctl_layer; lops; lops = lops->next) {
+ err = snd_ctl_card_register_lops(card, lops);
+ if (err < 0) {
+ snd_ctl_card_disconnect_all_lops(card);
+ break;
+ }
+ }
up_read(&snd_ctl_layer_rwsem);
- up_read(&card->controls_rwsem);
- return 0;
+ return err;
}
/*
@@ -2326,7 +2386,6 @@ static int snd_ctl_dev_disconnect(struct snd_device *device)
{
struct snd_card *card = device->device_data;
struct snd_ctl_file *ctl;
- struct snd_ctl_layer_ops *lops;
unsigned long flags;
read_lock_irqsave(&card->ctl_files_rwlock, flags);
@@ -2336,12 +2395,7 @@ static int snd_ctl_dev_disconnect(struct snd_device *device)
}
read_unlock_irqrestore(&card->ctl_files_rwlock, flags);
- down_read(&card->controls_rwsem);
- down_read(&snd_ctl_layer_rwsem);
- for (lops = snd_ctl_layer; lops; lops = lops->next)
- lops->ldisconnect(card);
- up_read(&snd_ctl_layer_rwsem);
- up_read(&card->controls_rwsem);
+ snd_ctl_card_disconnect_all_lops(card);
return snd_unregister_device(&card->ctl_dev);
}
@@ -762,8 +762,7 @@ static int __init snd_ctl_led_init(void)
return -ENOMEM;
}
}
- snd_ctl_register_layer(&snd_ctl_led_lops);
- return 0;
+ return snd_ctl_register_layer(&snd_ctl_led_lops);
}
static void __exit snd_ctl_led_exit(void)
@@ -314,6 +314,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent,
rwlock_init(&card->ctl_files_rwlock);
INIT_LIST_HEAD(&card->controls);
INIT_LIST_HEAD(&card->ctl_files);
+ INIT_LIST_HEAD(&card->lops_list);
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
xa_init(&card->ctl_numids);
xa_init(&card->ctl_hash);
The new control layer stuff introduced the nested rwsem for managing the list of registered control layer ops. Since then, a global snd_ctl_layer_rwsem is always read at each time a control notification is sent via snd_ctl_notify*() in a nested matter inside the card's controls_rwsem lock. This also required a bit complicated way of the lock at snd_ctl_activate_id() and snd_ctl_elem_write() with the downgrade of rwsem. This patch is an attempt to simplify the handling of ctl layer ops. Now, instead of traversing the global linked list, we keep a local list of lops in each card instance. This reduces the need of the global snd_ctl_layer_rwsem lock at snd_ctl_notify*() invocation. And, since the nested lock is avoided in most places, we can also avoid the rwsem downgrade hack in the above, too. Since the local list entry is created dynamically, snd_ctl_register_layer() may return an error, and the caller needs to check the return value. Signed-off-by: Takashi Iwai <tiwai@suse.de> --- I noticed the nested lock while looking at the pending bug report ("USB sound card freezes USB after resume from suspend" -- which hasn't been resolved yet). Only very lightly tested. include/sound/control.h | 2 +- include/sound/core.h | 9 +++ sound/core/control.c | 138 +++++++++++++++++++++++++++------------ sound/core/control_led.c | 3 +- sound/core/init.c | 1 + 5 files changed, 108 insertions(+), 45 deletions(-)