aboutsummaryrefslogtreecommitdiffstats
path: root/sound/core/seq/seq_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core/seq/seq_device.c')
-rw-r--r--sound/core/seq/seq_device.c572
1 files changed, 572 insertions, 0 deletions
diff --git a/sound/core/seq/seq_device.c b/sound/core/seq/seq_device.c
new file mode 100644
index 00000000..1f997675
--- /dev/null
+++ b/sound/core/seq/seq_device.c
@@ -0,0 +1,572 @@
+/*
+ * 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);