|
@@ -56,6 +56,7 @@ MODULE_LICENSE("GPL");
|
|
|
#define DRIVER_LOADED (1<<0)
|
|
|
#define DRIVER_REQUESTED (1<<1)
|
|
|
#define DRIVER_LOCKED (1<<2)
|
|
|
+#define DRIVER_REQUESTING (1<<3)
|
|
|
|
|
|
struct ops_list {
|
|
|
char id[ID_LEN]; /* driver id */
|
|
@@ -127,42 +128,82 @@ static void snd_seq_device_info(struct snd_info_entry *entry,
|
|
|
|
|
|
#ifdef CONFIG_MODULES
|
|
|
/* avoid auto-loading during module_init() */
|
|
|
-static int snd_seq_in_init;
|
|
|
+static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */
|
|
|
void snd_seq_autoload_lock(void)
|
|
|
{
|
|
|
- snd_seq_in_init++;
|
|
|
+ atomic_inc(&snd_seq_in_init);
|
|
|
}
|
|
|
|
|
|
void snd_seq_autoload_unlock(void)
|
|
|
{
|
|
|
- snd_seq_in_init--;
|
|
|
+ atomic_dec(&snd_seq_in_init);
|
|
|
}
|
|
|
-#endif
|
|
|
|
|
|
-void snd_seq_device_load_drivers(void)
|
|
|
+static void autoload_drivers(void)
|
|
|
{
|
|
|
-#ifdef CONFIG_MODULES
|
|
|
- struct ops_list *ops;
|
|
|
+ /* avoid reentrance */
|
|
|
+ if (atomic_inc_return(&snd_seq_in_init) == 1) {
|
|
|
+ struct ops_list *ops;
|
|
|
+
|
|
|
+ mutex_lock(&ops_mutex);
|
|
|
+ list_for_each_entry(ops, &opslist, list) {
|
|
|
+ if ((ops->driver & DRIVER_REQUESTING) &&
|
|
|
+ !(ops->driver & DRIVER_REQUESTED)) {
|
|
|
+ ops->used++;
|
|
|
+ mutex_unlock(&ops_mutex);
|
|
|
+ ops->driver |= DRIVER_REQUESTED;
|
|
|
+ request_module("snd-%s", ops->id);
|
|
|
+ mutex_lock(&ops_mutex);
|
|
|
+ ops->used--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mutex_unlock(&ops_mutex);
|
|
|
+ }
|
|
|
+ atomic_dec(&snd_seq_in_init);
|
|
|
+}
|
|
|
|
|
|
- /* Calling request_module during module_init()
|
|
|
- * may cause blocking.
|
|
|
- */
|
|
|
- if (snd_seq_in_init)
|
|
|
- return;
|
|
|
+static void call_autoload(struct work_struct *work)
|
|
|
+{
|
|
|
+ autoload_drivers();
|
|
|
+}
|
|
|
|
|
|
- mutex_lock(&ops_mutex);
|
|
|
- list_for_each_entry(ops, &opslist, list) {
|
|
|
- if (! (ops->driver & DRIVER_LOADED) &&
|
|
|
- ! (ops->driver & DRIVER_REQUESTED)) {
|
|
|
- ops->used++;
|
|
|
- mutex_unlock(&ops_mutex);
|
|
|
- ops->driver |= DRIVER_REQUESTED;
|
|
|
- request_module("snd-%s", ops->id);
|
|
|
- mutex_lock(&ops_mutex);
|
|
|
- ops->used--;
|
|
|
- }
|
|
|
+static DECLARE_WORK(autoload_work, call_autoload);
|
|
|
+
|
|
|
+static void try_autoload(struct ops_list *ops)
|
|
|
+{
|
|
|
+ if (!ops->driver) {
|
|
|
+ ops->driver |= DRIVER_REQUESTING;
|
|
|
+ schedule_work(&autoload_work);
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+static void queue_autoload_drivers(void)
|
|
|
+{
|
|
|
+ struct ops_list *ops;
|
|
|
+
|
|
|
+ mutex_lock(&ops_mutex);
|
|
|
+ list_for_each_entry(ops, &opslist, list)
|
|
|
+ try_autoload(ops);
|
|
|
mutex_unlock(&ops_mutex);
|
|
|
+}
|
|
|
+
|
|
|
+void snd_seq_autoload_init(void)
|
|
|
+{
|
|
|
+ atomic_dec(&snd_seq_in_init);
|
|
|
+#ifdef CONFIG_SND_SEQUENCER_MODULE
|
|
|
+ /* initial autoload only when snd-seq is a module */
|
|
|
+ queue_autoload_drivers();
|
|
|
+#endif
|
|
|
+}
|
|
|
+#else
|
|
|
+#define try_autoload(ops) /* NOP */
|
|
|
+#endif
|
|
|
+
|
|
|
+void snd_seq_device_load_drivers(void)
|
|
|
+{
|
|
|
+#ifdef CONFIG_MODULES
|
|
|
+ queue_autoload_drivers();
|
|
|
+ flush_work(&autoload_work);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -214,13 +255,14 @@ int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize,
|
|
|
ops->num_devices++;
|
|
|
mutex_unlock(&ops->reg_mutex);
|
|
|
|
|
|
- unlock_driver(ops);
|
|
|
-
|
|
|
if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
|
|
|
snd_seq_device_free(dev);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+ try_autoload(ops);
|
|
|
+ unlock_driver(ops);
|
|
|
+
|
|
|
if (result)
|
|
|
*result = dev;
|
|
|
|
|
@@ -318,16 +360,12 @@ int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
|
|
|
entry->init_device == NULL || entry->free_device == NULL)
|
|
|
return -EINVAL;
|
|
|
|
|
|
- snd_seq_autoload_lock();
|
|
|
ops = find_driver(id, 1);
|
|
|
- if (ops == NULL) {
|
|
|
- snd_seq_autoload_unlock();
|
|
|
+ if (ops == NULL)
|
|
|
return -ENOMEM;
|
|
|
- }
|
|
|
if (ops->driver & DRIVER_LOADED) {
|
|
|
pr_warn("ALSA: seq: driver_register: driver '%s' already exists\n", id);
|
|
|
unlock_driver(ops);
|
|
|
- snd_seq_autoload_unlock();
|
|
|
return -EBUSY;
|
|
|
}
|
|
|
|
|
@@ -344,7 +382,6 @@ int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry,
|
|
|
mutex_unlock(&ops->reg_mutex);
|
|
|
|
|
|
unlock_driver(ops);
|
|
|
- snd_seq_autoload_unlock();
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -554,6 +591,9 @@ static int __init alsa_seq_device_init(void)
|
|
|
|
|
|
static void __exit alsa_seq_device_exit(void)
|
|
|
{
|
|
|
+#ifdef CONFIG_MODULES
|
|
|
+ cancel_work_sync(&autoload_work);
|
|
|
+#endif
|
|
|
remove_drivers();
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
snd_info_free_entry(info_entry);
|
|
@@ -570,6 +610,7 @@ EXPORT_SYMBOL(snd_seq_device_new);
|
|
|
EXPORT_SYMBOL(snd_seq_device_register_driver);
|
|
|
EXPORT_SYMBOL(snd_seq_device_unregister_driver);
|
|
|
#ifdef CONFIG_MODULES
|
|
|
+EXPORT_SYMBOL(snd_seq_autoload_init);
|
|
|
EXPORT_SYMBOL(snd_seq_autoload_lock);
|
|
|
EXPORT_SYMBOL(snd_seq_autoload_unlock);
|
|
|
#endif
|