2005-04-17 02:20:36 +04:00
/*
2008-08-28 18:42:51 +04:00
* Sound core . This file is composed of two parts . sound_class
* which is common to both OSS and ALSA and OSS sound core which
* is used OSS or emulation of it .
*/
/*
* First , the common part .
*/
# include <linux/module.h>
# include <linux/device.h>
# include <linux/err.h>
2009-07-03 22:56:05 +04:00
# include <linux/kdev_t.h>
# include <linux/major.h>
2008-12-15 18:43:43 +03:00
# include <sound/core.h>
2008-08-28 18:42:51 +04:00
# ifdef CONFIG_SOUND_OSS_CORE
static int __init init_oss_soundcore ( void ) ;
2008-09-07 19:24:33 +04:00
static void cleanup_oss_soundcore ( void ) ;
2008-08-28 18:42:51 +04:00
# else
static inline int init_oss_soundcore ( void ) { return 0 ; }
static inline void cleanup_oss_soundcore ( void ) { }
# endif
struct class * sound_class ;
EXPORT_SYMBOL ( sound_class ) ;
MODULE_DESCRIPTION ( " Core sound module " ) ;
MODULE_AUTHOR ( " Alan Cox " ) ;
MODULE_LICENSE ( " GPL " ) ;
2009-09-19 01:01:12 +04:00
static char * sound_devnode ( struct device * dev , mode_t * mode )
2009-04-30 17:23:42 +04:00
{
2009-07-03 22:56:05 +04:00
if ( MAJOR ( dev - > devt ) = = SOUND_MAJOR )
return NULL ;
2009-04-30 17:23:42 +04:00
return kasprintf ( GFP_KERNEL , " snd/%s " , dev_name ( dev ) ) ;
}
2008-08-28 18:42:51 +04:00
static int __init init_soundcore ( void )
{
int rc ;
rc = init_oss_soundcore ( ) ;
if ( rc )
return rc ;
sound_class = class_create ( THIS_MODULE , " sound " ) ;
if ( IS_ERR ( sound_class ) ) {
cleanup_oss_soundcore ( ) ;
return PTR_ERR ( sound_class ) ;
}
2009-09-19 01:01:12 +04:00
sound_class - > devnode = sound_devnode ;
2009-04-30 17:23:42 +04:00
2008-08-28 18:42:51 +04:00
return 0 ;
}
static void __exit cleanup_soundcore ( void )
{
cleanup_oss_soundcore ( ) ;
class_destroy ( sound_class ) ;
}
2010-01-13 01:20:39 +03:00
subsys_initcall ( init_soundcore ) ;
2008-08-28 18:42:51 +04:00
module_exit ( cleanup_soundcore ) ;
# ifdef CONFIG_SOUND_OSS_CORE
/*
* OSS sound core handling . Breaks out sound functions to submodules
2005-04-17 02:20:36 +04:00
*
2008-10-27 18:21:19 +03:00
* Author : Alan Cox < alan @ lxorguk . ukuu . org . uk >
2005-04-17 02:20:36 +04:00
*
* Fixes :
*
*
* 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 .
*
* - - - - - - - - - - - - - - - - - - - -
*
* Top level handler for the sound subsystem . Various devices can
* plug into this . The fact they don ' t all go via OSS doesn ' t mean
* they don ' t have to implement the OSS API . There is a lot of logic
* to keeping much of the OSS weight out of the code in a compatibility
* module , but it ' s up to the driver to rember to load it . . .
*
* The code provides a set of functions for registration of devices
* by type . This is done rather than providing a single call so that
* we can hide any future changes in the internals ( eg when we go to
* 32 bit dev_t ) from the modules and their interface .
*
* Secondly we need to allocate the dsp , dsp16 and audio devices as
* one . Thus we misuse the chains a bit to simplify this .
*
* Thirdly to make it more fun and for 2.3 . x and above we do all
* of this using fine grained locking .
*
* FIXME : we have to resolve modules and fine grained load / unload
* locking at some point in 2.3 . x .
*/
# include <linux/init.h>
# include <linux/slab.h>
2008-05-16 02:39:03 +04:00
# include <linux/smp_lock.h>
2005-04-17 02:20:36 +04:00
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/sound.h>
# include <linux/kmod.h>
# define SOUND_STEP 16
struct sound_unit
{
int unit_minor ;
2006-03-28 13:56:41 +04:00
const struct file_operations * unit_fops ;
2005-04-17 02:20:36 +04:00
struct sound_unit * next ;
char name [ 32 ] ;
} ;
# ifdef CONFIG_SOUND_MSNDCLAS
extern int msnd_classic_init ( void ) ;
# endif
# ifdef CONFIG_SOUND_MSNDPIN
extern int msnd_pinnacle_init ( void ) ;
# endif
2009-08-06 13:14:26 +04:00
/*
* By default , OSS sound_core claims full legacy minor range ( 0 - 255 )
* of SOUND_MAJOR to trap open attempts to any sound minor and
* requests modules using custom sound - slot / service - * module aliases .
* The only benefit of doing this is allowing use of custom module
* aliases instead of the standard char - major - * ones . This behavior
* prevents alternative OSS implementation and is scheduled to be
* removed .
*
* CONFIG_SOUND_OSS_CORE_PRECLAIM and soundcore . preclaim_oss kernel
* parameter are added to allow distros and developers to try and
* switch to alternative implementations without needing to rebuild
* the kernel in the meantime . If preclaim_oss is non - zero , the
* kernel will behave the same as before . All SOUND_MAJOR minors are
* preclaimed and the custom module aliases along with standard chrdev
* ones are emitted if a missing device is opened . If preclaim_oss is
* zero , sound_core only grabs what ' s actually in use and for missing
* devices only the standard chrdev aliases are requested .
*
* All these clutters are scheduled to be removed along with
* sound - slot / service - * module aliases . Please take a look at
* feature - removal - schedule . txt for details .
*/
# ifdef CONFIG_SOUND_OSS_CORE_PRECLAIM
static int preclaim_oss = 1 ;
# else
static int preclaim_oss = 0 ;
# endif
module_param ( preclaim_oss , int , 0444 ) ;
static int soundcore_open ( struct inode * , struct file * ) ;
static const struct file_operations soundcore_fops =
{
/* We must have an owner or the module locking fails */
. owner = THIS_MODULE ,
. open = soundcore_open ,
} ;
2005-04-17 02:20:36 +04:00
/*
* Low level list operator . Scan the ordered list , find a hole and
* join into it . Called with the lock asserted
*/
2006-03-28 13:56:41 +04:00
static int __sound_insert_unit ( struct sound_unit * s , struct sound_unit * * list , const struct file_operations * fops , int index , int low , int top )
2005-04-17 02:20:36 +04:00
{
int n = low ;
if ( index < 0 ) { /* first free */
while ( * list & & ( * list ) - > unit_minor < n )
list = & ( ( * list ) - > next ) ;
while ( n < top )
{
/* Found a hole ? */
if ( * list = = NULL | | ( * list ) - > unit_minor > n )
break ;
list = & ( ( * list ) - > next ) ;
n + = SOUND_STEP ;
}
if ( n > = top )
return - ENOENT ;
} else {
n = low + ( index * 16 ) ;
while ( * list ) {
if ( ( * list ) - > unit_minor = = n )
return - EBUSY ;
if ( ( * list ) - > unit_minor > n )
break ;
list = & ( ( * list ) - > next ) ;
}
}
/*
* Fill it in
*/
s - > unit_minor = n ;
s - > unit_fops = fops ;
/*
* Link it
*/
s - > next = * list ;
* list = s ;
return n ;
}
/*
* Remove a node from the chain . Called with the lock asserted
*/
static struct sound_unit * __sound_remove_unit ( struct sound_unit * * list , int unit )
{
while ( * list )
{
struct sound_unit * p = * list ;
if ( p - > unit_minor = = unit )
{
* list = p - > next ;
return p ;
}
list = & ( p - > next ) ;
}
printk ( KERN_ERR " Sound device %d went missing! \n " , unit ) ;
return NULL ;
}
/*
* This lock guards the sound loader list .
*/
static DEFINE_SPINLOCK ( sound_loader_lock ) ;
/*
* Allocate the controlling structure and add it to the sound driver
* list . Acquires locks as needed
*/
2006-03-28 13:56:41 +04:00
static int sound_insert_unit ( struct sound_unit * * list , const struct file_operations * fops , int index , int low , int top , const char * name , umode_t mode , struct device * dev )
2005-04-17 02:20:36 +04:00
{
struct sound_unit * s = kmalloc ( sizeof ( * s ) , GFP_KERNEL ) ;
int r ;
if ( ! s )
return - ENOMEM ;
2009-08-06 13:14:26 +04:00
2005-04-17 02:20:36 +04:00
spin_lock ( & sound_loader_lock ) ;
2009-08-06 13:14:26 +04:00
retry :
2005-04-17 02:20:36 +04:00
r = __sound_insert_unit ( s , list , fops , index , low , top ) ;
spin_unlock ( & sound_loader_lock ) ;
if ( r < 0 )
goto fail ;
else if ( r < SOUND_STEP )
sprintf ( s - > name , " sound/%s " , name ) ;
else
sprintf ( s - > name , " sound/%s%d " , name , r / SOUND_STEP ) ;
2009-08-06 13:14:26 +04:00
if ( ! preclaim_oss ) {
/*
* Something else might have grabbed the minor . If
* first free slot is requested , rescan with @ low set
* to the next unit ; otherwise , - EBUSY .
*/
r = __register_chrdev ( SOUND_MAJOR , s - > unit_minor , 1 , s - > name ,
& soundcore_fops ) ;
if ( r < 0 ) {
spin_lock ( & sound_loader_lock ) ;
__sound_remove_unit ( list , s - > unit_minor ) ;
if ( index < 0 ) {
low = s - > unit_minor + SOUND_STEP ;
goto retry ;
}
spin_unlock ( & sound_loader_lock ) ;
return - EBUSY ;
}
}
2008-07-22 07:03:34 +04:00
device_create ( sound_class , dev , MKDEV ( SOUND_MAJOR , s - > unit_minor ) ,
NULL , s - > name + 6 ) ;
2009-08-06 13:14:26 +04:00
return s - > unit_minor ;
2005-04-17 02:20:36 +04:00
2009-08-06 13:14:26 +04:00
fail :
2005-04-17 02:20:36 +04:00
kfree ( s ) ;
return r ;
}
/*
* Remove a unit . Acquires locks as needed . The drivers MUST have
* completed the removal before their file operations become
* invalid .
*/
static void sound_remove_unit ( struct sound_unit * * list , int unit )
{
struct sound_unit * p ;
spin_lock ( & sound_loader_lock ) ;
p = __sound_remove_unit ( list , unit ) ;
spin_unlock ( & sound_loader_lock ) ;
if ( p ) {
2009-08-06 13:14:26 +04:00
if ( ! preclaim_oss )
__unregister_chrdev ( SOUND_MAJOR , p - > unit_minor , 1 ,
p - > name ) ;
2006-08-08 09:19:37 +04:00
device_destroy ( sound_class , MKDEV ( SOUND_MAJOR , p - > unit_minor ) ) ;
2005-04-17 02:20:36 +04:00
kfree ( p ) ;
}
}
/*
* Allocations
*
* 0 * 16 Mixers
* 1 * 8 Sequencers
* 2 * 16 Midi
* 3 * 16 DSP
* 4 * 16 SunDSP
* 5 * 16 DSP16
* 6 - - sndstat ( obsolete )
* 7 * 16 unused
* 8 - - alternate sequencer ( see above )
* 9 * 16 raw synthesizer access
* 10 * 16 unused
* 11 * 16 unused
* 12 * 16 unused
* 13 * 16 unused
* 14 * 16 unused
* 15 * 16 unused
*/
static struct sound_unit * chains [ SOUND_STEP ] ;
/**
2005-08-30 10:58:37 +04:00
* register_sound_special_device - register a special sound node
2005-04-17 02:20:36 +04:00
* @ fops : File operations for the driver
* @ unit : Unit number to allocate
2005-08-30 10:58:37 +04:00
* @ dev : device pointer
2005-04-17 02:20:36 +04:00
*
* Allocate a special sound device by minor number from the sound
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 18:09:05 +03:00
* subsystem . The allocated number is returned on success . On failure
2005-04-17 02:20:36 +04:00
* a negative error code is returned .
*/
2006-03-28 13:56:41 +04:00
int register_sound_special_device ( const struct file_operations * fops , int unit ,
2005-08-30 10:58:37 +04:00
struct device * dev )
2005-04-17 02:20:36 +04:00
{
const int chain = unit % SOUND_STEP ;
int max_unit = 128 + chain ;
const char * name ;
char _name [ 16 ] ;
switch ( chain ) {
case 0 :
name = " mixer " ;
break ;
case 1 :
name = " sequencer " ;
if ( unit > = SOUND_STEP )
goto __unknown ;
max_unit = unit + 1 ;
break ;
case 2 :
name = " midi " ;
break ;
case 3 :
name = " dsp " ;
break ;
case 4 :
name = " audio " ;
break ;
case 8 :
name = " sequencer2 " ;
if ( unit > = SOUND_STEP )
goto __unknown ;
max_unit = unit + 1 ;
break ;
case 9 :
name = " dmmidi " ;
break ;
case 10 :
name = " dmfm " ;
break ;
case 12 :
name = " adsp " ;
break ;
case 13 :
name = " amidi " ;
break ;
case 14 :
name = " admmidi " ;
break ;
default :
{
__unknown :
sprintf ( _name , " unknown%d " , chain ) ;
if ( unit > = SOUND_STEP )
strcat ( _name , " - " ) ;
name = _name ;
}
break ;
}
return sound_insert_unit ( & chains [ chain ] , fops , - 1 , unit , max_unit ,
2005-08-30 10:58:37 +04:00
name , S_IRUSR | S_IWUSR , dev ) ;
2005-04-17 02:20:36 +04:00
}
2005-08-30 10:58:37 +04:00
EXPORT_SYMBOL ( register_sound_special_device ) ;
2006-03-28 13:56:41 +04:00
int register_sound_special ( const struct file_operations * fops , int unit )
2005-08-30 10:58:37 +04:00
{
return register_sound_special_device ( fops , unit , NULL ) ;
}
2005-04-17 02:20:36 +04:00
EXPORT_SYMBOL ( register_sound_special ) ;
/**
* register_sound_mixer - register a mixer device
* @ fops : File operations for the driver
* @ dev : Unit number to allocate
*
* Allocate a mixer device . Unit is the number of the mixer requested .
* Pass - 1 to request the next free mixer unit . On success the allocated
* number is returned , on failure a negative error code is returned .
*/
2006-03-28 13:56:41 +04:00
int register_sound_mixer ( const struct file_operations * fops , int dev )
2005-04-17 02:20:36 +04:00
{
return sound_insert_unit ( & chains [ 0 ] , fops , dev , 0 , 128 ,
2005-08-30 10:58:37 +04:00
" mixer " , S_IRUSR | S_IWUSR , NULL ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( register_sound_mixer ) ;
/**
* register_sound_midi - register a midi device
* @ fops : File operations for the driver
* @ dev : Unit number to allocate
*
* Allocate a midi device . Unit is the number of the midi device requested .
* Pass - 1 to request the next free midi unit . On success the allocated
* number is returned , on failure a negative error code is returned .
*/
2006-03-28 13:56:41 +04:00
int register_sound_midi ( const struct file_operations * fops , int dev )
2005-04-17 02:20:36 +04:00
{
return sound_insert_unit ( & chains [ 2 ] , fops , dev , 2 , 130 ,
2005-08-30 10:58:37 +04:00
" midi " , S_IRUSR | S_IWUSR , NULL ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( register_sound_midi ) ;
/*
* DSP ' s are registered as a triple . Register only one and cheat
* in open - see below .
*/
/**
* register_sound_dsp - register a DSP device
* @ fops : File operations for the driver
* @ dev : Unit number to allocate
*
* Allocate a DSP device . Unit is the number of the DSP requested .
* Pass - 1 to request the next free DSP unit . On success the allocated
* number is returned , on failure a negative error code is returned .
*
* This function allocates both the audio and dsp device entries together
* and will always allocate them as a matching pair - eg dsp3 / audio3
*/
2006-03-28 13:56:41 +04:00
int register_sound_dsp ( const struct file_operations * fops , int dev )
2005-04-17 02:20:36 +04:00
{
return sound_insert_unit ( & chains [ 3 ] , fops , dev , 3 , 131 ,
2005-08-30 10:58:37 +04:00
" dsp " , S_IWUSR | S_IRUSR , NULL ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( register_sound_dsp ) ;
/**
* unregister_sound_special - unregister a special sound device
* @ unit : unit number to allocate
*
* Release a sound device that was allocated with
* register_sound_special ( ) . The unit passed is the return value from
* the register function .
*/
void unregister_sound_special ( int unit )
{
sound_remove_unit ( & chains [ unit % SOUND_STEP ] , unit ) ;
}
EXPORT_SYMBOL ( unregister_sound_special ) ;
/**
* unregister_sound_mixer - unregister a mixer
* @ unit : unit number to allocate
*
* Release a sound device that was allocated with register_sound_mixer ( ) .
* The unit passed is the return value from the register function .
*/
void unregister_sound_mixer ( int unit )
{
sound_remove_unit ( & chains [ 0 ] , unit ) ;
}
EXPORT_SYMBOL ( unregister_sound_mixer ) ;
/**
* unregister_sound_midi - unregister a midi device
* @ unit : unit number to allocate
*
* Release a sound device that was allocated with register_sound_midi ( ) .
* The unit passed is the return value from the register function .
*/
void unregister_sound_midi ( int unit )
{
2008-11-20 23:25:25 +03:00
sound_remove_unit ( & chains [ 2 ] , unit ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( unregister_sound_midi ) ;
/**
* unregister_sound_dsp - unregister a DSP device
* @ unit : unit number to allocate
*
* Release a sound device that was allocated with register_sound_dsp ( ) .
* The unit passed is the return value from the register function .
*
* Both of the allocated units are released together automatically .
*/
void unregister_sound_dsp ( int unit )
{
2008-11-20 23:25:25 +03:00
sound_remove_unit ( & chains [ 3 ] , unit ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( unregister_sound_dsp ) ;
static struct sound_unit * __look_for_unit ( int chain , int unit )
{
struct sound_unit * s ;
s = chains [ chain ] ;
while ( s & & s - > unit_minor < = unit )
{
if ( s - > unit_minor = = unit )
return s ;
s = s - > next ;
}
return NULL ;
}
2008-11-20 23:25:25 +03:00
static int soundcore_open ( struct inode * inode , struct file * file )
2005-04-17 02:20:36 +04:00
{
int chain ;
int unit = iminor ( inode ) ;
struct sound_unit * s ;
2006-03-28 13:56:41 +04:00
const struct file_operations * new_fops = NULL ;
2005-04-17 02:20:36 +04:00
chain = unit & 0x0F ;
if ( chain = = 4 | | chain = = 5 ) /* dsp/audio/dsp16 */
{
unit & = 0xF0 ;
unit | = 3 ;
chain = 3 ;
}
spin_lock ( & sound_loader_lock ) ;
s = __look_for_unit ( chain , unit ) ;
if ( s )
new_fops = fops_get ( s - > unit_fops ) ;
2009-08-06 13:14:26 +04:00
if ( preclaim_oss & & ! new_fops ) {
2005-04-17 02:20:36 +04:00
spin_unlock ( & sound_loader_lock ) ;
2009-08-06 13:13:58 +04:00
2005-04-17 02:20:36 +04:00
/*
* Please , don ' t change this order or code .
* For ALSA slot means soundcard and OSS emulation code
* comes as add - on modules which aren ' t depend on
* ALSA toplevel modules for soundcards , thus we need
* load them at first . [ Jaroslav Kysela < perex @ jcu . cz > ]
*/
request_module ( " sound-slot-%i " , unit > > 4 ) ;
request_module ( " sound-service-%i-%i " , unit > > 4 , chain ) ;
2009-08-06 13:13:58 +04:00
/*
* sound - slot / service - * module aliases are scheduled
* for removal in favor of the standard char - major - *
* module aliases . For the time being , generate both
* the legacy and standard module aliases to ease
* transition .
*/
if ( request_module ( " char-major-%d-%d " , SOUND_MAJOR , unit ) > 0 )
request_module ( " char-major-%d " , SOUND_MAJOR ) ;
2005-04-17 02:20:36 +04:00
spin_lock ( & sound_loader_lock ) ;
s = __look_for_unit ( chain , unit ) ;
if ( s )
new_fops = fops_get ( s - > unit_fops ) ;
}
if ( new_fops ) {
/*
* We rely upon the fact that we can ' t be unloaded while the
* subdriver is there , so if - > open ( ) is successful we can
* safely drop the reference counter and if it is not we can
* revert to old - > f_op . Ugly , indeed , but that ' s the cost of
* switching - > f_op in the first place .
*/
int err = 0 ;
2006-03-28 13:56:41 +04:00
const struct file_operations * old_fops = file - > f_op ;
2005-04-17 02:20:36 +04:00
file - > f_op = new_fops ;
spin_unlock ( & sound_loader_lock ) ;
2010-07-04 02:02:31 +04:00
2010-07-11 14:16:36 +04:00
if ( file - > f_op - > open )
2005-04-17 02:20:36 +04:00
err = file - > f_op - > open ( inode , file ) ;
2010-07-04 02:02:31 +04:00
2005-04-17 02:20:36 +04:00
if ( err ) {
fops_put ( file - > f_op ) ;
file - > f_op = fops_get ( old_fops ) ;
}
2010-07-04 02:02:31 +04:00
2005-04-17 02:20:36 +04:00
fops_put ( old_fops ) ;
return err ;
}
spin_unlock ( & sound_loader_lock ) ;
return - ENODEV ;
}
MODULE_ALIAS_CHARDEV_MAJOR ( SOUND_MAJOR ) ;
2008-09-07 19:24:33 +04:00
static void cleanup_oss_soundcore ( void )
2005-04-17 02:20:36 +04:00
{
/* We have nothing to really do here - we know the lists must be
empty */
unregister_chrdev ( SOUND_MAJOR , " sound " ) ;
}
2008-08-28 18:42:51 +04:00
static int __init init_oss_soundcore ( void )
2005-04-17 02:20:36 +04:00
{
2009-08-06 13:14:26 +04:00
if ( preclaim_oss & &
register_chrdev ( SOUND_MAJOR , " sound " , & soundcore_fops ) = = - 1 ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_ERR " soundcore: sound device already in use. \n " ) ;
return - EBUSY ;
}
return 0 ;
}
2008-08-28 18:42:51 +04:00
# endif /* CONFIG_SOUND_OSS_CORE */