575 lines · 13.5 KB · C
RawDownload
/* *  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_FSstatic 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_FSstatic 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);	}

Lines 1–500 of 575.