[PATCH RFC] ALSA: control: Avoid nested locks at notification
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(-)
diff --git a/include/sound/control.h b/include/sound/control.h index cc3dcc6cfb0f..c40c693853e6 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -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); diff --git a/include/sound/core.h b/include/sound/core.h index 4ea5f66b59d7..3e49fb483340 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -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 */ diff --git a/sound/core/control.c b/sound/core/control.c index 82aa1af1d1d8..70515521fc49 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -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); } diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 3cadd40100f3..18b366207a9c 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -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) diff --git a/sound/core/init.c b/sound/core/init.c index df0c22480375..34bda15ad9ef 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -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);
On 23. 05. 23 17:52, Takashi Iwai wrote:
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.
I'm not convinced about this transition. What about to move the layer notifications to a workqueue to reorder the controls_rwsem locking (kctl access) ?
Signed-off-by: Takashi Iwai tiwai@suse.de
I noticed the nested lock while looking at the pending bug report
From the log or code ?
Jaroslav
On Tue, 23 May 2023 18:53:11 +0200, Jaroslav Kysela wrote:
On 23. 05. 23 17:52, Takashi Iwai wrote:
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.
I'm not convinced about this transition. What about to move the layer notifications to a workqueue to reorder the controls_rwsem locking (kctl access) ?
It'll need allocation of each new notify event, no?
IMO, keeping the list of ops in each card instance may have a room for more possible improvement. The current patch chains always all lops in the card's lops_list, but when the register callback returns a certain error code for the uninteresting lops, we can skip chaining it. Then the unnecessary hook invocation at each notification can be avoided.
Signed-off-by: Takashi Iwai tiwai@suse.de
I noticed the nested lock while looking at the pending bug report
From the log or code ?
The code.
And, control_led.c has also a global mutex lock that is applied for almost all code paths. If the hook is called from all cards, this should be optimized as well. (OTOH, if it's called only from one bound card, it won't matter much.)
thanks,
Takashi
participants (2)
-
Jaroslav Kysela
-
Takashi Iwai