2005-04-16 15:20:36 -07:00
/*
* 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>
2006-01-16 16:29:08 +01:00
# include <linux/mutex.h>
2005-04-16 15:20:36 -07:00
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 */
2005-11-17 14:04:02 +01:00
struct snd_seq_dev_ops ops ;
2005-04-16 15:20:36 -07:00
/* 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 */
2006-01-16 16:29:08 +01:00
struct mutex reg_mutex ;
2005-04-16 15:20:36 -07:00
struct list_head list ; /* next driver */
} ;
static LIST_HEAD ( opslist ) ;
static int num_ops ;
2006-01-16 16:29:08 +01:00
static DEFINE_MUTEX ( ops_mutex ) ;
2005-12-01 10:43:51 +01:00
# ifdef CONFIG_PROC_FS
2006-05-17 17:14:51 +02:00
static struct snd_info_entry * info_entry ;
2005-12-01 10:43:51 +01:00
# endif
2005-04-16 15:20:36 -07:00
/*
* prototypes
*/
2005-11-17 14:04:02 +01:00
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 ) ;
2005-04-16 15:20:36 -07:00
static void remove_drivers ( void ) ;
/*
* show all drivers and their status
*/
2005-12-01 10:43:51 +01:00
# ifdef CONFIG_PROC_FS
2005-11-17 14:04:02 +01:00
static void snd_seq_device_info ( struct snd_info_entry * entry ,
struct snd_info_buffer * buffer )
2005-04-16 15:20:36 -07:00
{
2006-10-05 16:02:22 +02:00
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2006-10-05 16:02:22 +02:00
list_for_each_entry ( ops , & opslist , list ) {
2005-04-16 15:20:36 -07:00
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 ) ;
}
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
}
2005-12-01 10:43:51 +01:00
# endif
2005-04-16 15:20:36 -07:00
/*
* load all registered drivers ( called from seq_clientmgr . c )
*/
2008-07-09 10:28:41 +02:00
# ifdef CONFIG_MODULES
2005-04-16 15:20:36 -07:00
/* 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 )
{
2008-07-09 10:28:41 +02:00
# ifdef CONFIG_MODULES
2006-10-05 16:02:22 +02:00
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
/* Calling request_module during module_init()
* may cause blocking .
*/
if ( snd_seq_in_init )
return ;
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2006-10-05 16:02:22 +02:00
list_for_each_entry ( ops , & opslist , list ) {
2005-04-16 15:20:36 -07:00
if ( ! ( ops - > driver & DRIVER_LOADED ) & &
! ( ops - > driver & DRIVER_REQUESTED ) ) {
ops - > used + + ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
ops - > driver | = DRIVER_REQUESTED ;
request_module ( " snd-%s " , ops - > id ) ;
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
ops - > used - - ;
}
}
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
# 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 )
*/
2005-11-17 14:04:02 +01:00
int snd_seq_device_new ( struct snd_card * card , int device , char * id , int argsize ,
struct snd_seq_device * * result )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct snd_seq_device * dev ;
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
int err ;
2005-11-17 14:04:02 +01:00
static struct snd_device_ops dops = {
2005-04-16 15:20:36 -07:00
. 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 ;
2008-08-08 17:09:09 +02:00
if ( snd_BUG_ON ( ! id ) )
return - EINVAL ;
2005-04-16 15:20:36 -07:00
ops = find_driver ( id , 1 ) ;
if ( ops = = NULL )
return - ENOMEM ;
2005-09-09 14:20:49 +02:00
dev = kzalloc ( sizeof ( * dev ) * 2 + argsize , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
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 */
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
list_add_tail ( & dev - > list , & ops - > dev_list ) ;
ops - > num_devices + + ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
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
*/
2005-11-17 14:04:02 +01:00
static int snd_seq_device_free ( struct snd_seq_device * dev )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
2008-08-08 17:09:09 +02:00
if ( snd_BUG_ON ( ! dev ) )
return - EINVAL ;
2005-04-16 15:20:36 -07:00
ops = find_driver ( dev - > id , 0 ) ;
if ( ops = = NULL )
return - ENXIO ;
/* remove the device from the list */
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
list_del ( & dev - > list ) ;
ops - > num_devices - - ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
free_device ( dev , ops ) ;
if ( dev - > private_free )
dev - > private_free ( dev ) ;
kfree ( dev ) ;
unlock_driver ( ops ) ;
return 0 ;
}
2005-11-17 14:04:02 +01:00
static int snd_seq_device_dev_free ( struct snd_device * device )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct snd_seq_device * dev = device - > device_data ;
2005-04-16 15:20:36 -07:00
return snd_seq_device_free ( dev ) ;
}
/*
* register the device
*/
2005-11-17 14:04:02 +01:00
static int snd_seq_device_dev_register ( struct snd_device * device )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct snd_seq_device * dev = device - > device_data ;
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
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
*/
2005-11-17 14:04:02 +01:00
static int snd_seq_device_dev_disconnect ( struct snd_device * device )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct snd_seq_device * dev = device - > device_data ;
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
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
*/
2005-11-17 14:04:02 +01:00
int snd_seq_device_register_driver ( char * id , struct snd_seq_dev_ops * entry ,
int argsize )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct ops_list * ops ;
2006-10-05 16:02:22 +02:00
struct snd_seq_device * dev ;
2005-04-16 15:20:36 -07:00
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 ;
}
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
/* copy driver operators */
ops - > ops = * entry ;
ops - > driver | = DRIVER_LOADED ;
ops - > argsize = argsize ;
/* initialize existing devices if necessary */
2006-10-05 16:02:22 +02:00
list_for_each_entry ( dev , & ops - > dev_list , list ) {
2005-04-16 15:20:36 -07:00
init_device ( dev , ops ) ;
}
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
unlock_driver ( ops ) ;
snd_seq_autoload_unlock ( ) ;
return 0 ;
}
/*
* create driver record
*/
2005-11-17 14:04:02 +01:00
static struct ops_list * create_driver ( char * id )
2005-04-16 15:20:36 -07:00
{
2005-11-17 14:04:02 +01:00
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
2006-07-25 15:28:03 +02:00
ops = kzalloc ( sizeof ( * ops ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( ops = = NULL )
return ops ;
/* set up driver entry */
strlcpy ( ops - > id , id , sizeof ( ops - > id ) ) ;
2006-01-16 16:29:08 +01:00
mutex_init ( & ops - > reg_mutex ) ;
2006-07-03 00:25:22 -07:00
/*
* 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 ) ;
2005-04-16 15:20:36 -07:00
ops - > driver = DRIVER_EMPTY ;
INIT_LIST_HEAD ( & ops - > dev_list ) ;
/* lock this instance */
ops - > used = 1 ;
/* register driver entry */
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
list_add_tail ( & ops - > list , & opslist ) ;
num_ops + + ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
return ops ;
}
/*
* unregister the specified driver
*/
int snd_seq_device_unregister_driver ( char * id )
{
2005-11-17 14:04:02 +01:00
struct ops_list * ops ;
2006-10-05 16:02:22 +02:00
struct snd_seq_device * dev ;
2005-04-16 15:20:36 -07:00
ops = find_driver ( id , 0 ) ;
if ( ops = = NULL )
return - ENXIO ;
if ( ! ( ops - > driver & DRIVER_LOADED ) | |
( ops - > driver & DRIVER_LOCKED ) ) {
2005-11-17 14:04:02 +01:00
snd_printk ( KERN_ERR " driver_unregister: cannot unload driver '%s': status=%x \n " ,
id , ops - > driver ) ;
2005-04-16 15:20:36 -07:00
unlock_driver ( ops ) ;
return - EBUSY ;
}
/* close and release all devices associated with this driver */
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
ops - > driver | = DRIVER_LOCKED ; /* do not remove this driver recursively */
2006-10-05 16:02:22 +02:00
list_for_each_entry ( dev , & ops - > dev_list , list ) {
2005-04-16 15:20:36 -07:00
free_device ( dev , ops ) ;
}
ops - > driver = 0 ;
if ( ops - > num_init_devices > 0 )
2005-11-17 14:04:02 +01:00
snd_printk ( KERN_ERR " free_driver: init_devices > 0!! (%d) \n " ,
ops - > num_init_devices ) ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
unlock_driver ( ops ) ;
/* remove empty driver entries */
remove_drivers ( ) ;
return 0 ;
}
/*
* remove empty driver entries
*/
static void remove_drivers ( void )
{
struct list_head * head ;
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
head = opslist . next ;
while ( head ! = & opslist ) {
2005-11-17 14:04:02 +01:00
struct ops_list * ops = list_entry ( head , struct ops_list , list ) ;
2005-04-16 15:20:36 -07:00
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 ;
}
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
}
/*
* initialize the device - call init_device operator
*/
2005-11-17 14:04:02 +01:00
static int init_device ( struct snd_seq_device * dev , struct ops_list * ops )
2005-04-16 15:20:36 -07:00
{
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 ) {
2005-11-17 14:04:02 +01:00
snd_printk ( KERN_ERR " incompatible device '%s' for plug-in '%s' (%d %d) \n " ,
dev - > name , ops - > id , ops - > argsize , dev - > argsize ) ;
2005-04-16 15:20:36 -07:00
return - EINVAL ;
}
if ( ops - > ops . init_device ( dev ) > = 0 ) {
dev - > status = SNDRV_SEQ_DEVICE_REGISTERED ;
ops - > num_init_devices + + ;
} else {
2005-11-17 14:04:02 +01:00
snd_printk ( KERN_ERR " init_device failed: %s: %s \n " ,
dev - > name , dev - > id ) ;
2005-04-16 15:20:36 -07:00
}
return 0 ;
}
/*
* release the device - call free_device operator
*/
2005-11-17 14:04:02 +01:00
static int free_device ( struct snd_seq_device * dev , struct ops_list * ops )
2005-04-16 15:20:36 -07:00
{
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 ) {
2005-11-17 14:04:02 +01:00
snd_printk ( KERN_ERR " incompatible device '%s' for plug-in '%s' (%d %d) \n " ,
dev - > name , ops - > id , ops - > argsize , dev - > argsize ) ;
2005-04-16 15:20:36 -07:00
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 {
2005-11-17 14:04:02 +01:00
snd_printk ( KERN_ERR " free_device failed: %s: %s \n " ,
dev - > name , dev - > id ) ;
2005-04-16 15:20:36 -07:00
}
return 0 ;
}
/*
* find the matching driver with given id
*/
2005-11-17 14:04:02 +01:00
static struct ops_list * find_driver ( char * id , int create_if_empty )
2005-04-16 15:20:36 -07:00
{
2006-10-05 16:02:22 +02:00
struct ops_list * ops ;
2005-04-16 15:20:36 -07:00
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2006-10-05 16:02:22 +02:00
list_for_each_entry ( ops , & opslist , list ) {
2005-04-16 15:20:36 -07:00
if ( strcmp ( ops - > id , id ) = = 0 ) {
ops - > used + + ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
return ops ;
}
}
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
if ( create_if_empty )
return create_driver ( id ) ;
return NULL ;
}
2005-11-17 14:04:02 +01:00
static void unlock_driver ( struct ops_list * ops )
2005-04-16 15:20:36 -07:00
{
2006-01-16 16:29:08 +01:00
mutex_lock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
ops - > used - - ;
2006-01-16 16:29:08 +01:00
mutex_unlock ( & ops_mutex ) ;
2005-04-16 15:20:36 -07:00
}
/*
* module part
*/
static int __init alsa_seq_device_init ( void )
{
2005-12-01 10:43:51 +01:00
# ifdef CONFIG_PROC_FS
2005-11-17 14:04:02 +01:00
info_entry = snd_info_create_module_entry ( THIS_MODULE , " drivers " ,
snd_seq_root ) ;
2005-04-16 15:20:36 -07:00
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 ;
}
2005-12-01 10:43:51 +01:00
# endif
2005-04-16 15:20:36 -07:00
return 0 ;
}
static void __exit alsa_seq_device_exit ( void )
{
remove_drivers ( ) ;
2005-12-01 10:43:51 +01:00
# ifdef CONFIG_PROC_FS
2006-06-23 14:37:59 +02:00
snd_info_free_entry ( info_entry ) ;
2005-12-01 10:43:51 +01:00
# endif
2005-04-16 15:20:36 -07:00
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 ) ;