summaryrefslogtreecommitdiff
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.c575
1 files changed, 575 insertions, 0 deletions
diff --git a/sound/core/seq/seq_device.c b/sound/core/seq/seq_device.c
new file mode 100644
index 000000000000..4d80f39612e8
--- /dev/null
+++ b/sound/core/seq/seq_device.c
@@ -0,0 +1,575 @@
+/*
+ * 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 <sound/driver.h>
+#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>
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("ALSA sequencer device management");
+MODULE_LICENSE("GPL");
+
+/*
+ * driver list
+ */
+typedef struct ops_list ops_list_t;
+
+/* 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 */
+ snd_seq_dev_ops_t 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 semaphore reg_mutex;
+
+ struct list_head list; /* next driver */
+};
+
+
+static LIST_HEAD(opslist);
+static int num_ops;
+static DECLARE_MUTEX(ops_mutex);
+static snd_info_entry_t *info_entry = NULL;
+
+/*
+ * prototypes
+ */
+static int snd_seq_device_free(snd_seq_device_t *dev);
+static int snd_seq_device_dev_free(snd_device_t *device);
+static int snd_seq_device_dev_register(snd_device_t *device);
+static int snd_seq_device_dev_disconnect(snd_device_t *device);
+static int snd_seq_device_dev_unregister(snd_device_t *device);
+
+static int init_device(snd_seq_device_t *dev, ops_list_t *ops);
+static int free_device(snd_seq_device_t *dev, ops_list_t *ops);
+static ops_list_t *find_driver(char *id, int create_if_empty);
+static ops_list_t *create_driver(char *id);
+static void unlock_driver(ops_list_t *ops);
+static void remove_drivers(void);
+
+/*
+ * show all drivers and their status
+ */
+
+static void snd_seq_device_info(snd_info_entry_t *entry, snd_info_buffer_t * buffer)
+{
+ struct list_head *head;
+
+ down(&ops_mutex);
+ list_for_each(head, &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, 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);
+ }
+ up(&ops_mutex);
+}
+
+/*
+ * load all registered drivers (called from seq_clientmgr.c)
+ */
+
+#ifdef CONFIG_KMOD
+/* 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_KMOD
+ struct list_head *head;
+
+ /* Calling request_module during module_init()
+ * may cause blocking.
+ */
+ if (snd_seq_in_init)
+ return;
+
+ if (! current->fs->root)
+ return;
+
+ down(&ops_mutex);
+ list_for_each(head, &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, list);
+ if (! (ops->driver & DRIVER_LOADED) &&
+ ! (ops->driver & DRIVER_REQUESTED)) {
+ ops->used++;
+ up(&ops_mutex);
+ ops->driver |= DRIVER_REQUESTED;
+ request_module("snd-%s", ops->id);
+ down(&ops_mutex);
+ ops->used--;
+ }
+ }
+ up(&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(snd_card_t *card, int device, char *id, int argsize,
+ snd_seq_device_t **result)
+{
+ snd_seq_device_t *dev;
+ ops_list_t *ops;
+ int err;
+ static snd_device_ops_t dops = {
+ .dev_free = snd_seq_device_dev_free,
+ .dev_register = snd_seq_device_dev_register,
+ .dev_disconnect = snd_seq_device_dev_disconnect,
+ .dev_unregister = snd_seq_device_dev_unregister
+ };
+
+ if (result)
+ *result = NULL;
+
+ snd_assert(id != NULL, return -EINVAL);
+
+ ops = find_driver(id, 1);
+ if (ops == NULL)
+ return -ENOMEM;
+
+ dev = kcalloc(1, 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 */
+ down(&ops->reg_mutex);
+ list_add_tail(&dev->list, &ops->dev_list);
+ ops->num_devices++;
+ up(&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(snd_seq_device_t *dev)
+{
+ ops_list_t *ops;
+
+ snd_assert(dev != NULL, return -EINVAL);
+
+ ops = find_driver(dev->id, 0);
+ if (ops == NULL)
+ return -ENXIO;
+
+ /* remove the device from the list */
+ down(&ops->reg_mutex);
+ list_del(&dev->list);
+ ops->num_devices--;
+ up(&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(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ return snd_seq_device_free(dev);
+}
+
+/*
+ * register the device
+ */
+static int snd_seq_device_dev_register(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ ops_list_t *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(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ ops_list_t *ops;
+
+ ops = find_driver(dev->id, 0);
+ if (ops == NULL)
+ return -ENOENT;
+
+ free_device(dev, ops);
+
+ unlock_driver(ops);
+ return 0;
+}
+
+/*
+ * unregister the existing device
+ */
+static int snd_seq_device_dev_unregister(snd_device_t *device)
+{
+ snd_seq_device_t *dev = device->device_data;
+ return snd_seq_device_free(dev);
+}
+
+/*
+ * register device driver
+ * id = driver id
+ * entry = driver operators - duplicated to each instance
+ */
+int snd_seq_device_register_driver(char *id, snd_seq_dev_ops_t *entry, int argsize)
+{
+ struct list_head *head;
+ ops_list_t *ops;
+
+ 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;
+ }
+
+ down(&ops->reg_mutex);
+ /* copy driver operators */
+ ops->ops = *entry;
+ ops->driver |= DRIVER_LOADED;
+ ops->argsize = argsize;
+
+ /* initialize existing devices if necessary */
+ list_for_each(head, &ops->dev_list) {
+ snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, list);
+ init_device(dev, ops);
+ }
+ up(&ops->reg_mutex);
+
+ unlock_driver(ops);
+ snd_seq_autoload_unlock();
+
+ return 0;
+}
+
+
+/*
+ * create driver record
+ */
+static ops_list_t * create_driver(char *id)
+{
+ ops_list_t *ops;
+
+ ops = kmalloc(sizeof(*ops), GFP_KERNEL);
+ if (ops == NULL)
+ return ops;
+ memset(ops, 0, sizeof(*ops));
+
+ /* set up driver entry */
+ strlcpy(ops->id, id, sizeof(ops->id));
+ init_MUTEX(&ops->reg_mutex);
+ ops->driver = DRIVER_EMPTY;
+ INIT_LIST_HEAD(&ops->dev_list);
+ /* lock this instance */
+ ops->used = 1;
+
+ /* register driver entry */
+ down(&ops_mutex);
+ list_add_tail(&ops->list, &opslist);
+ num_ops++;
+ up(&ops_mutex);
+
+ return ops;
+}
+
+
+/*
+ * unregister the specified driver
+ */
+int snd_seq_device_unregister_driver(char *id)
+{
+ struct list_head *head;
+ ops_list_t *ops;
+
+ 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 */
+ down(&ops->reg_mutex);
+ ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */
+ list_for_each(head, &ops->dev_list) {
+ snd_seq_device_t *dev = list_entry(head, snd_seq_device_t, 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);
+ up(&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;
+
+ down(&ops_mutex);
+ head = opslist.next;
+ while (head != &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, 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;
+ }
+ up(&ops_mutex);
+}
+
+/*
+ * initialize the device - call init_device operator
+ */
+static int init_device(snd_seq_device_t *dev, ops_list_t *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(snd_seq_device_t *dev, ops_list_t *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 ops_list_t * find_driver(char *id, int create_if_empty)
+{
+ struct list_head *head;
+
+ down(&ops_mutex);
+ list_for_each(head, &opslist) {
+ ops_list_t *ops = list_entry(head, ops_list_t, list);
+ if (strcmp(ops->id, id) == 0) {
+ ops->used++;
+ up(&ops_mutex);
+ return ops;
+ }
+ }
+ up(&ops_mutex);
+ if (create_if_empty)
+ return create_driver(id);
+ return NULL;
+}
+
+static void unlock_driver(ops_list_t *ops)
+{
+ down(&ops_mutex);
+ ops->used--;
+ up(&ops_mutex);
+}
+
+
+/*
+ * module part
+ */
+
+static int __init alsa_seq_device_init(void)
+{
+ 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_size = 2048;
+ info_entry->c.text.read = snd_seq_device_info;
+ if (snd_info_register(info_entry) < 0) {
+ snd_info_free_entry(info_entry);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void __exit alsa_seq_device_exit(void)
+{
+ remove_drivers();
+ snd_info_unregister(info_entry);
+ 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);
+#ifdef CONFIG_KMOD
+EXPORT_SYMBOL(snd_seq_autoload_lock);
+EXPORT_SYMBOL(snd_seq_autoload_unlock);
+#endif