2005-04-17 02:20:36 +04:00
/*
* linux / sound / oss / dmasound / dmasound_core . c
*
*
* OSS / Free compatible Atari TT / Falcon and Amiga DMA sound driver for
* Linux / m68k
* Extended to support Power Macintosh for Linux / ppc by Paul Mackerras
*
* ( c ) 1995 by Michael Schlueter & Michael Marte
*
* Michael Schlueter ( michael @ duck . syd . de ) did the basic structure of the VFS
* interface and the u - law to signed byte conversion .
*
* Michael Marte ( marte @ informatik . uni - muenchen . de ) did the sound queue ,
* / dev / mixer , / dev / sndstat and complemented the VFS interface . He would like
* to thank :
* - Michael Schlueter for initial ideas and documentation on the MFP and
* the DMA sound hardware .
* - Therapy ? for their CD ' Troublegum ' which really made me rock .
*
* / dev / sndstat is based on code by Hannu Savolainen , the author of the
* VoxWare family of drivers .
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of this archive
* for more details .
*
* History :
*
* 1995 / 8 / 25 First release
*
* 1995 / 9 / 02 Roman Hodek :
* - Fixed atari_stram_alloc ( ) call , the timer
* programming and several race conditions
* 1995 / 9 / 14 Roman Hodek :
* - After some discussion with Michael Schlueter ,
* revised the interrupt disabling
* - Slightly speeded up U8 - > S8 translation by using
* long operations where possible
* - Added 4 : 3 interpolation for / dev / audio
*
* 1995 / 9 / 20 Torsten Scherer :
* - Fixed a bug in sq_write and changed / dev / audio
* converting to play at 12517 Hz instead of 6258 Hz .
*
* 1995 / 9 / 23 Torsten Scherer :
* - Changed sq_interrupt ( ) and sq_play ( ) to pre - program
* the DMA for another frame while there ' s still one
* running . This allows the IRQ response to be
* arbitrarily delayed and playing will still continue .
*
* 1995 / 10 / 14 Guenther Kelleter , Torsten Scherer :
* - Better support for Falcon audio ( the Falcon doesn ' t
* raise an IRQ at the end of a frame , but at the
* beginning instead ! ) . uses ' if ( codec_dma ) ' in lots
* of places to simply switch between Falcon and TT
* code .
*
* 1995 / 11 / 06 Torsten Scherer :
* - Started introducing a hardware abstraction scheme
* ( may perhaps also serve for Amigas ? )
* - Can now play samples at almost all frequencies by
* means of a more generalized expand routine
* - Takes a good deal of care to cut data only at
* sample sizes
* - Buffer size is now a kernel runtime option
* - Implemented fsync ( ) & several minor improvements
* Guenther Kelleter :
* - Useful hints and bug fixes
* - Cross - checked it for Falcons
*
* 1996 / 3 / 9 Geert Uytterhoeven :
* - Support added for Amiga , A - law , 16 - bit little
* endian .
* - Unification to drivers / sound / dmasound . c .
*
* 1996 / 4 / 6 Martin Mitchell :
* - Updated to 1.3 kernel .
*
* 1996 / 6 / 13 Topi Kanerva :
* - Fixed things that were broken ( mainly the amiga
* 14 - bit routines )
* - / dev / sndstat shows now the real hardware frequency
* - The lowpass filter is disabled by default now
*
* 1996 / 9 / 25 Geert Uytterhoeven :
* - Modularization
*
* 1998 / 6 / 10 Andreas Schwab :
* - Converted to use sound_core
*
* 1999 / 12 / 28 Richard Zidlicky :
* - Added support for Q40
*
* 2000 / 2 / 27 Geert Uytterhoeven :
* - Clean up and split the code into 4 parts :
* o dmasound_core : machine - independent code
* o dmasound_atari : Atari TT and Falcon support
* o dmasound_awacs : Apple PowerMac support
* o dmasound_paula : Amiga support
*
* 2000 / 3 / 25 Geert Uytterhoeven :
* - Integration of dmasound_q40
* - Small clean ups
*
* 2001 / 01 / 26 [ 1.0 ] Iain Sandoe
* - make / dev / sndstat show revision & edition info .
* - since dmasound . mach . sq_setup ( ) can fail on pmac
* its type has been changed to int and the returns
* are checked .
* [ 1.1 ] - stop missing translations from being called .
* 2001 / 02 / 08 [ 1.2 ] - remove unused translation tables & move machine -
* specific tables to low - level .
* - return correct info . for SNDCTL_DSP_GETFMTS .
* [ 1.3 ] - implement SNDCTL_DSP_GETCAPS fully .
* [ 1.4 ] - make / dev / sndstat text length usage deterministic .
* - make / dev / sndstat call to low - level
* dmasound . mach . state_info ( ) pass max space to ll driver .
* - tidy startup banners and output info .
* [ 1.5 ] - tidy up a little ( removed some unused # defines in
* dmasound . h )
* - fix up HAS_RECORD conditionalisation .
* - add record code in places it is missing . . .
* - change buf - sizes to bytes to allow < 1 kb for pmac
* if user param entry is < 256 the value is taken to
* be in kb > 256 is taken to be in bytes .
* - make default buff / frag params conditional on
* machine to allow smaller values for pmac .
* - made the ioctls , read & write comply with the OSS
* rules on setting params .
* - added parsing of _setup ( ) params for record .
* 2001 / 04 / 04 [ 1.6 ] - fix bug where sample rates higher than maximum were
* being reported as OK .
* - fix open ( ) to return - EBUSY as per OSS doc . when
* audio is in use - this is independent of O_NOBLOCK .
* - fix bug where SNDCTL_DSP_POST was blocking .
*/
/* Record capability notes 30/01/2001:
* At present these observations apply only to pmac LL driver ( the only one
* that can do record , at present ) . However , if other LL drivers for machines
* with record are added they may apply .
*
* The fragment parameters for the record and play channels are separate .
* However , if the driver is opened O_RDWR there is no way ( in the current OSS
* API ) to specify their values independently for the record and playback
* channels . Since the only common factor between the input & output is the
* sample rate ( on pmac ) it should be possible to open / dev / dspX O_WRONLY and
* / dev / dspY O_RDONLY . The input & output channels could then have different
* characteristics ( other than the first that sets sample rate claiming the
* right to set it for ever ) . As it stands , the format , channels , number of
* bits & sample rate are assumed to be common . In the future perhaps these
* should be the responsibility of the LL driver - and then if a card really
* does not share items between record & playback they can be specified
* separately .
*/
/* Thread-safeness of shared_resources notes: 31/01/2001
* If the user opens O_RDWR and then splits record & play between two threads
* both of which inherit the fd - and then starts changing things from both
* - we will have difficulty telling .
*
* It ' s bad application coding - but . . .
* TODO : think about how to sort this out . . . without bogging everything down in
* semaphores .
*
* Similarly , the OSS spec says " all changes to parameters must be between
* open ( ) and the first read ( ) or write ( ) . - and a bit later on ( by
* implication ) " between SNDCTL_DSP_RESET and the first read() or write() after
* it " . If the app is multi-threaded and this rule is broken between threads
* we will have trouble spotting it - and the fault will be rather obscure : - (
*
* We will try and put out at least a kmsg if we see it happen . . . but I think
* it will be quite hard to trap it with an - EXXX return . . . because we can ' t
* see the fault until after the damage is done .
*/
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/sound.h>
# include <linux/init.h>
# include <linux/soundcard.h>
# include <linux/poll.h>
# include <linux/smp_lock.h>
# include <asm/uaccess.h>
# include "dmasound.h"
# define DMASOUND_CORE_REVISION 1
# define DMASOUND_CORE_EDITION 6
/*
* Declarations
*/
int dmasound_catchRadius = 0 ;
2006-03-25 14:07:05 +03:00
module_param ( dmasound_catchRadius , int , 0 ) ;
2005-04-17 02:20:36 +04:00
static unsigned int numWriteBufs = DEFAULT_N_BUFFERS ;
2006-03-25 14:07:05 +03:00
module_param ( numWriteBufs , int , 0 ) ;
2005-04-17 02:20:36 +04:00
static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */
2006-03-25 14:07:05 +03:00
module_param ( writeBufSize , int , 0 ) ;
2005-04-17 02:20:36 +04:00
# ifdef HAS_RECORD
static unsigned int numReadBufs = DEFAULT_N_BUFFERS ;
2006-03-25 14:07:05 +03:00
module_param ( numReadBufs , int , 0 ) ;
2005-04-17 02:20:36 +04:00
static unsigned int readBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */
2006-03-25 14:07:05 +03:00
module_param ( readBufSize , int , 0 ) ;
2005-04-17 02:20:36 +04:00
# endif
MODULE_LICENSE ( " GPL " ) ;
# ifdef MODULE
static int sq_unit = - 1 ;
static int mixer_unit = - 1 ;
static int state_unit = - 1 ;
static int irq_installed ;
# endif /* MODULE */
/* software implemented recording volume! */
uint software_input_volume = SW_INPUT_VOLUME_SCALE * SW_INPUT_VOLUME_DEFAULT ;
EXPORT_SYMBOL ( software_input_volume ) ;
/* control over who can modify resources shared between play/record */
static mode_t shared_resource_owner ;
static int shared_resources_initialised ;
/*
* Mid level stuff
*/
struct sound_settings dmasound = { . lock = SPIN_LOCK_UNLOCKED } ;
static inline void sound_silence ( void )
{
dmasound . mach . silence ( ) ; /* _MUST_ stop DMA */
}
static inline int sound_set_format ( int format )
{
return dmasound . mach . setFormat ( format ) ;
}
static int sound_set_speed ( int speed )
{
if ( speed < 0 )
return dmasound . soft . speed ;
/* trap out-of-range speed settings.
at present we allow ( arbitrarily ) low rates - using soft
up - conversion - but we can ' t allow > max because there is
no soft down - conversion .
*/
if ( dmasound . mach . max_dsp_speed & &
( speed > dmasound . mach . max_dsp_speed ) )
speed = dmasound . mach . max_dsp_speed ;
dmasound . soft . speed = speed ;
if ( dmasound . minDev = = SND_DEV_DSP )
dmasound . dsp . speed = dmasound . soft . speed ;
return dmasound . soft . speed ;
}
static int sound_set_stereo ( int stereo )
{
if ( stereo < 0 )
return dmasound . soft . stereo ;
stereo = ! ! stereo ; /* should be 0 or 1 now */
dmasound . soft . stereo = stereo ;
if ( dmasound . minDev = = SND_DEV_DSP )
dmasound . dsp . stereo = stereo ;
return stereo ;
}
static ssize_t sound_copy_translate ( TRANS * trans , const u_char __user * userPtr ,
size_t userCount , u_char frame [ ] ,
ssize_t * frameUsed , ssize_t frameLeft )
{
ssize_t ( * ct_func ) ( const u_char __user * , size_t , u_char * , ssize_t * , ssize_t ) ;
switch ( dmasound . soft . format ) {
case AFMT_MU_LAW :
ct_func = trans - > ct_ulaw ;
break ;
case AFMT_A_LAW :
ct_func = trans - > ct_alaw ;
break ;
case AFMT_S8 :
ct_func = trans - > ct_s8 ;
break ;
case AFMT_U8 :
ct_func = trans - > ct_u8 ;
break ;
case AFMT_S16_BE :
ct_func = trans - > ct_s16be ;
break ;
case AFMT_U16_BE :
ct_func = trans - > ct_u16be ;
break ;
case AFMT_S16_LE :
ct_func = trans - > ct_s16le ;
break ;
case AFMT_U16_LE :
ct_func = trans - > ct_u16le ;
break ;
default :
return 0 ;
}
/* if the user has requested a non-existent translation don't try
to call it but just return 0 bytes moved
*/
if ( ct_func )
return ct_func ( userPtr , userCount , frame , frameUsed , frameLeft ) ;
return 0 ;
}
/*
* / dev / mixer abstraction
*/
static struct {
int busy ;
int modify_counter ;
} mixer ;
static int mixer_open ( struct inode * inode , struct file * file )
{
if ( ! try_module_get ( dmasound . mach . owner ) )
return - ENODEV ;
mixer . busy = 1 ;
return 0 ;
}
static int mixer_release ( struct inode * inode , struct file * file )
{
lock_kernel ( ) ;
mixer . busy = 0 ;
module_put ( dmasound . mach . owner ) ;
unlock_kernel ( ) ;
return 0 ;
}
static int mixer_ioctl ( struct inode * inode , struct file * file , u_int cmd ,
u_long arg )
{
if ( _SIOC_DIR ( cmd ) & _SIOC_WRITE )
mixer . modify_counter + + ;
switch ( cmd ) {
case OSS_GETVERSION :
return IOCTL_OUT ( arg , SOUND_VERSION ) ;
case SOUND_MIXER_INFO :
{
mixer_info info ;
memset ( & info , 0 , sizeof ( info ) ) ;
strlcpy ( info . id , dmasound . mach . name2 , sizeof ( info . id ) ) ;
strlcpy ( info . name , dmasound . mach . name2 , sizeof ( info . name ) ) ;
info . modify_counter = mixer . modify_counter ;
if ( copy_to_user ( ( void __user * ) arg , & info , sizeof ( info ) ) )
return - EFAULT ;
return 0 ;
}
}
if ( dmasound . mach . mixer_ioctl )
return dmasound . mach . mixer_ioctl ( cmd , arg ) ;
return - EINVAL ;
}
2007-02-12 11:55:37 +03:00
static const struct file_operations mixer_fops =
2005-04-17 02:20:36 +04:00
{
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. ioctl = mixer_ioctl ,
. open = mixer_open ,
. release = mixer_release ,
} ;
static void mixer_init ( void )
{
# ifndef MODULE
int mixer_unit ;
# endif
mixer_unit = register_sound_mixer ( & mixer_fops , - 1 ) ;
if ( mixer_unit < 0 )
return ;
mixer . busy = 0 ;
dmasound . treble = 0 ;
dmasound . bass = 0 ;
if ( dmasound . mach . mixer_init )
dmasound . mach . mixer_init ( ) ;
}
/*
* Sound queue stuff , the heart of the driver
*/
struct sound_queue dmasound_write_sq ;
static void sq_reset_output ( void ) ;
# ifdef HAS_RECORD
struct sound_queue dmasound_read_sq ;
static void sq_reset_input ( void ) ;
# endif
static int sq_allocate_buffers ( struct sound_queue * sq , int num , int size )
{
int i ;
if ( sq - > buffers )
return 0 ;
sq - > numBufs = num ;
sq - > bufSize = size ;
sq - > buffers = kmalloc ( num * sizeof ( char * ) , GFP_KERNEL ) ;
if ( ! sq - > buffers )
return - ENOMEM ;
for ( i = 0 ; i < num ; i + + ) {
sq - > buffers [ i ] = dmasound . mach . dma_alloc ( size , GFP_KERNEL ) ;
if ( ! sq - > buffers [ i ] ) {
while ( i - - )
dmasound . mach . dma_free ( sq - > buffers [ i ] , size ) ;
kfree ( sq - > buffers ) ;
sq - > buffers = NULL ;
return - ENOMEM ;
}
}
return 0 ;
}
static void sq_release_buffers ( struct sound_queue * sq )
{
int i ;
if ( sq - > buffers ) {
for ( i = 0 ; i < sq - > numBufs ; i + + )
dmasound . mach . dma_free ( sq - > buffers [ i ] , sq - > bufSize ) ;
kfree ( sq - > buffers ) ;
sq - > buffers = NULL ;
}
}
static int sq_setup ( struct sound_queue * sq )
{
int ( * setup_func ) ( void ) = NULL ;
int hard_frame ;
if ( sq - > locked ) { /* are we already set? - and not changeable */
# ifdef DEBUG_DMASOUND
printk ( " dmasound_core: tried to sq_setup a locked queue \n " ) ;
# endif
return - EINVAL ;
}
sq - > locked = 1 ; /* don't think we have a race prob. here _check_ */
/* make sure that the parameters are set up
This should have been done already . . .
*/
dmasound . mach . init ( ) ;
/* OK. If the user has set fragment parameters explicitly, then we
should leave them alone . . . as long as they are valid .
Invalid user fragment params can occur if we allow the whole buffer
to be used when the user requests the fragments sizes ( with no soft
x - lation ) and then the user subsequently sets a soft x - lation that
requires increased internal buffering .
Othwerwise ( if the user did not set them ) OSS says that we should
select frag params on the basis of 0.5 s output & 0.1 s input
latency . ( TODO . For now we will copy in the defaults . )
*/
if ( sq - > user_frags < = 0 ) {
sq - > max_count = sq - > numBufs ;
sq - > max_active = sq - > numBufs ;
sq - > block_size = sq - > bufSize ;
/* set up the user info */
sq - > user_frags = sq - > numBufs ;
sq - > user_frag_size = sq - > bufSize ;
sq - > user_frag_size * =
( dmasound . soft . size * ( dmasound . soft . stereo + 1 ) ) ;
sq - > user_frag_size / =
( dmasound . hard . size * ( dmasound . hard . stereo + 1 ) ) ;
} else {
/* work out requested block size */
sq - > block_size = sq - > user_frag_size ;
sq - > block_size * =
( dmasound . hard . size * ( dmasound . hard . stereo + 1 ) ) ;
sq - > block_size / =
( dmasound . soft . size * ( dmasound . soft . stereo + 1 ) ) ;
/* the user wants to write frag-size chunks */
sq - > block_size * = dmasound . hard . speed ;
sq - > block_size / = dmasound . soft . speed ;
/* this only works for size values which are powers of 2 */
hard_frame =
( dmasound . hard . size * ( dmasound . hard . stereo + 1 ) ) / 8 ;
sq - > block_size + = ( hard_frame - 1 ) ;
sq - > block_size & = ~ ( hard_frame - 1 ) ; /* make sure we are aligned */
/* let's just check for obvious mistakes */
if ( sq - > block_size < = 0 | | sq - > block_size > sq - > bufSize ) {
# ifdef DEBUG_DMASOUND
printk ( " dmasound_core: invalid frag size (user set %d) \n " , sq - > user_frag_size ) ;
# endif
sq - > block_size = sq - > bufSize ;
}
if ( sq - > user_frags < = sq - > numBufs ) {
sq - > max_count = sq - > user_frags ;
/* if user has set max_active - then use it */
sq - > max_active = ( sq - > max_active < = sq - > max_count ) ?
sq - > max_active : sq - > max_count ;
} else {
# ifdef DEBUG_DMASOUND
printk ( " dmasound_core: invalid frag count (user set %d) \n " , sq - > user_frags ) ;
# endif
sq - > max_count =
sq - > max_active = sq - > numBufs ;
}
}
sq - > front = sq - > count = sq - > rear_size = 0 ;
sq - > syncing = 0 ;
sq - > active = 0 ;
if ( sq = = & write_sq ) {
sq - > rear = - 1 ;
setup_func = dmasound . mach . write_sq_setup ;
}
# ifdef HAS_RECORD
else {
sq - > rear = 0 ;
setup_func = dmasound . mach . read_sq_setup ;
}
# endif
if ( setup_func )
return setup_func ( ) ;
return 0 ;
}
static inline void sq_play ( void )
{
dmasound . mach . play ( ) ;
}
static ssize_t sq_write ( struct file * file , const char __user * src , size_t uLeft ,
loff_t * ppos )
{
ssize_t uWritten = 0 ;
u_char * dest ;
ssize_t uUsed = 0 , bUsed , bLeft ;
unsigned long flags ;
/* ++TeSche: Is something like this necessary?
* Hey , that ' s an honest question ! Or does any other part of the
* filesystem already checks this situation ? I really don ' t know .
*/
if ( uLeft = = 0 )
return 0 ;
/* implement any changes we have made to the soft/hard params.
this is not satisfactory really , all we have done up to now is to
say what we would like - there hasn ' t been any real checking of capability
*/
if ( shared_resources_initialised = = 0 ) {
dmasound . mach . init ( ) ;
shared_resources_initialised = 1 ;
}
/* set up the sq if it is not already done. This may seem a dumb place
to do it - but it is what OSS requires . It means that write ( ) can
return memory allocation errors . To avoid this possibility use the
GETBLKSIZE or GETOSPACE ioctls ( after you ' ve fiddled with all the
params you want to change ) - these ioctls also force the setup .
*/
if ( write_sq . locked = = 0 ) {
if ( ( uWritten = sq_setup ( & write_sq ) ) < 0 ) return uWritten ;
uWritten = 0 ;
}
/* FIXME: I think that this may be the wrong behaviour when we get strapped
for time and the cpu is close to being ( or actually ) behind in sending data .
- because we ' ve lost the time that the N samples , already in the buffer ,
would have given us to get here with the next lot from the user .
*/
/* The interrupt doesn't start to play the last, incomplete frame.
* Thus we can append to it without disabling the interrupts ! ( Note
* also that write_sq . rear isn ' t affected by the interrupt . )
*/
/* as of 1.6 this behaviour changes if SNDCTL_DSP_POST has been issued:
this will mimic the behaviour of syncing and allow the sq_play ( ) to
queue a partial fragment . Since sq_play ( ) may / will be called from
the IRQ handler - at least on Pmac we have to deal with it .
The strategy - possibly not optimum - is to kill _POST status if we
get here . This seems , at least , reasonable - in the sense that POST
is supposed to indicate that we might not write before the queue
is drained - and if we get here in time then it does not apply .
*/
spin_lock_irqsave ( & dmasound . lock , flags ) ;
write_sq . syncing & = ~ 2 ; /* take out POST status */
spin_unlock_irqrestore ( & dmasound . lock , flags ) ;
if ( write_sq . count > 0 & &
( bLeft = write_sq . block_size - write_sq . rear_size ) > 0 ) {
dest = write_sq . buffers [ write_sq . rear ] ;
bUsed = write_sq . rear_size ;
uUsed = sound_copy_translate ( dmasound . trans_write , src , uLeft ,
dest , & bUsed , bLeft ) ;
if ( uUsed < = 0 )
return uUsed ;
src + = uUsed ;
uWritten + = uUsed ;
uLeft = ( uUsed < = uLeft ) ? ( uLeft - uUsed ) : 0 ; /* paranoia */
write_sq . rear_size = bUsed ;
}
while ( uLeft ) {
while ( write_sq . count > = write_sq . max_active ) {
sq_play ( ) ;
if ( write_sq . open_mode & O_NONBLOCK )
return uWritten > 0 ? uWritten : - EAGAIN ;
SLEEP ( write_sq . action_queue ) ;
if ( signal_pending ( current ) )
return uWritten > 0 ? uWritten : - EINTR ;
}
/* Here, we can avoid disabling the interrupt by first
* copying and translating the data , and then updating
* the write_sq variables . Until this is done , the interrupt
* won ' t see the new frame and we can work on it
* undisturbed .
*/
dest = write_sq . buffers [ ( write_sq . rear + 1 ) % write_sq . max_count ] ;
bUsed = 0 ;
bLeft = write_sq . block_size ;
uUsed = sound_copy_translate ( dmasound . trans_write , src , uLeft ,
dest , & bUsed , bLeft ) ;
if ( uUsed < = 0 )
break ;
src + = uUsed ;
uWritten + = uUsed ;
uLeft = ( uUsed < = uLeft ) ? ( uLeft - uUsed ) : 0 ; /* paranoia */
if ( bUsed ) {
write_sq . rear = ( write_sq . rear + 1 ) % write_sq . max_count ;
write_sq . rear_size = bUsed ;
write_sq . count + + ;
}
} /* uUsed may have been 0 */
sq_play ( ) ;
return uUsed < 0 ? uUsed : uWritten ;
}
static unsigned int sq_poll ( struct file * file , struct poll_table_struct * wait )
{
unsigned int mask = 0 ;
int retVal ;
if ( write_sq . locked = = 0 ) {
if ( ( retVal = sq_setup ( & write_sq ) ) < 0 )
return retVal ;
return 0 ;
}
if ( file - > f_mode & FMODE_WRITE )
poll_wait ( file , & write_sq . action_queue , wait ) ;
# ifdef HAS_RECORD
if ( file - > f_mode & FMODE_READ )
poll_wait ( file , & read_sq . action_queue , wait ) ;
if ( file - > f_mode & FMODE_READ )
if ( read_sq . block_size - read_sq . rear_size > 0 )
mask | = POLLIN | POLLRDNORM ;
# endif
if ( file - > f_mode & FMODE_WRITE )
if ( write_sq . count < write_sq . max_active | | write_sq . block_size - write_sq . rear_size > 0 )
mask | = POLLOUT | POLLWRNORM ;
return mask ;
}
# ifdef HAS_RECORD
/*
* Here is how the values are used for reading .
* The value ' active ' simply indicates the DMA is running . This is done
* so the driver semantics are DMA starts when the first read is posted .
* The value ' front ' indicates the buffer we should next send to the user .
* The value ' rear ' indicates the buffer the DMA is currently filling .
* When ' front ' = = ' rear ' the buffer " ring " is empty ( we always have an
* empty available ) . The ' rear_size ' is used to track partial offsets
* into the buffer we are currently returning to the user .
* This level ( > [ 1.5 ] ) doesn ' t care what strategy the LL driver uses with
* DMA on over - run . It can leave it running ( and keep active = = 1 ) or it
* can kill it and set active = = 0 in which case this routine will spot
* it and restart the DMA .
*/
static ssize_t sq_read ( struct file * file , char __user * dst , size_t uLeft ,
loff_t * ppos )
{
ssize_t uRead , bLeft , bUsed , uUsed ;
if ( uLeft = = 0 )
return 0 ;
/* cater for the compatibility mode - record compiled in but no LL */
if ( dmasound . mach . record = = NULL )
return - EINVAL ;
/* see comment in sq_write()
*/
if ( shared_resources_initialised = = 0 ) {
dmasound . mach . init ( ) ;
shared_resources_initialised = 1 ;
}
/* set up the sq if it is not already done. see comments in sq_write().
*/
if ( read_sq . locked = = 0 ) {
if ( ( uRead = sq_setup ( & read_sq ) ) < 0 )
return uRead ;
}
uRead = 0 ;
/* Move what the user requests, depending upon other options.
*/
while ( uLeft > 0 ) {
/* we happened to get behind and the LL driver killed DMA
then we should set it going again . This also sets it
going the first time through .
*/
if ( ! read_sq . active )
dmasound . mach . record ( ) ;
/* When front == rear, the DMA is not done yet.
*/
while ( read_sq . front = = read_sq . rear ) {
if ( read_sq . open_mode & O_NONBLOCK ) {
return uRead > 0 ? uRead : - EAGAIN ;
}
SLEEP ( read_sq . action_queue ) ;
if ( signal_pending ( current ) )
return uRead > 0 ? uRead : - EINTR ;
}
/* The amount we move is either what is left in the
* current buffer or what the user wants .
*/
bLeft = read_sq . block_size - read_sq . rear_size ;
bUsed = read_sq . rear_size ;
uUsed = sound_copy_translate ( dmasound . trans_read , dst , uLeft ,
read_sq . buffers [ read_sq . front ] ,
& bUsed , bLeft ) ;
if ( uUsed < = 0 )
return uUsed ;
dst + = uUsed ;
uRead + = uUsed ;
uLeft - = uUsed ;
read_sq . rear_size + = bUsed ;
if ( read_sq . rear_size > = read_sq . block_size ) {
read_sq . rear_size = 0 ;
read_sq . front + + ;
if ( read_sq . front > = read_sq . max_active )
read_sq . front = 0 ;
}
}
return uRead ;
}
# endif /* HAS_RECORD */
static inline void sq_init_waitqueue ( struct sound_queue * sq )
{
init_waitqueue_head ( & sq - > action_queue ) ;
init_waitqueue_head ( & sq - > open_queue ) ;
init_waitqueue_head ( & sq - > sync_queue ) ;
sq - > busy = 0 ;
}
#if 0 /* blocking open() */
static inline void sq_wake_up ( struct sound_queue * sq , struct file * file ,
mode_t mode )
{
if ( file - > f_mode & mode ) {
sq - > busy = 0 ; /* CHECK: IS THIS OK??? */
WAKE_UP ( sq - > open_queue ) ;
}
}
# endif
static int sq_open2 ( struct sound_queue * sq , struct file * file , mode_t mode ,
int numbufs , int bufsize )
{
int rc = 0 ;
if ( file - > f_mode & mode ) {
if ( sq - > busy ) {
#if 0 /* blocking open() */
rc = - EBUSY ;
if ( file - > f_flags & O_NONBLOCK )
return rc ;
rc = - EINTR ;
while ( sq - > busy ) {
SLEEP ( sq - > open_queue ) ;
if ( signal_pending ( current ) )
return rc ;
}
rc = 0 ;
# else
/* OSS manual says we will return EBUSY regardless
of O_NOBLOCK .
*/
return - EBUSY ;
# endif
}
sq - > busy = 1 ; /* Let's play spot-the-race-condition */
/* allocate the default number & size of buffers.
( i . e . specified in _setup ( ) or as module params )
can ' t be changed at the moment - but _could_ be perhaps
in the setfragments ioctl .
*/
if ( ( rc = sq_allocate_buffers ( sq , numbufs , bufsize ) ) ) {
#if 0 /* blocking open() */
sq_wake_up ( sq , file , mode ) ;
# else
sq - > busy = 0 ;
# endif
return rc ;
}
sq - > open_mode = file - > f_mode ;
}
return rc ;
}
# define write_sq_init_waitqueue() sq_init_waitqueue(&write_sq)
#if 0 /* blocking open() */
# define write_sq_wake_up(file) sq_wake_up(&write_sq, file, FMODE_WRITE)
# endif
# define write_sq_release_buffers() sq_release_buffers(&write_sq)
# define write_sq_open(file) \
sq_open2 ( & write_sq , file , FMODE_WRITE , numWriteBufs , writeBufSize )
# ifdef HAS_RECORD
# define read_sq_init_waitqueue() sq_init_waitqueue(&read_sq)
#if 0 /* blocking open() */
# define read_sq_wake_up(file) sq_wake_up(&read_sq, file, FMODE_READ)
# endif
# define read_sq_release_buffers() sq_release_buffers(&read_sq)
# define read_sq_open(file) \
sq_open2 ( & read_sq , file , FMODE_READ , numReadBufs , readBufSize )
# else
# define read_sq_init_waitqueue() do {} while (0)
#if 0 /* blocking open() */
# define read_sq_wake_up(file) do {} while (0)
# endif
# define read_sq_release_buffers() do {} while (0)
# define sq_reset_input() do {} while (0)
# endif
static int sq_open ( struct inode * inode , struct file * file )
{
int rc ;
if ( ! try_module_get ( dmasound . mach . owner ) )
return - ENODEV ;
rc = write_sq_open ( file ) ; /* checks the f_mode */
if ( rc )
goto out ;
# ifdef HAS_RECORD
if ( dmasound . mach . record ) {
rc = read_sq_open ( file ) ; /* checks the f_mode */
if ( rc )
goto out ;
} else { /* no record function installed; in compat mode */
if ( file - > f_mode & FMODE_READ ) {
/* TODO: if O_RDWR, release any resources grabbed by write part */
rc = - ENXIO ;
goto out ;
}
}
# else /* !HAS_RECORD */
if ( file - > f_mode & FMODE_READ ) {
/* TODO: if O_RDWR, release any resources grabbed by write part */
rc = - ENXIO ; /* I think this is what is required by open(2) */
goto out ;
}
# endif /* HAS_RECORD */
if ( dmasound . mach . sq_open )
dmasound . mach . sq_open ( file - > f_mode ) ;
/* CHECK whether this is sensible - in the case that dsp0 could be opened
O_RDONLY and dsp1 could be opened O_WRONLY
*/
dmasound . minDev = iminor ( inode ) & 0x0f ;
/* OK. - we should make some attempt at consistency. At least the H'ware
options should be set with a valid mode . We will make it that the LL
driver must supply defaults for hard & soft params .
*/
if ( shared_resource_owner = = 0 ) {
/* you can make this AFMT_U8/mono/8K if you want to mimic old
OSS behaviour - while we still have soft translations ; - ) */
dmasound . soft = dmasound . mach . default_soft ;
dmasound . dsp = dmasound . mach . default_soft ;
dmasound . hard = dmasound . mach . default_hard ;
}
# ifndef DMASOUND_STRICT_OSS_COMPLIANCE
/* none of the current LL drivers can actually do this "native" at the moment
OSS does not really require us to supply / dev / audio if we can ' t do it .
*/
if ( dmasound . minDev = = SND_DEV_AUDIO ) {
sound_set_speed ( 8000 ) ;
sound_set_stereo ( 0 ) ;
sound_set_format ( AFMT_MU_LAW ) ;
}
# endif
return 0 ;
out :
module_put ( dmasound . mach . owner ) ;
return rc ;
}
static void sq_reset_output ( void )
{
sound_silence ( ) ; /* this _must_ stop DMA, we might be about to lose the buffers */
write_sq . active = 0 ;
write_sq . count = 0 ;
write_sq . rear_size = 0 ;
/* write_sq.front = (write_sq.rear+1) % write_sq.max_count;*/
write_sq . front = 0 ;
write_sq . rear = - 1 ; /* same as for set-up */
/* OK - we can unlock the parameters and fragment settings */
write_sq . locked = 0 ;
write_sq . user_frags = 0 ;
write_sq . user_frag_size = 0 ;
}
# ifdef HAS_RECORD
static void sq_reset_input ( void )
{
if ( dmasound . mach . record & & read_sq . active ) {
if ( dmasound . mach . abort_read ) { /* this routine must really be present */
read_sq . syncing = 1 ;
/* this can use the read_sq.sync_queue to sleep if
necessary - it should not return until DMA
is really stopped - because we might deallocate
the buffers as the next action . . .
*/
dmasound . mach . abort_read ( ) ;
} else {
printk ( KERN_ERR
" dmasound_core: %s has no abort_read()!! all bets are off \n " ,
dmasound . mach . name ) ;
}
}
read_sq . syncing =
read_sq . active =
read_sq . front =
read_sq . count =
read_sq . rear = 0 ;
/* OK - we can unlock the parameters and fragment settings */
read_sq . locked = 0 ;
read_sq . user_frags = 0 ;
read_sq . user_frag_size = 0 ;
}
# endif
static void sq_reset ( void )
{
sq_reset_output ( ) ;
sq_reset_input ( ) ;
/* we could consider resetting the shared_resources_owner here... but I
think it is probably still rather non - obvious to application writer
*/
/* we release everything else though */
shared_resources_initialised = 0 ;
}
static int sq_fsync ( struct file * filp , struct dentry * dentry )
{
int rc = 0 ;
int timeout = 5 ;
write_sq . syncing | = 1 ;
sq_play ( ) ; /* there may be an incomplete frame waiting */
while ( write_sq . active ) {
SLEEP ( write_sq . sync_queue ) ;
if ( signal_pending ( current ) ) {
/* While waiting for audio output to drain, an
* interrupt occurred . Stop audio output immediately
* and clear the queue . */
sq_reset_output ( ) ;
rc = - EINTR ;
break ;
}
if ( ! - - timeout ) {
printk ( KERN_WARNING " dmasound: Timeout draining output \n " ) ;
sq_reset_output ( ) ;
rc = - EIO ;
break ;
}
}
/* flag no sync regardless of whether we had a DSP_POST or not */
write_sq . syncing = 0 ;
return rc ;
}
static int sq_release ( struct inode * inode , struct file * file )
{
int rc = 0 ;
lock_kernel ( ) ;
# ifdef HAS_RECORD
/* probably best to do the read side first - so that time taken to do it
overlaps with playing any remaining output samples .
*/
if ( file - > f_mode & FMODE_READ ) {
sq_reset_input ( ) ; /* make sure dma is stopped and all is quiet */
read_sq_release_buffers ( ) ;
read_sq . busy = 0 ;
}
# endif
if ( file - > f_mode & FMODE_WRITE ) {
if ( write_sq . busy )
2006-12-08 13:37:40 +03:00
rc = sq_fsync ( file , file - > f_path . dentry ) ;
2005-04-17 02:20:36 +04:00
sq_reset_output ( ) ; /* make sure dma is stopped and all is quiet */
write_sq_release_buffers ( ) ;
write_sq . busy = 0 ;
}
if ( file - > f_mode & shared_resource_owner ) { /* it's us that has them */
shared_resource_owner = 0 ;
shared_resources_initialised = 0 ;
dmasound . hard = dmasound . mach . default_hard ;
}
module_put ( dmasound . mach . owner ) ;
#if 0 /* blocking open() */
/* Wake up a process waiting for the queue being released.
* Note : There may be several processes waiting for a call
* to open ( ) returning . */
/* Iain: hmm I don't understand this next comment ... */
/* There is probably a DOS atack here. They change the mode flag. */
/* XXX add check here,*/
read_sq_wake_up ( file ) ; /* checks f_mode */
write_sq_wake_up ( file ) ; /* checks f_mode */
# endif /* blocking open() */
unlock_kernel ( ) ;
return rc ;
}
/* here we see if we have a right to modify format, channels, size and so on
if no - one else has claimed it already then we do . . .
TODO : We might change this to mask O_RDWR such that only one or the other channel
is the owner - if we have problems .
*/
static int shared_resources_are_mine ( mode_t md )
{
if ( shared_resource_owner )
return ( shared_resource_owner & md ) ;
else {
shared_resource_owner = md ;
return 1 ;
}
}
/* if either queue is locked we must deny the right to change shared params
*/
static int queues_are_quiescent ( void )
{
# ifdef HAS_RECORD
if ( dmasound . mach . record )
if ( read_sq . locked )
return 0 ;
# endif
if ( write_sq . locked )
return 0 ;
return 1 ;
}
/* check and set a queue's fragments per user's wishes...
we will check against the pre - defined literals and the actual sizes .
This is a bit fraught - because soft translations can mess with our
buffer requirements * after * this call - OSS says " call setfrags first "
*/
/* It is possible to replace all the -EINVAL returns with an override that
just puts the allowable value in . This may be what many OSS apps require
*/
static int set_queue_frags ( struct sound_queue * sq , int bufs , int size )
{
if ( sq - > locked ) {
# ifdef DEBUG_DMASOUND
printk ( " dmasound_core: tried to set_queue_frags on a locked queue \n " ) ;
# endif
return - EINVAL ;
}
if ( ( size < MIN_FRAG_SIZE ) | | ( size > MAX_FRAG_SIZE ) )
return - EINVAL ;
size = ( 1 < < size ) ; /* now in bytes */
if ( size > sq - > bufSize )
return - EINVAL ; /* this might still not work */
if ( bufs < = 0 )
return - EINVAL ;
if ( bufs > sq - > numBufs ) /* the user is allowed say "don't care" with 0x7fff */
bufs = sq - > numBufs ;
/* there is, currently, no way to specify max_active separately
from max_count . This could be a LL driver issue - I guess
if there is a requirement for these values to be different then
we will have to pass that info . up to this level .
*/
sq - > user_frags =
sq - > max_active = bufs ;
sq - > user_frag_size = size ;
return 0 ;
}
static int sq_ioctl ( struct inode * inode , struct file * file , u_int cmd ,
u_long arg )
{
int val , result ;
u_long fmt ;
int data ;
int size , nbufs ;
audio_buf_info info ;
switch ( cmd ) {
case SNDCTL_DSP_RESET :
sq_reset ( ) ;
return 0 ;
break ;
case SNDCTL_DSP_GETFMTS :
fmt = dmasound . mach . hardware_afmts ; /* this is what OSS says.. */
return IOCTL_OUT ( arg , fmt ) ;
break ;
case SNDCTL_DSP_GETBLKSIZE :
/* this should tell the caller about bytes that the app can
read / write - the app doesn ' t care about our internal buffers .
We force sq_setup ( ) here as per OSS 1.1 ( which should
compute the values necessary ) .
Since there is no mechanism to specify read / write separately , for
fds opened O_RDWR , the write_sq values will , arbitrarily , overwrite
the read_sq ones .
*/
size = 0 ;
# ifdef HAS_RECORD
if ( dmasound . mach . record & & ( file - > f_mode & FMODE_READ ) ) {
if ( ! read_sq . locked )
sq_setup ( & read_sq ) ; /* set params */
size = read_sq . user_frag_size ;
}
# endif
if ( file - > f_mode & FMODE_WRITE ) {
if ( ! write_sq . locked )
sq_setup ( & write_sq ) ;
size = write_sq . user_frag_size ;
}
return IOCTL_OUT ( arg , size ) ;
break ;
case SNDCTL_DSP_POST :
/* all we are going to do is to tell the LL that any
partial frags can be queued for output .
The LL will have to clear this flag when last output
is queued .
*/
write_sq . syncing | = 0x2 ;
sq_play ( ) ;
return 0 ;
case SNDCTL_DSP_SYNC :
/* This call, effectively, has the same behaviour as SNDCTL_DSP_RESET
except that it waits for output to finish before resetting
everything - read , however , is killed imediately .
*/
result = 0 ;
if ( ( file - > f_mode & FMODE_READ ) & & dmasound . mach . record )
sq_reset_input ( ) ;
if ( file - > f_mode & FMODE_WRITE ) {
2006-12-08 13:37:40 +03:00
result = sq_fsync ( file , file - > f_path . dentry ) ;
2005-04-17 02:20:36 +04:00
sq_reset_output ( ) ;
}
/* if we are the shared resource owner then release them */
if ( file - > f_mode & shared_resource_owner )
shared_resources_initialised = 0 ;
return result ;
break ;
case SOUND_PCM_READ_RATE :
return IOCTL_OUT ( arg , dmasound . soft . speed ) ;
case SNDCTL_DSP_SPEED :
/* changing this on the fly will have weird effects on the sound.
Where there are rate conversions implemented in soft form - it
will cause the _ctx_xxx ( ) functions to be substituted .
However , there doesn ' t appear to be any reason to dis - allow it from
a driver pov .
*/
if ( shared_resources_are_mine ( file - > f_mode ) ) {
IOCTL_IN ( arg , data ) ;
data = sound_set_speed ( data ) ;
shared_resources_initialised = 0 ;
return IOCTL_OUT ( arg , data ) ;
} else
return - EINVAL ;
break ;
/* OSS says these next 4 actions are undefined when the device is
busy / active - we will just return - EINVAL .
To be allowed to change one - ( a ) you have to own the right
( b ) the queue ( s ) must be quiescent
*/
case SNDCTL_DSP_STEREO :
if ( shared_resources_are_mine ( file - > f_mode ) & &
queues_are_quiescent ( ) ) {
IOCTL_IN ( arg , data ) ;
shared_resources_initialised = 0 ;
return IOCTL_OUT ( arg , sound_set_stereo ( data ) ) ;
} else
return - EINVAL ;
break ;
case SOUND_PCM_WRITE_CHANNELS :
if ( shared_resources_are_mine ( file - > f_mode ) & &
queues_are_quiescent ( ) ) {
IOCTL_IN ( arg , data ) ;
/* the user might ask for 20 channels, we will return 1 or 2 */
shared_resources_initialised = 0 ;
return IOCTL_OUT ( arg , sound_set_stereo ( data - 1 ) + 1 ) ;
} else
return - EINVAL ;
break ;
case SNDCTL_DSP_SETFMT :
if ( shared_resources_are_mine ( file - > f_mode ) & &
queues_are_quiescent ( ) ) {
int format ;
IOCTL_IN ( arg , data ) ;
shared_resources_initialised = 0 ;
format = sound_set_format ( data ) ;
result = IOCTL_OUT ( arg , format ) ;
if ( result < 0 )
return result ;
if ( format ! = data & & data ! = AFMT_QUERY )
return - EINVAL ;
return 0 ;
} else
return - EINVAL ;
case SNDCTL_DSP_SUBDIVIDE :
return - EINVAL ;
case SNDCTL_DSP_SETFRAGMENT :
/* we can do this independently for the two queues - with the
proviso that for fds opened O_RDWR we cannot separate the
actions and both queues will be set per the last call .
NOTE : this does * NOT * actually set the queue up - merely
registers our intentions .
*/
IOCTL_IN ( arg , data ) ;
result = 0 ;
nbufs = ( data > > 16 ) & 0x7fff ; /* 0x7fff is 'use maximum' */
size = data & 0xffff ;
# ifdef HAS_RECORD
if ( ( file - > f_mode & FMODE_READ ) & & dmasound . mach . record ) {
result = set_queue_frags ( & read_sq , nbufs , size ) ;
if ( result )
return result ;
}
# endif
if ( file - > f_mode & FMODE_WRITE ) {
result = set_queue_frags ( & write_sq , nbufs , size ) ;
if ( result )
return result ;
}
/* NOTE: this return value is irrelevant - OSS specifically says that
the value is ' random ' and that the user _must_ check the actual
frags values using SNDCTL_DSP_GETBLKSIZE or similar */
return IOCTL_OUT ( arg , data ) ;
break ;
case SNDCTL_DSP_GETOSPACE :
/*
*/
if ( file - > f_mode & FMODE_WRITE ) {
if ( ! write_sq . locked )
sq_setup ( & write_sq ) ;
info . fragments = write_sq . max_active - write_sq . count ;
info . fragstotal = write_sq . max_active ;
info . fragsize = write_sq . user_frag_size ;
info . bytes = info . fragments * info . fragsize ;
if ( copy_to_user ( ( void __user * ) arg , & info , sizeof ( info ) ) )
return - EFAULT ;
return 0 ;
} else
return - EINVAL ;
break ;
case SNDCTL_DSP_GETCAPS :
val = dmasound . mach . capabilities & 0xffffff00 ;
return IOCTL_OUT ( arg , val ) ;
default :
return mixer_ioctl ( inode , file , cmd , arg ) ;
}
return - EINVAL ;
}
2007-02-12 11:55:37 +03:00
static const struct file_operations sq_fops =
2005-04-17 02:20:36 +04:00
{
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sq_write ,
. poll = sq_poll ,
. ioctl = sq_ioctl ,
. open = sq_open ,
. release = sq_release ,
2007-03-14 12:04:31 +03:00
} ;
2005-04-17 02:20:36 +04:00
# ifdef HAS_RECORD
2007-03-14 12:04:31 +03:00
static const struct file_operations sq_fops_record =
{
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sq_write ,
. poll = sq_poll ,
. ioctl = sq_ioctl ,
. open = sq_open ,
. release = sq_release ,
. read = sq_read ,
2005-04-17 02:20:36 +04:00
} ;
2007-03-14 12:04:31 +03:00
# endif
2005-04-17 02:20:36 +04:00
static int sq_init ( void )
{
2007-03-14 12:04:31 +03:00
const struct file_operations * fops = & sq_fops ;
2005-04-17 02:20:36 +04:00
# ifndef MODULE
int sq_unit ;
# endif
# ifdef HAS_RECORD
if ( dmasound . mach . record )
2007-03-14 12:04:31 +03:00
fops = & sq_fops_record ;
2005-04-17 02:20:36 +04:00
# endif
2007-03-14 12:04:31 +03:00
sq_unit = register_sound_dsp ( fops , - 1 ) ;
2005-04-17 02:20:36 +04:00
if ( sq_unit < 0 ) {
printk ( KERN_ERR " dmasound_core: couldn't register fops \n " ) ;
return sq_unit ;
}
write_sq_init_waitqueue ( ) ;
read_sq_init_waitqueue ( ) ;
/* These parameters will be restored for every clean open()
* in the case of multiple open ( ) s ( e . g . dsp0 & dsp1 ) they
* will be set so long as the shared resources have no owner .
*/
if ( shared_resource_owner = = 0 ) {
dmasound . soft = dmasound . mach . default_soft ;
dmasound . hard = dmasound . mach . default_hard ;
dmasound . dsp = dmasound . mach . default_soft ;
shared_resources_initialised = 0 ;
}
return 0 ;
}
/*
* / dev / sndstat
*/
/* we allow more space for record-enabled because there are extra output lines.
the number here must include the amount we are prepared to give to the low - level
driver .
*/
# ifdef HAS_RECORD
# define STAT_BUFF_LEN 1024
# else
# define STAT_BUFF_LEN 768
# endif
/* this is how much space we will allow the low-level driver to use
in the stat buffer . Currently , 2 * ( 80 character line + < NL > ) .
We do not police this ( it is up to the ll driver to be honest ) .
*/
# define LOW_LEVEL_STAT_ALLOC 162
static struct {
int busy ;
char buf [ STAT_BUFF_LEN ] ; /* state.buf should not overflow! */
int len , ptr ;
} state ;
/* publish this function for use by low-level code, if required */
char * get_afmt_string ( int afmt )
{
switch ( afmt ) {
case AFMT_MU_LAW :
return " mu-law " ;
break ;
case AFMT_A_LAW :
return " A-law " ;
break ;
case AFMT_U8 :
return " unsigned 8 bit " ;
break ;
case AFMT_S8 :
return " signed 8 bit " ;
break ;
case AFMT_S16_BE :
return " signed 16 bit BE " ;
break ;
case AFMT_U16_BE :
return " unsigned 16 bit BE " ;
break ;
case AFMT_S16_LE :
return " signed 16 bit LE " ;
break ;
case AFMT_U16_LE :
return " unsigned 16 bit LE " ;
break ;
case 0 :
return " format not set " ;
break ;
default :
break ;
}
return " ERROR: Unsupported AFMT_XXXX code " ;
}
static int state_open ( struct inode * inode , struct file * file )
{
char * buffer = state . buf ;
int len = 0 ;
if ( state . busy )
return - EBUSY ;
if ( ! try_module_get ( dmasound . mach . owner ) )
return - ENODEV ;
state . ptr = 0 ;
state . busy = 1 ;
len + = sprintf ( buffer + len , " %sDMA sound driver rev %03d : \n " ,
dmasound . mach . name , ( DMASOUND_CORE_REVISION < < 4 ) +
( ( dmasound . mach . version > > 8 ) & 0x0f ) ) ;
len + = sprintf ( buffer + len ,
" Core driver edition %02d.%02d : %s driver edition %02d.%02d \n " ,
DMASOUND_CORE_REVISION , DMASOUND_CORE_EDITION , dmasound . mach . name2 ,
( dmasound . mach . version > > 8 ) , ( dmasound . mach . version & 0xff ) ) ;
/* call the low-level module to fill in any stat info. that it has
if present . Maximum buffer usage is specified .
*/
if ( dmasound . mach . state_info )
len + = dmasound . mach . state_info ( buffer + len ,
( size_t ) LOW_LEVEL_STAT_ALLOC ) ;
/* make usage of the state buffer as deterministic as poss.
exceptional conditions could cause overrun - and this is flagged as
a kernel error .
*/
/* formats and settings */
len + = sprintf ( buffer + len , " \t \t === Formats & settings === \n " ) ;
len + = sprintf ( buffer + len , " Parameter %20s%20s \n " , " soft " , " hard " ) ;
len + = sprintf ( buffer + len , " Format :%20s%20s \n " ,
get_afmt_string ( dmasound . soft . format ) ,
get_afmt_string ( dmasound . hard . format ) ) ;
len + = sprintf ( buffer + len , " Samp Rate:%14d s/sec%14d s/sec \n " ,
dmasound . soft . speed , dmasound . hard . speed ) ;
len + = sprintf ( buffer + len , " Channels :%20s%20s \n " ,
dmasound . soft . stereo ? " stereo " : " mono " ,
dmasound . hard . stereo ? " stereo " : " mono " ) ;
/* sound queue status */
len + = sprintf ( buffer + len , " \t \t === Sound Queue status === \n " ) ;
len + = sprintf ( buffer + len , " Allocated:%8s%6s \n " , " Buffers " , " Size " ) ;
len + = sprintf ( buffer + len , " %9s:%8d%6d \n " ,
" write " , write_sq . numBufs , write_sq . bufSize ) ;
# ifdef HAS_RECORD
if ( dmasound . mach . record )
len + = sprintf ( buffer + len , " %9s:%8d%6d \n " ,
" read " , read_sq . numBufs , read_sq . bufSize ) ;
# endif
len + = sprintf ( buffer + len ,
" Current : MaxFrg FragSiz MaxAct Frnt Rear "
" Cnt RrSize A B S L xruns \n " ) ;
len + = sprintf ( buffer + len , " %9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d \n " ,
" write " , write_sq . max_count , write_sq . block_size ,
write_sq . max_active , write_sq . front , write_sq . rear ,
write_sq . count , write_sq . rear_size , write_sq . active ,
write_sq . busy , write_sq . syncing , write_sq . locked , write_sq . xruns ) ;
# ifdef HAS_RECORD
if ( dmasound . mach . record )
len + = sprintf ( buffer + len , " %9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d \n " ,
" read " , read_sq . max_count , read_sq . block_size ,
read_sq . max_active , read_sq . front , read_sq . rear ,
read_sq . count , read_sq . rear_size , read_sq . active ,
read_sq . busy , read_sq . syncing , read_sq . locked , read_sq . xruns ) ;
# endif
# ifdef DEBUG_DMASOUND
printk ( " dmasound: stat buffer used %d bytes \n " , len ) ;
# endif
if ( len > = STAT_BUFF_LEN )
printk ( KERN_ERR " dmasound_core: stat buffer overflowed! \n " ) ;
state . len = len ;
return 0 ;
}
static int state_release ( struct inode * inode , struct file * file )
{
lock_kernel ( ) ;
state . busy = 0 ;
module_put ( dmasound . mach . owner ) ;
unlock_kernel ( ) ;
return 0 ;
}
static ssize_t state_read ( struct file * file , char __user * buf , size_t count ,
loff_t * ppos )
{
int n = state . len - state . ptr ;
if ( n > count )
n = count ;
if ( n < = 0 )
return 0 ;
if ( copy_to_user ( buf , & state . buf [ state . ptr ] , n ) )
return - EFAULT ;
state . ptr + = n ;
return n ;
}
2007-02-12 11:55:37 +03:00
static const struct file_operations state_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = state_read ,
. open = state_open ,
. release = state_release ,
} ;
static int state_init ( void )
{
# ifndef MODULE
int state_unit ;
# endif
state_unit = register_sound_special ( & state_fops , SND_DEV_STATUS ) ;
if ( state_unit < 0 )
return state_unit ;
state . busy = 0 ;
return 0 ;
}
/*
* Config & Setup
*
* This function is called by _one_ chipset - specific driver
*/
int dmasound_init ( void )
{
int res ;
# ifdef MODULE
if ( irq_installed )
return - EBUSY ;
# endif
/* Set up sound queue, /dev/audio and /dev/dsp. */
/* Set default settings. */
if ( ( res = sq_init ( ) ) < 0 )
return res ;
/* Set up /dev/sndstat. */
if ( ( res = state_init ( ) ) < 0 )
return res ;
/* Set up /dev/mixer. */
mixer_init ( ) ;
if ( ! dmasound . mach . irqinit ( ) ) {
printk ( KERN_ERR " DMA sound driver: Interrupt initialization failed \n " ) ;
return - ENODEV ;
}
# ifdef MODULE
irq_installed = 1 ;
# endif
printk ( KERN_INFO " %s DMA sound driver rev %03d installed \n " ,
dmasound . mach . name , ( DMASOUND_CORE_REVISION < < 4 ) +
( ( dmasound . mach . version > > 8 ) & 0x0f ) ) ;
printk ( KERN_INFO
" Core driver edition %02d.%02d : %s driver edition %02d.%02d \n " ,
DMASOUND_CORE_REVISION , DMASOUND_CORE_EDITION , dmasound . mach . name2 ,
( dmasound . mach . version > > 8 ) , ( dmasound . mach . version & 0xff ) ) ;
printk ( KERN_INFO " Write will use %4d fragments of %7d bytes as default \n " ,
numWriteBufs , writeBufSize ) ;
# ifdef HAS_RECORD
if ( dmasound . mach . record )
printk ( KERN_INFO
" Read will use %4d fragments of %7d bytes as default \n " ,
numReadBufs , readBufSize ) ;
# endif
return 0 ;
}
# ifdef MODULE
void dmasound_deinit ( void )
{
if ( irq_installed ) {
sound_silence ( ) ;
dmasound . mach . irqcleanup ( ) ;
irq_installed = 0 ;
}
write_sq_release_buffers ( ) ;
read_sq_release_buffers ( ) ;
if ( mixer_unit > = 0 )
unregister_sound_mixer ( mixer_unit ) ;
if ( state_unit > = 0 )
unregister_sound_special ( state_unit ) ;
if ( sq_unit > = 0 )
unregister_sound_dsp ( sq_unit ) ;
}
# else /* !MODULE */
static int dmasound_setup ( char * str )
{
int ints [ 6 ] , size ;
str = get_options ( str , ARRAY_SIZE ( ints ) , ints ) ;
/* check the bootstrap parameter for "dmasound=" */
/* FIXME: other than in the most naive of cases there is no sense in these
* buffers being other than powers of two . This is not checked yet .
*/
switch ( ints [ 0 ] ) {
# ifdef HAS_RECORD
case 5 :
if ( ( ints [ 5 ] < 0 ) | | ( ints [ 5 ] > MAX_CATCH_RADIUS ) )
printk ( " dmasound_setup: invalid catch radius, using default = %d \n " , catchRadius ) ;
else
catchRadius = ints [ 5 ] ;
/* fall through */
case 4 :
if ( ints [ 4 ] < MIN_BUFFERS )
printk ( " dmasound_setup: invalid number of read buffers, using default = %d \n " ,
numReadBufs ) ;
else
numReadBufs = ints [ 4 ] ;
/* fall through */
case 3 :
if ( ( size = ints [ 3 ] ) < 256 ) /* check for small buffer specs */
size < < = 10 ;
if ( size < MIN_BUFSIZE | | size > MAX_BUFSIZE )
printk ( " dmasound_setup: invalid read buffer size, using default = %d \n " , readBufSize ) ;
else
readBufSize = size ;
/* fall through */
# else
case 3 :
if ( ( ints [ 3 ] < 0 ) | | ( ints [ 3 ] > MAX_CATCH_RADIUS ) )
printk ( " dmasound_setup: invalid catch radius, using default = %d \n " , catchRadius ) ;
else
catchRadius = ints [ 3 ] ;
/* fall through */
# endif
case 2 :
if ( ints [ 1 ] < MIN_BUFFERS )
printk ( " dmasound_setup: invalid number of buffers, using default = %d \n " , numWriteBufs ) ;
else
numWriteBufs = ints [ 1 ] ;
/* fall through */
case 1 :
if ( ( size = ints [ 2 ] ) < 256 ) /* check for small buffer specs */
size < < = 10 ;
if ( size < MIN_BUFSIZE | | size > MAX_BUFSIZE )
printk ( " dmasound_setup: invalid write buffer size, using default = %d \n " , writeBufSize ) ;
else
writeBufSize = size ;
case 0 :
break ;
default :
printk ( " dmasound_setup: invalid number of arguments \n " ) ;
return 0 ;
}
return 1 ;
}
__setup ( " dmasound= " , dmasound_setup ) ;
# endif /* !MODULE */
/*
* Conversion tables
*/
# ifdef HAS_8BIT_TABLES
/* 8 bit mu-law */
char dmasound_ulaw2dma8 [ ] = {
- 126 , - 122 , - 118 , - 114 , - 110 , - 106 , - 102 , - 98 ,
- 94 , - 90 , - 86 , - 82 , - 78 , - 74 , - 70 , - 66 ,
- 63 , - 61 , - 59 , - 57 , - 55 , - 53 , - 51 , - 49 ,
- 47 , - 45 , - 43 , - 41 , - 39 , - 37 , - 35 , - 33 ,
- 31 , - 30 , - 29 , - 28 , - 27 , - 26 , - 25 , - 24 ,
- 23 , - 22 , - 21 , - 20 , - 19 , - 18 , - 17 , - 16 ,
- 16 , - 15 , - 15 , - 14 , - 14 , - 13 , - 13 , - 12 ,
- 12 , - 11 , - 11 , - 10 , - 10 , - 9 , - 9 , - 8 ,
- 8 , - 8 , - 7 , - 7 , - 7 , - 7 , - 6 , - 6 ,
- 6 , - 6 , - 5 , - 5 , - 5 , - 5 , - 4 , - 4 ,
- 4 , - 4 , - 4 , - 4 , - 3 , - 3 , - 3 , - 3 ,
- 3 , - 3 , - 3 , - 3 , - 2 , - 2 , - 2 , - 2 ,
- 2 , - 2 , - 2 , - 2 , - 2 , - 2 , - 2 , - 2 ,
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , 0 ,
125 , 121 , 117 , 113 , 109 , 105 , 101 , 97 ,
93 , 89 , 85 , 81 , 77 , 73 , 69 , 65 ,
62 , 60 , 58 , 56 , 54 , 52 , 50 , 48 ,
46 , 44 , 42 , 40 , 38 , 36 , 34 , 32 ,
30 , 29 , 28 , 27 , 26 , 25 , 24 , 23 ,
22 , 21 , 20 , 19 , 18 , 17 , 16 , 15 ,
15 , 14 , 14 , 13 , 13 , 12 , 12 , 11 ,
11 , 10 , 10 , 9 , 9 , 8 , 8 , 7 ,
7 , 7 , 6 , 6 , 6 , 6 , 5 , 5 ,
5 , 5 , 4 , 4 , 4 , 4 , 3 , 3 ,
3 , 3 , 3 , 3 , 2 , 2 , 2 , 2 ,
2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 ,
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
} ;
/* 8 bit A-law */
char dmasound_alaw2dma8 [ ] = {
- 22 , - 21 , - 24 , - 23 , - 18 , - 17 , - 20 , - 19 ,
- 30 , - 29 , - 32 , - 31 , - 26 , - 25 , - 28 , - 27 ,
- 11 , - 11 , - 12 , - 12 , - 9 , - 9 , - 10 , - 10 ,
- 15 , - 15 , - 16 , - 16 , - 13 , - 13 , - 14 , - 14 ,
- 86 , - 82 , - 94 , - 90 , - 70 , - 66 , - 78 , - 74 ,
- 118 , - 114 , - 126 , - 122 , - 102 , - 98 , - 110 , - 106 ,
- 43 , - 41 , - 47 , - 45 , - 35 , - 33 , - 39 , - 37 ,
- 59 , - 57 , - 63 , - 61 , - 51 , - 49 , - 55 , - 53 ,
- 2 , - 2 , - 2 , - 2 , - 2 , - 2 , - 2 , - 2 ,
- 2 , - 2 , - 2 , - 2 , - 2 , - 2 , - 2 , - 2 ,
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
- 6 , - 6 , - 6 , - 6 , - 5 , - 5 , - 5 , - 5 ,
- 8 , - 8 , - 8 , - 8 , - 7 , - 7 , - 7 , - 7 ,
- 3 , - 3 , - 3 , - 3 , - 3 , - 3 , - 3 , - 3 ,
- 4 , - 4 , - 4 , - 4 , - 4 , - 4 , - 4 , - 4 ,
21 , 20 , 23 , 22 , 17 , 16 , 19 , 18 ,
29 , 28 , 31 , 30 , 25 , 24 , 27 , 26 ,
10 , 10 , 11 , 11 , 8 , 8 , 9 , 9 ,
14 , 14 , 15 , 15 , 12 , 12 , 13 , 13 ,
86 , 82 , 94 , 90 , 70 , 66 , 78 , 74 ,
118 , 114 , 126 , 122 , 102 , 98 , 110 , 106 ,
43 , 41 , 47 , 45 , 35 , 33 , 39 , 37 ,
59 , 57 , 63 , 61 , 51 , 49 , 55 , 53 ,
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
5 , 5 , 5 , 5 , 4 , 4 , 4 , 4 ,
7 , 7 , 7 , 7 , 6 , 6 , 6 , 6 ,
2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ,
3 , 3 , 3 , 3 , 3 , 3 , 3 , 3
} ;
# endif /* HAS_8BIT_TABLES */
/*
* Visible symbols for modules
*/
EXPORT_SYMBOL ( dmasound ) ;
EXPORT_SYMBOL ( dmasound_init ) ;
# ifdef MODULE
EXPORT_SYMBOL ( dmasound_deinit ) ;
# endif
EXPORT_SYMBOL ( dmasound_write_sq ) ;
# ifdef HAS_RECORD
EXPORT_SYMBOL ( dmasound_read_sq ) ;
# endif
EXPORT_SYMBOL ( dmasound_catchRadius ) ;
# ifdef HAS_8BIT_TABLES
EXPORT_SYMBOL ( dmasound_ulaw2dma8 ) ;
EXPORT_SYMBOL ( dmasound_alaw2dma8 ) ;
# endif
EXPORT_SYMBOL ( get_afmt_string ) ;