ALSA: seq: bind seq driver automatically
Currently the sequencer module binding is performed independently from the card module itself. The reason behind it is to keep the sequencer stuff optional and allow the system running without it (e.g. for using PCM or rawmidi only). This works in most cases, but a remaining problem is that the binding isn't done automatically when a new driver module is probed. Typically this becomes visible when a hotplug driver like usb audio is used. This patch tries to address this and other potential issues. First, the seq-binder (seq_device.c) tries to load a missing driver module at creating a new device object. This is done asynchronously in a workq for avoiding the deadlock (modprobe call in module init path). This action, however, should be enabled only when the sequencer stuff was already initialized, i.e. snd-seq module was already loaded. For that, a new function, snd_seq_autoload_init() is introduced here; this clears the blocking of autoloading, and also tries to load all pending driver modules. Reported-by: Adam Goode <agoode@chromium.org> Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
54841a06c5
commit
68ab61084d
@ -108,9 +108,13 @@ int snd_seq_event_port_detach(int client, int port);
|
|||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
void snd_seq_autoload_lock(void);
|
void snd_seq_autoload_lock(void);
|
||||||
void snd_seq_autoload_unlock(void);
|
void snd_seq_autoload_unlock(void);
|
||||||
|
void snd_seq_autoload_init(void);
|
||||||
|
#define snd_seq_autoload_exit() snd_seq_autoload_lock()
|
||||||
#else
|
#else
|
||||||
#define snd_seq_autoload_lock()
|
#define snd_seq_autoload_lock()
|
||||||
#define snd_seq_autoload_unlock()
|
#define snd_seq_autoload_unlock()
|
||||||
|
#define snd_seq_autoload_init()
|
||||||
|
#define snd_seq_autoload_exit()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* __SOUND_SEQ_KERNEL_H */
|
#endif /* __SOUND_SEQ_KERNEL_H */
|
||||||
|
@ -110,6 +110,7 @@ static int __init alsa_seq_init(void)
|
|||||||
if ((err = snd_seq_system_client_init()) < 0)
|
if ((err = snd_seq_system_client_init()) < 0)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
snd_seq_autoload_init();
|
||||||
error:
|
error:
|
||||||
snd_seq_autoload_unlock();
|
snd_seq_autoload_unlock();
|
||||||
return err;
|
return err;
|
||||||
@ -131,6 +132,8 @@ static void __exit alsa_seq_exit(void)
|
|||||||
|
|
||||||
/* release event memory */
|
/* release event memory */
|
||||||
snd_sequencer_memory_done();
|
snd_sequencer_memory_done();
|
||||||
|
|
||||||
|
snd_seq_autoload_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(alsa_seq_init)
|
module_init(alsa_seq_init)
|
||||||
|
@ -56,6 +56,7 @@ MODULE_LICENSE("GPL");
|
|||||||
#define DRIVER_LOADED (1<<0)
|
#define DRIVER_LOADED (1<<0)
|
||||||
#define DRIVER_REQUESTED (1<<1)
|
#define DRIVER_REQUESTED (1<<1)
|
||||||
#define DRIVER_LOCKED (1<<2)
|
#define DRIVER_LOCKED (1<<2)
|
||||||
|
#define DRIVER_REQUESTING (1<<3)
|
||||||
|
|
||||||
struct ops_list {
|
struct ops_list {
|
||||||
char id[ID_LEN]; /* driver id */
|
char id[ID_LEN]; /* driver id */
|
||||||
@ -127,7 +128,7 @@ static void snd_seq_device_info(struct snd_info_entry *entry,
|
|||||||
|
|
||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
/* avoid auto-loading during module_init() */
|
/* avoid auto-loading during module_init() */
|
||||||
static atomic_t snd_seq_in_init = ATOMIC_INIT(0);
|
static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */
|
||||||
void snd_seq_autoload_lock(void)
|
void snd_seq_autoload_lock(void)
|
||||||
{
|
{
|
||||||
atomic_inc(&snd_seq_in_init);
|
atomic_inc(&snd_seq_in_init);
|
||||||
@ -137,32 +138,72 @@ void snd_seq_autoload_unlock(void)
|
|||||||
{
|
{
|
||||||
atomic_dec(&snd_seq_in_init);
|
atomic_dec(&snd_seq_in_init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void autoload_drivers(void)
|
||||||
|
{
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void call_autoload(struct work_struct *work)
|
||||||
|
{
|
||||||
|
autoload_drivers();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
#endif
|
||||||
|
|
||||||
void snd_seq_device_load_drivers(void)
|
void snd_seq_device_load_drivers(void)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
struct ops_list *ops;
|
queue_autoload_drivers();
|
||||||
|
flush_work(&autoload_work);
|
||||||
/* Calling request_module during module_init()
|
|
||||||
* may cause blocking.
|
|
||||||
*/
|
|
||||||
if (atomic_read(&snd_seq_in_init))
|
|
||||||
return;
|
|
||||||
|
|
||||||
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--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex_unlock(&ops_mutex);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,13 +255,14 @@ int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize,
|
|||||||
ops->num_devices++;
|
ops->num_devices++;
|
||||||
mutex_unlock(&ops->reg_mutex);
|
mutex_unlock(&ops->reg_mutex);
|
||||||
|
|
||||||
unlock_driver(ops);
|
|
||||||
|
|
||||||
if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
|
if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) {
|
||||||
snd_seq_device_free(dev);
|
snd_seq_device_free(dev);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try_autoload(ops);
|
||||||
|
unlock_driver(ops);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
*result = dev;
|
*result = dev;
|
||||||
|
|
||||||
@ -554,6 +596,9 @@ static int __init alsa_seq_device_init(void)
|
|||||||
|
|
||||||
static void __exit alsa_seq_device_exit(void)
|
static void __exit alsa_seq_device_exit(void)
|
||||||
{
|
{
|
||||||
|
#ifdef CONFIG_MODULES
|
||||||
|
cancel_work_sync(&autoload_work);
|
||||||
|
#endif
|
||||||
remove_drivers();
|
remove_drivers();
|
||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
snd_info_free_entry(info_entry);
|
snd_info_free_entry(info_entry);
|
||||||
@ -570,6 +615,7 @@ EXPORT_SYMBOL(snd_seq_device_new);
|
|||||||
EXPORT_SYMBOL(snd_seq_device_register_driver);
|
EXPORT_SYMBOL(snd_seq_device_register_driver);
|
||||||
EXPORT_SYMBOL(snd_seq_device_unregister_driver);
|
EXPORT_SYMBOL(snd_seq_device_unregister_driver);
|
||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
|
EXPORT_SYMBOL(snd_seq_autoload_init);
|
||||||
EXPORT_SYMBOL(snd_seq_autoload_lock);
|
EXPORT_SYMBOL(snd_seq_autoload_lock);
|
||||||
EXPORT_SYMBOL(snd_seq_autoload_unlock);
|
EXPORT_SYMBOL(snd_seq_autoload_unlock);
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user