2005-04-17 02:20:36 +04:00
/*
* Advanced Linux Sound Architecture
* Copyright ( c ) by Jaroslav Kysela < perex @ suse . cz >
*
*
* 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
*
*/
# include <sound/driver.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/time.h>
2005-11-22 17:46:41 +03:00
# include <linux/device.h>
2005-04-17 02:20:36 +04:00
# include <linux/moduleparam.h>
# include <sound/core.h>
# include <sound/minors.h>
# include <sound/info.h>
# include <sound/version.h>
# include <sound/control.h>
# include <sound/initval.h>
# include <linux/kmod.h>
2006-01-16 18:29:08 +03:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
# define SNDRV_OS_MINORS 256
static int major = CONFIG_SND_MAJOR ;
int snd_major ;
2006-04-28 17:13:39 +04:00
EXPORT_SYMBOL ( snd_major ) ;
2005-04-17 02:20:36 +04:00
static int cards_limit = 1 ;
MODULE_AUTHOR ( " Jaroslav Kysela <perex@suse.cz> " ) ;
MODULE_DESCRIPTION ( " Advanced Linux Sound Architecture driver for soundcards. " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_param ( major , int , 0444 ) ;
MODULE_PARM_DESC ( major , " Major # for sound driver. " ) ;
module_param ( cards_limit , int , 0444 ) ;
MODULE_PARM_DESC ( cards_limit , " Count of auto-loadable soundcards. " ) ;
MODULE_ALIAS_CHARDEV_MAJOR ( CONFIG_SND_MAJOR ) ;
/* this one holds the actual max. card number currently available.
* as default , it ' s identical with cards_limit option . when more
* modules are loaded manually , this limit number increases , too .
*/
int snd_ecards_limit ;
2006-04-28 17:13:39 +04:00
EXPORT_SYMBOL ( snd_ecards_limit ) ;
2005-04-17 02:20:36 +04:00
2005-11-20 16:05:49 +03:00
static struct snd_minor * snd_minors [ SNDRV_OS_MINORS ] ;
2006-01-16 18:29:08 +03:00
static DEFINE_MUTEX ( sound_mutex ) ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_KMOD
/**
* snd_request_card - try to load the card module
* @ card : the card number
*
* Tries to load the module " snd-card-X " for the given card number
* via KMOD . Returns immediately if already loaded .
*/
void snd_request_card ( int card )
{
if ( ! current - > fs - > root )
return ;
2006-05-15 21:49:05 +04:00
if ( snd_card_locked ( card ) )
2005-04-17 02:20:36 +04:00
return ;
if ( card < 0 | | card > = cards_limit )
return ;
request_module ( " snd-card-%i " , card ) ;
}
2006-04-28 17:13:39 +04:00
EXPORT_SYMBOL ( snd_request_card ) ;
2005-04-17 02:20:36 +04:00
static void snd_request_other ( int minor )
{
char * str ;
if ( ! current - > fs - > root )
return ;
switch ( minor ) {
case SNDRV_MINOR_SEQUENCER : str = " snd-seq " ; break ;
case SNDRV_MINOR_TIMER : str = " snd-timer " ; break ;
default : return ;
}
request_module ( str ) ;
}
# endif /* request_module support */
2005-11-20 16:06:59 +03:00
/**
* snd_lookup_minor_data - get user data of a registered device
* @ minor : the minor number
* @ type : device type ( SNDRV_DEVICE_TYPE_XXX )
*
* Checks that a minor device with the specified type is registered , and returns
* its user data pointer .
*/
void * snd_lookup_minor_data ( unsigned int minor , int type )
{
struct snd_minor * mreg ;
void * private_data ;
2006-03-13 16:14:10 +03:00
if ( minor > = ARRAY_SIZE ( snd_minors ) )
2005-11-20 16:06:59 +03:00
return NULL ;
2006-01-16 18:29:08 +03:00
mutex_lock ( & sound_mutex ) ;
2005-11-20 16:06:59 +03:00
mreg = snd_minors [ minor ] ;
if ( mreg & & mreg - > type = = type )
private_data = mreg - > private_data ;
else
private_data = NULL ;
2006-01-16 18:29:08 +03:00
mutex_unlock ( & sound_mutex ) ;
2005-11-20 16:06:59 +03:00
return private_data ;
}
2006-04-28 17:13:39 +04:00
EXPORT_SYMBOL ( snd_lookup_minor_data ) ;
2005-04-17 02:20:36 +04:00
static int snd_open ( struct inode * inode , struct file * file )
{
2005-11-20 16:07:47 +03:00
unsigned int minor = iminor ( inode ) ;
2005-11-17 15:51:18 +03:00
struct snd_minor * mptr = NULL ;
2006-03-28 13:56:41 +04:00
const struct file_operations * old_fops ;
2005-04-17 02:20:36 +04:00
int err = 0 ;
2006-03-13 16:14:10 +03:00
if ( minor > = ARRAY_SIZE ( snd_minors ) )
2005-11-20 16:07:47 +03:00
return - ENODEV ;
mptr = snd_minors [ minor ] ;
if ( mptr = = NULL ) {
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_KMOD
2005-11-20 16:07:47 +03:00
int dev = SNDRV_MINOR_DEVICE ( minor ) ;
if ( dev = = SNDRV_MINOR_CONTROL ) {
/* /dev/aloadC? */
int card = SNDRV_MINOR_CARD ( minor ) ;
2005-04-17 02:20:36 +04:00
if ( snd_cards [ card ] = = NULL )
2005-11-20 16:07:47 +03:00
snd_request_card ( card ) ;
} else if ( dev = = SNDRV_MINOR_GLOBAL ) {
/* /dev/aloadSEQ */
2005-04-17 02:20:36 +04:00
snd_request_other ( minor ) ;
2005-11-20 16:07:47 +03:00
}
# ifndef CONFIG_SND_DYNAMIC_MINORS
/* /dev/snd/{controlC?,seq} */
mptr = snd_minors [ minor ] ;
if ( mptr = = NULL )
# endif
2005-04-17 02:20:36 +04:00
# endif
2005-11-20 16:07:47 +03:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
old_fops = file - > f_op ;
file - > f_op = fops_get ( mptr - > f_ops ) ;
if ( file - > f_op - > open )
err = file - > f_op - > open ( inode , file ) ;
if ( err ) {
fops_put ( file - > f_op ) ;
file - > f_op = fops_get ( old_fops ) ;
}
fops_put ( old_fops ) ;
return err ;
}
2007-02-12 11:55:37 +03:00
static const struct file_operations snd_fops =
2005-04-17 02:20:36 +04:00
{
. owner = THIS_MODULE ,
. open = snd_open
} ;
2005-11-20 16:07:47 +03:00
# ifdef CONFIG_SND_DYNAMIC_MINORS
static int snd_find_free_minor ( void )
{
int minor ;
for ( minor = 0 ; minor < ARRAY_SIZE ( snd_minors ) ; + + minor ) {
/* skip minors still used statically for autoloading devices */
if ( SNDRV_MINOR_DEVICE ( minor ) = = SNDRV_MINOR_CONTROL | |
minor = = SNDRV_MINOR_SEQUENCER )
continue ;
if ( ! snd_minors [ minor ] )
return minor ;
}
return - EBUSY ;
}
# else
2005-11-17 15:51:18 +03:00
static int snd_kernel_minor ( int type , struct snd_card * card , int dev )
2005-04-17 02:20:36 +04:00
{
int minor ;
switch ( type ) {
case SNDRV_DEVICE_TYPE_SEQUENCER :
case SNDRV_DEVICE_TYPE_TIMER :
minor = type ;
break ;
case SNDRV_DEVICE_TYPE_CONTROL :
snd_assert ( card ! = NULL , return - EINVAL ) ;
minor = SNDRV_MINOR ( card - > number , type ) ;
break ;
case SNDRV_DEVICE_TYPE_HWDEP :
case SNDRV_DEVICE_TYPE_RAWMIDI :
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK :
case SNDRV_DEVICE_TYPE_PCM_CAPTURE :
snd_assert ( card ! = NULL , return - EINVAL ) ;
minor = SNDRV_MINOR ( card - > number , type + dev ) ;
break ;
default :
return - EINVAL ;
}
snd_assert ( minor > = 0 & & minor < SNDRV_OS_MINORS , return - EINVAL ) ;
return minor ;
}
2005-11-20 16:07:47 +03:00
# endif
2005-04-17 02:20:36 +04:00
/**
2006-10-05 17:05:34 +04:00
* snd_register_device_for_dev - Register the ALSA device file for the card
2005-04-17 02:20:36 +04:00
* @ type : the device type , SNDRV_DEVICE_TYPE_XXX
* @ card : the card instance
* @ dev : the device index
2005-11-20 16:03:48 +03:00
* @ f_ops : the file operations
2005-11-20 16:06:59 +03:00
* @ private_data : user pointer for f_ops - > open ( )
2005-04-17 02:20:36 +04:00
* @ name : the device file name
2006-10-05 17:05:34 +04:00
* @ device : the & struct device to link this new device to
2005-04-17 02:20:36 +04:00
*
* Registers an ALSA device file for the given card .
* The operators have to be set in reg parameter .
*
2006-10-05 17:05:34 +04:00
* Returns zero if successful , or a negative error code on failure .
2005-04-17 02:20:36 +04:00
*/
2006-10-05 17:05:34 +04:00
int snd_register_device_for_dev ( int type , struct snd_card * card , int dev ,
const struct file_operations * f_ops ,
void * private_data ,
const char * name , struct device * device )
2005-04-17 02:20:36 +04:00
{
2005-11-20 16:07:47 +03:00
int minor ;
2005-11-17 15:51:18 +03:00
struct snd_minor * preg ;
2005-04-17 02:20:36 +04:00
snd_assert ( name , return - EINVAL ) ;
2006-07-05 13:24:22 +04:00
preg = kmalloc ( sizeof * preg , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( preg = = NULL )
return - ENOMEM ;
2005-11-20 16:03:48 +03:00
preg - > type = type ;
2005-11-20 16:05:49 +03:00
preg - > card = card ? card - > number : - 1 ;
2005-04-17 02:20:36 +04:00
preg - > device = dev ;
2005-11-20 16:03:48 +03:00
preg - > f_ops = f_ops ;
2005-11-20 16:06:59 +03:00
preg - > private_data = private_data ;
2006-01-16 18:29:08 +03:00
mutex_lock ( & sound_mutex ) ;
2005-11-20 16:07:47 +03:00
# ifdef CONFIG_SND_DYNAMIC_MINORS
minor = snd_find_free_minor ( ) ;
# else
minor = snd_kernel_minor ( type , card , dev ) ;
if ( minor > = 0 & & snd_minors [ minor ] )
minor = - EBUSY ;
# endif
if ( minor < 0 ) {
2006-01-16 18:29:08 +03:00
mutex_unlock ( & sound_mutex ) ;
2005-04-17 02:20:36 +04:00
kfree ( preg ) ;
2005-11-20 16:07:47 +03:00
return minor ;
2005-04-17 02:20:36 +04:00
}
2005-11-20 16:05:49 +03:00
snd_minors [ minor ] = preg ;
2006-08-08 09:19:37 +04:00
preg - > dev = device_create ( sound_class , device , MKDEV ( major , minor ) ,
" %s " , name ) ;
if ( preg - > dev )
dev_set_drvdata ( preg - > dev , private_data ) ;
2005-04-17 02:20:36 +04:00
2006-01-16 18:29:08 +03:00
mutex_unlock ( & sound_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-10-05 17:05:34 +04:00
EXPORT_SYMBOL ( snd_register_device_for_dev ) ;
2006-04-28 17:13:39 +04:00
2006-09-06 16:27:46 +04:00
/* find the matching minor record
* return the index of snd_minor , or - 1 if not found
*/
static int find_snd_minor ( int type , struct snd_card * card , int dev )
{
int cardnum , minor ;
struct snd_minor * mptr ;
cardnum = card ? card - > number : - 1 ;
for ( minor = 0 ; minor < ARRAY_SIZE ( snd_minors ) ; + + minor )
if ( ( mptr = snd_minors [ minor ] ) ! = NULL & &
mptr - > type = = type & &
mptr - > card = = cardnum & &
mptr - > device = = dev )
return minor ;
return - 1 ;
}
2005-04-17 02:20:36 +04:00
/**
* snd_unregister_device - unregister the device on the given card
* @ type : the device type , SNDRV_DEVICE_TYPE_XXX
* @ card : the card instance
* @ dev : the device index
*
* Unregisters the device file already registered via
* snd_register_device ( ) .
*
* Returns zero if sucecessful , or a negative error code on failure
*/
2005-11-17 15:51:18 +03:00
int snd_unregister_device ( int type , struct snd_card * card , int dev )
2005-04-17 02:20:36 +04:00
{
2006-09-06 16:27:46 +04:00
int minor ;
2005-04-17 02:20:36 +04:00
2006-01-16 18:29:08 +03:00
mutex_lock ( & sound_mutex ) ;
2006-09-06 16:27:46 +04:00
minor = find_snd_minor ( type , card , dev ) ;
if ( minor < 0 ) {
2006-01-16 18:29:08 +03:00
mutex_unlock ( & sound_mutex ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2006-08-08 09:19:37 +04:00
device_destroy ( sound_class , MKDEV ( major , minor ) ) ;
2005-04-17 02:20:36 +04:00
2006-09-06 16:27:46 +04:00
kfree ( snd_minors [ minor ] ) ;
2005-11-20 16:05:49 +03:00
snd_minors [ minor ] = NULL ;
2006-01-16 18:29:08 +03:00
mutex_unlock ( & sound_mutex ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-04-28 17:13:39 +04:00
EXPORT_SYMBOL ( snd_unregister_device ) ;
2006-09-06 16:27:46 +04:00
int snd_add_device_sysfs_file ( int type , struct snd_card * card , int dev ,
2006-08-08 09:19:37 +04:00
struct device_attribute * attr )
2006-09-06 16:27:46 +04:00
{
int minor , ret = - EINVAL ;
2006-08-08 09:19:37 +04:00
struct device * d ;
2006-09-06 16:27:46 +04:00
mutex_lock ( & sound_mutex ) ;
minor = find_snd_minor ( type , card , dev ) ;
2006-08-08 09:19:37 +04:00
if ( minor > = 0 & & ( d = snd_minors [ minor ] - > dev ) ! = NULL )
ret = device_create_file ( d , attr ) ;
2006-09-06 16:27:46 +04:00
mutex_unlock ( & sound_mutex ) ;
return ret ;
}
EXPORT_SYMBOL ( snd_add_device_sysfs_file ) ;
2005-12-01 12:42:42 +03:00
# ifdef CONFIG_PROC_FS
2005-04-17 02:20:36 +04:00
/*
* INFO PART
*/
2006-05-17 19:14:51 +04:00
static struct snd_info_entry * snd_minor_info_entry ;
2005-04-17 02:20:36 +04:00
2005-11-20 16:03:48 +03:00
static const char * snd_device_type_name ( int type )
{
switch ( type ) {
case SNDRV_DEVICE_TYPE_CONTROL :
return " control " ;
case SNDRV_DEVICE_TYPE_HWDEP :
return " hardware dependent " ;
case SNDRV_DEVICE_TYPE_RAWMIDI :
return " raw midi " ;
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK :
return " digital audio playback " ;
case SNDRV_DEVICE_TYPE_PCM_CAPTURE :
return " digital audio capture " ;
case SNDRV_DEVICE_TYPE_SEQUENCER :
return " sequencer " ;
case SNDRV_DEVICE_TYPE_TIMER :
return " timer " ;
default :
return " ? " ;
}
}
2005-11-17 15:51:18 +03:00
static void snd_minor_info_read ( struct snd_info_entry * entry , struct snd_info_buffer * buffer )
2005-04-17 02:20:36 +04:00
{
2005-11-20 16:05:49 +03:00
int minor ;
2005-11-17 15:51:18 +03:00
struct snd_minor * mptr ;
2005-04-17 02:20:36 +04:00
2006-01-16 18:29:08 +03:00
mutex_lock ( & sound_mutex ) ;
2005-11-20 16:05:49 +03:00
for ( minor = 0 ; minor < SNDRV_OS_MINORS ; + + minor ) {
if ( ! ( mptr = snd_minors [ minor ] ) )
continue ;
if ( mptr - > card > = 0 ) {
if ( mptr - > device > = 0 )
2005-11-20 16:09:05 +03:00
snd_iprintf ( buffer , " %3i: [%2i-%2i]: %s \n " ,
2005-11-20 16:05:49 +03:00
minor , mptr - > card , mptr - > device ,
snd_device_type_name ( mptr - > type ) ) ;
else
2005-11-20 16:09:05 +03:00
snd_iprintf ( buffer , " %3i: [%2i] : %s \n " ,
2005-11-20 16:05:49 +03:00
minor , mptr - > card ,
snd_device_type_name ( mptr - > type ) ) ;
} else
2005-11-20 16:09:05 +03:00
snd_iprintf ( buffer , " %3i: : %s \n " , minor ,
2005-11-20 16:05:49 +03:00
snd_device_type_name ( mptr - > type ) ) ;
2005-04-17 02:20:36 +04:00
}
2006-01-16 18:29:08 +03:00
mutex_unlock ( & sound_mutex ) ;
2005-04-17 02:20:36 +04:00
}
int __init snd_minor_info_init ( void )
{
2005-11-17 15:51:18 +03:00
struct snd_info_entry * entry ;
2005-04-17 02:20:36 +04:00
entry = snd_info_create_module_entry ( THIS_MODULE , " devices " , NULL ) ;
if ( entry ) {
entry - > c . text . read = snd_minor_info_read ;
if ( snd_info_register ( entry ) < 0 ) {
snd_info_free_entry ( entry ) ;
entry = NULL ;
}
}
snd_minor_info_entry = entry ;
return 0 ;
}
int __exit snd_minor_info_done ( void )
{
2006-06-23 16:37:59 +04:00
snd_info_free_entry ( snd_minor_info_entry ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-12-01 12:42:42 +03:00
# endif /* CONFIG_PROC_FS */
2005-04-17 02:20:36 +04:00
/*
* INIT PART
*/
static int __init alsa_sound_init ( void )
{
snd_major = major ;
snd_ecards_limit = cards_limit ;
if ( register_chrdev ( major , " alsa " , & snd_fops ) ) {
snd_printk ( KERN_ERR " unable to register native major device number %d \n " , major ) ;
return - EIO ;
}
if ( snd_info_init ( ) < 0 ) {
unregister_chrdev ( major , " alsa " ) ;
return - ENOMEM ;
}
snd_info_minor_register ( ) ;
# ifndef MODULE
printk ( KERN_INFO " Advanced Linux Sound Architecture Driver Version " CONFIG_SND_VERSION CONFIG_SND_DATE " . \n " ) ;
# endif
return 0 ;
}
static void __exit alsa_sound_exit ( void )
{
snd_info_minor_unregister ( ) ;
snd_info_done ( ) ;
2007-07-19 12:47:50 +04:00
unregister_chrdev ( major , " alsa " ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( alsa_sound_init )
module_exit ( alsa_sound_exit )