/* * 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 <linux/module.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; /* registered 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 * 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) { pr_warn("ALSA: seq: 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)) { pr_err("ALSA: seq: 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) pr_err("ALSA: seq: 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) { pr_err("ALSA: seq: 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 { pr_err("ALSA: seq: 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) { pr_err("ALSA: seq: 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 { pr_err("ALSA: seq: 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) pr_err("ALSA: seq: 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_MODULES EXPORT_SYMBOL(snd_seq_autoload_lock); EXPORT_SYMBOL(snd_seq_autoload_unlock); #endif