| /* | |
| * ALSA sequencer device management | |
| * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| * | |
| * | |
| *---------------------------------------------------------------- | |
| * | |
| * This device handler separates the card driver module from sequencer | |
| * stuff (sequencer core, synth drivers, etc), so that user can avoid | |
| * to spend unnecessary resources e.g. if he needs only listening to | |
| * MP3s. | |
| * | |
| * The card (or lowlevel) driver creates a sequencer device entry | |
| * via snd_seq_device_new(). This is an entry pointer to communicate | |
| * with the sequencer device "driver", which is involved with the | |
| * actual part to communicate with the sequencer core. | |
| * Each sequencer device entry has an id string and the corresponding | |
| * driver with the same id is loaded when required. For example, | |
| * lowlevel codes to access emu8000 chip on sbawe card are included in | |
| * emu8000-synth module. To activate this module, the hardware | |
| * resources like i/o port are passed via snd_seq_device argument. | |
| * | |
| */ | |
| #include <linux/init.h> | |
| #include <sound/core.h> | |
| #include <sound/info.h> | |
| #include <sound/seq_device.h> | |
| #include <sound/seq_kernel.h> | |
| #include <sound/initval.h> | |
| #include <linux/kmod.h> | |
| #include <linux/slab.h> | |
| #include <linux/mutex.h> | |
| MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); | |
| MODULE_DESCRIPTION("ALSA sequencer device management"); | |
| MODULE_LICENSE("GPL"); | |
| /* driver state */ | |
| #define DRIVER_EMPTY 0 | |
| #define DRIVER_LOADED (1<<0) | |
| #define DRIVER_REQUESTED (1<<1) | |
| #define DRIVER_LOCKED (1<<2) | |
| struct ops_list { | |
| char id[ID_LEN]; /* driver id */ | |
| int driver; /* driver state */ | |
| int used; /* reference counter */ | |
| int argsize; /* argument size */ | |
| /* operators */ | |
| struct snd_seq_dev_ops ops; | |
| /* registred devices */ | |
| struct list_head dev_list; /* list of devices */ | |
| int num_devices; /* number of associated devices */ | |
| int num_init_devices; /* number of initialized devices */ | |
| struct mutex reg_mutex; | |
| struct list_head list; /* next driver */ | |
| }; | |
| static LIST_HEAD(opslist); | |
| static int num_ops; | |
| static DEFINE_MUTEX(ops_mutex); | |
| #ifdef CONFIG_PROC_FS | |
| static struct snd_info_entry *info_entry; | |
| #endif | |
| /* | |
| * prototypes | |
| */ | |
| static int snd_seq_device_free(struct snd_seq_device *dev); | |
| static int snd_seq_device_dev_free(struct snd_device *device); | |
| static int snd_seq_device_dev_register(struct snd_device *device); | |
| static int snd_seq_device_dev_disconnect(struct snd_device *device); | |
| static int init_device(struct snd_seq_device *dev, struct ops_list *ops); | |
| static int free_device(struct snd_seq_device *dev, struct ops_list *ops); | |
| static struct ops_list *find_driver(char *id, int create_if_empty); | |
| static struct ops_list *create_driver(char *id); | |
| static void unlock_driver(struct ops_list *ops); | |
| static void remove_drivers(void); | |
| /* | |
| * show all drivers and their status | |
| */ | |
| #ifdef CONFIG_PROC_FS | |
| static void snd_seq_device_info(struct snd_info_entry *entry, | |
| struct snd_info_buffer *buffer) | |
| { | |
| struct ops_list *ops; | |
| mutex_lock(&ops_mutex); | |
| list_for_each_entry(ops, &opslist, list) { | |
| snd_iprintf(buffer, "snd-%s%s%s%s,%d\n", | |
| ops->id, | |
| ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""), | |
| ops->driver & DRIVER_REQUESTED ? ",requested" : "", | |
| ops->driver & DRIVER_LOCKED ? ",locked" : "", | |
| ops->num_devices); | |
| } | |
| mutex_unlock(&ops_mutex); | |
| } | |
| #endif | |
| /* | |
| * load all registered drivers (called from seq_clientmgr.c) | |
| */ | |
| #ifdef CONFIG_MODULES | |
| /* avoid auto-loading during module_init() */ | |
| static int snd_seq_in_init; | |
| void snd_seq_autoload_lock(void) | |
| { | |
| snd_seq_in_init++; | |
| } | |
| void snd_seq_autoload_unlock(void) | |
| { | |
| snd_seq_in_init--; | |
| } | |
| #endif | |
| void snd_seq_device_load_drivers(void) | |
| { | |
| #ifdef CONFIG_MODULES | |
| struct ops_list *ops; | |
| /* Calling request_module during module_init() | |
| * may cause blocking. | |
| */ | |
| if (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 | |
| } | |
| /* | |
| * register a sequencer device | |
| * card = card info (NULL allowed) | |
| * device = device number (if any) | |
| * id = id of driver | |
| * result = return pointer (NULL allowed if unnecessary) | |
| */ | |
| int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize, | |
| struct snd_seq_device **result) | |
| { | |
| struct snd_seq_device *dev; | |
| struct ops_list *ops; | |
| int err; | |
| static struct snd_device_ops dops = { | |
| .dev_free = snd_seq_device_dev_free, | |
| .dev_register = snd_seq_device_dev_register, | |
| .dev_disconnect = snd_seq_device_dev_disconnect, | |
| }; | |
| if (result) | |
| *result = NULL; | |
| if (snd_BUG_ON(!id)) | |
| return -EINVAL; | |
| ops = find_driver(id, 1); | |
| if (ops == NULL) | |
| return -ENOMEM; | |
| dev = kzalloc(sizeof(*dev)*2 + argsize, GFP_KERNEL); | |
| if (dev == NULL) { | |
| unlock_driver(ops); | |
| return -ENOMEM; | |
| } | |
| /* set up device info */ | |
| dev->card = card; | |
| dev->device = device; | |
| strlcpy(dev->id, id, sizeof(dev->id)); | |
| dev->argsize = argsize; | |
| dev->status = SNDRV_SEQ_DEVICE_FREE; | |
| /* add this device to the list */ | |
| mutex_lock(&ops->reg_mutex); | |
| list_add_tail(&dev->list, &ops->dev_list); | |
| 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; | |
| } | |
| if (result) | |
| *result = dev; | |
| return 0; | |
| } | |
| /* | |
| * free the existing device | |
| */ | |
| static int snd_seq_device_free(struct snd_seq_device *dev) | |
| { | |
| struct ops_list *ops; | |
| if (snd_BUG_ON(!dev)) | |
| return -EINVAL; | |
| ops = find_driver(dev->id, 0); | |
| if (ops == NULL) | |
| return -ENXIO; | |
| /* remove the device from the list */ | |
| mutex_lock(&ops->reg_mutex); | |
| list_del(&dev->list); | |
| ops->num_devices--; | |
| mutex_unlock(&ops->reg_mutex); | |
| free_device(dev, ops); | |
| if (dev->private_free) | |
| dev->private_free(dev); | |
| kfree(dev); | |
| unlock_driver(ops); | |
| return 0; | |
| } | |
| static int snd_seq_device_dev_free(struct snd_device *device) | |
| { | |
| struct snd_seq_device *dev = device->device_data; | |
| return snd_seq_device_free(dev); | |
| } | |
| /* | |
| * register the device | |
| */ | |
| static int snd_seq_device_dev_register(struct snd_device *device) | |
| { | |
| struct snd_seq_device *dev = device->device_data; | |
| struct ops_list *ops; | |
| ops = find_driver(dev->id, 0); | |
| if (ops == NULL) | |
| return -ENOENT; | |
| /* initialize this device if the corresponding driver was | |
| * already loaded | |
| */ | |
| if (ops->driver & DRIVER_LOADED) | |
| init_device(dev, ops); | |
| unlock_driver(ops); | |
| return 0; | |
| } | |
| /* | |
| * disconnect the device | |
| */ | |
| static int snd_seq_device_dev_disconnect(struct snd_device *device) | |
| { | |
| struct snd_seq_device *dev = device->device_data; | |
| struct ops_list *ops; | |
| ops = find_driver(dev->id, 0); | |
| if (ops == NULL) | |
| return -ENOENT; | |
| free_device(dev, ops); | |
| unlock_driver(ops); | |
| return 0; | |
| } | |
| /* | |
| * register device driver | |
| * id = driver id | |
| * entry = driver operators - duplicated to each instance | |
| */ | |
| int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry, | |
| int argsize) | |
| { | |
| struct ops_list *ops; | |
| struct snd_seq_device *dev; | |
| if (id == NULL || entry == NULL || | |
| 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(); | |
| return -ENOMEM; | |
| } | |
| if (ops->driver & DRIVER_LOADED) { | |
| snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id); | |
| unlock_driver(ops); | |
| snd_seq_autoload_unlock(); | |
| return -EBUSY; | |
| } | |
| mutex_lock(&ops->reg_mutex); | |
| /* copy driver operators */ | |
| ops->ops = *entry; | |
| ops->driver |= DRIVER_LOADED; | |
| ops->argsize = argsize; | |
| /* initialize existing devices if necessary */ | |
| list_for_each_entry(dev, &ops->dev_list, list) { | |
| init_device(dev, ops); | |
| } | |
| mutex_unlock(&ops->reg_mutex); | |
| unlock_driver(ops); | |
| snd_seq_autoload_unlock(); | |
| return 0; | |
| } | |
| /* | |
| * create driver record | |
| */ | |
| static struct ops_list * create_driver(char *id) | |
| { | |
| struct ops_list *ops; | |
| ops = kzalloc(sizeof(*ops), GFP_KERNEL); | |
| if (ops == NULL) | |
| return ops; | |
| /* set up driver entry */ | |
| strlcpy(ops->id, id, sizeof(ops->id)); | |
| mutex_init(&ops->reg_mutex); | |
| /* | |
| * The ->reg_mutex locking rules are per-driver, so we create | |
| * separate per-driver lock classes: | |
| */ | |
| lockdep_set_class(&ops->reg_mutex, (struct lock_class_key *)id); | |
| ops->driver = DRIVER_EMPTY; | |
| INIT_LIST_HEAD(&ops->dev_list); | |
| /* lock this instance */ | |
| ops->used = 1; | |
| /* register driver entry */ | |
| mutex_lock(&ops_mutex); | |
| list_add_tail(&ops->list, &opslist); | |
| num_ops++; | |
| mutex_unlock(&ops_mutex); | |
| return ops; | |
| } | |
| /* | |
| * unregister the specified driver | |
| */ | |
| int snd_seq_device_unregister_driver(char *id) | |
| { | |
| struct ops_list *ops; | |
| struct snd_seq_device *dev; | |
| ops = find_driver(id, 0); | |
| if (ops == NULL) | |
| return -ENXIO; | |
| if (! (ops->driver & DRIVER_LOADED) || | |
| (ops->driver & DRIVER_LOCKED)) { | |
| snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", | |
| id, ops->driver); | |
| unlock_driver(ops); | |
| return -EBUSY; | |
| } | |
| /* close and release all devices associated with this driver */ | |
| mutex_lock(&ops->reg_mutex); | |
| ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */ | |
| list_for_each_entry(dev, &ops->dev_list, list) { | |
| free_device(dev, ops); | |
| } | |
| ops->driver = 0; | |
| if (ops->num_init_devices > 0) | |
| snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", | |
| ops->num_init_devices); | |
| mutex_unlock(&ops->reg_mutex); | |
| unlock_driver(ops); | |
| /* remove empty driver entries */ | |
| remove_drivers(); | |
| return 0; | |
| } | |
| /* | |
| * remove empty driver entries | |
| */ | |
| static void remove_drivers(void) | |
| { | |
| struct list_head *head; | |
| mutex_lock(&ops_mutex); | |
| head = opslist.next; | |
| while (head != &opslist) { | |
| struct ops_list *ops = list_entry(head, struct ops_list, list); | |
| if (! (ops->driver & DRIVER_LOADED) && | |
| ops->used == 0 && ops->num_devices == 0) { | |
| head = head->next; | |
| list_del(&ops->list); | |
| kfree(ops); | |
| num_ops--; | |
| } else | |
| head = head->next; | |
| } | |
| mutex_unlock(&ops_mutex); | |
| } | |
| /* | |
| * initialize the device - call init_device operator | |
| */ | |
| static int init_device(struct snd_seq_device *dev, struct ops_list *ops) | |
| { | |
| if (! (ops->driver & DRIVER_LOADED)) | |
| return 0; /* driver is not loaded yet */ | |
| if (dev->status != SNDRV_SEQ_DEVICE_FREE) | |
| return 0; /* already initialized */ | |
| if (ops->argsize != dev->argsize) { | |
| snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", | |
| dev->name, ops->id, ops->argsize, dev->argsize); | |
| return -EINVAL; | |
| } | |
| if (ops->ops.init_device(dev) >= 0) { | |
| dev->status = SNDRV_SEQ_DEVICE_REGISTERED; | |
| ops->num_init_devices++; | |
| } else { | |
| snd_printk(KERN_ERR "init_device failed: %s: %s\n", | |
| dev->name, dev->id); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * release the device - call free_device operator | |
| */ | |
| static int free_device(struct snd_seq_device *dev, struct ops_list *ops) | |
| { | |
| int result; | |
| if (! (ops->driver & DRIVER_LOADED)) | |
| return 0; /* driver is not loaded yet */ | |
| if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED) | |
| return 0; /* not registered */ | |
| if (ops->argsize != dev->argsize) { | |
| snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", | |
| dev->name, ops->id, ops->argsize, dev->argsize); | |
| return -EINVAL; | |
| } | |
| if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) { | |
| dev->status = SNDRV_SEQ_DEVICE_FREE; | |
| dev->driver_data = NULL; | |
| ops->num_init_devices--; | |
| } else { | |
| snd_printk(KERN_ERR "free_device failed: %s: %s\n", | |
| dev->name, dev->id); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * find the matching driver with given id | |
| */ | |
| static struct ops_list * find_driver(char *id, int create_if_empty) | |
| { | |
| struct ops_list *ops; | |
| mutex_lock(&ops_mutex); | |
| list_for_each_entry(ops, &opslist, list) { | |
| if (strcmp(ops->id, id) == 0) { | |
| ops->used++; | |
| mutex_unlock(&ops_mutex); | |
| return ops; | |
| } | |
| } | |
| mutex_unlock(&ops_mutex); | |
| if (create_if_empty) | |
| return create_driver(id); | |
| return NULL; | |
| } | |
| static void unlock_driver(struct ops_list *ops) | |
| { | |
| mutex_lock(&ops_mutex); | |
| ops->used--; | |
| mutex_unlock(&ops_mutex); | |
| } | |
| /* | |
| * module part | |
| */ | |
| static int __init alsa_seq_device_init(void) | |
| { | |
| #ifdef CONFIG_PROC_FS | |
| info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", | |
| snd_seq_root); | |
| if (info_entry == NULL) | |
| return -ENOMEM; | |
| info_entry->content = SNDRV_INFO_CONTENT_TEXT; | |
| info_entry->c.text.read = snd_seq_device_info; | |
| if (snd_info_register(info_entry) < 0) { | |
| snd_info_free_entry(info_entry); | |
| return -ENOMEM; | |
| } | |
| #endif | |
| return 0; | |
| } | |
| static void __exit alsa_seq_device_exit(void) | |
| { | |
| remove_drivers(); | |
| #ifdef CONFIG_PROC_FS | |
| snd_info_free_entry(info_entry); | |
| #endif | |
| if (num_ops) | |
| snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops); | |
| } | |
| module_init(alsa_seq_device_init) | |
| module_exit(alsa_seq_device_exit) | |
| EXPORT_SYMBOL(snd_seq_device_load_drivers); | |
| EXPORT_SYMBOL(snd_seq_device_new); | |
| EXPORT_SYMBOL(snd_seq_device_register_driver); | |
| EXPORT_SYMBOL(snd_seq_device_unregister_driver); | |
| EXPORT_SYMBOL(snd_seq_autoload_lock); | |
| EXPORT_SYMBOL(snd_seq_autoload_unlock); |