 fca660beb8
			
		
	
	
	fca660beb8
	
	
	
		
			
			it is not (and it has never been) an ->fsync() instance... Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
		
			
				
	
	
		
			1589 lines
		
	
	
	
		
			44 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1589 lines
		
	
	
	
		
			44 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *  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 12517Hz instead of 6258Hz.
 | |
|  *
 | |
|  *	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 < 1kb 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/mutex.h>
 | |
| 
 | |
| #include <asm/uaccess.h>
 | |
| 
 | |
| #include "dmasound.h"
 | |
| 
 | |
| #define DMASOUND_CORE_REVISION 1
 | |
| #define DMASOUND_CORE_EDITION 6
 | |
| 
 | |
|     /*
 | |
|      *  Declarations
 | |
|      */
 | |
| 
 | |
| static DEFINE_MUTEX(dmasound_core_mutex);
 | |
| int dmasound_catchRadius = 0;
 | |
| module_param(dmasound_catchRadius, int, 0);
 | |
| 
 | |
| static unsigned int numWriteBufs = DEFAULT_N_BUFFERS;
 | |
| module_param(numWriteBufs, int, 0);
 | |
| static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ;	/* in bytes */
 | |
| module_param(writeBufSize, int, 0);
 | |
| 
 | |
| 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 */
 | |
| 
 | |
| /* control over who can modify resources shared between play/record */
 | |
| static fmode_t shared_resource_owner;
 | |
| static int shared_resources_initialised;
 | |
| 
 | |
|     /*
 | |
|      *  Mid level stuff
 | |
|      */
 | |
| 
 | |
| struct sound_settings dmasound = {
 | |
| 	.lock = __SPIN_LOCK_UNLOCKED(dmasound.lock)
 | |
| };
 | |
| 
 | |
| 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)
 | |
| {
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	if (!try_module_get(dmasound.mach.owner)) {
 | |
| 		mutex_unlock(&dmasound_core_mutex);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 	mixer.busy = 1;
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mixer_release(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	mixer.busy = 0;
 | |
| 	module_put(dmasound.mach.owner);
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mixer_ioctl(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;
 | |
| }
 | |
| 
 | |
| static long mixer_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	ret = mixer_ioctl(file, cmd, arg);
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct file_operations mixer_fops =
 | |
| {
 | |
| 	.owner		= THIS_MODULE,
 | |
| 	.llseek		= no_llseek,
 | |
| 	.unlocked_ioctl	= mixer_unlocked_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) ;
 | |
| 
 | |
| 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;
 | |
| 	}
 | |
| 	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.non_blocking)
 | |
| 				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);
 | |
| 	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;
 | |
| 
 | |
| }
 | |
| 
 | |
| 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,
 | |
| 			      fmode_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, fmode_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->non_blocking = file->f_flags & O_NONBLOCK;
 | |
| 	}
 | |
| 	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 )
 | |
| 
 | |
| static int sq_open(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	int rc;
 | |
| 
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	if (!try_module_get(dmasound.mach.owner)) {
 | |
| 		mutex_unlock(&dmasound_core_mutex);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	rc = write_sq_open(file); /* checks the f_mode */
 | |
| 	if (rc)
 | |
| 		goto out;
 | |
| 	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;
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 	return 0;
 | |
|  out:
 | |
| 	module_put(dmasound.mach.owner);
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 	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 ;
 | |
| }
 | |
| 
 | |
| static void sq_reset(void)
 | |
| {
 | |
| 	sq_reset_output() ;
 | |
| 	/* 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(void)
 | |
| {
 | |
| 	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;
 | |
| 
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 
 | |
| 	if (file->f_mode & FMODE_WRITE) {
 | |
| 		if (write_sq.busy)
 | |
| 			rc = sq_fsync();
 | |
| 
 | |
| 		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() */
 | |
| 
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 
 | |
| 	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(fmode_t md)
 | |
| {
 | |
| 	if (shared_resource_owner)
 | |
| 		return (shared_resource_owner & md) != 0;
 | |
| 	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)
 | |
| {
 | |
| 	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 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 ;
 | |
| 		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 immediately.
 | |
| 		*/
 | |
| 		result = 0 ;
 | |
| 		if (file->f_mode & FMODE_WRITE) {
 | |
| 			result = sq_fsync();
 | |
| 			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;
 | |
| 		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(file, cmd, arg);
 | |
| 	}
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static long sq_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	ret = sq_ioctl(file, cmd, arg);
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct file_operations sq_fops =
 | |
| {
 | |
| 	.owner		= THIS_MODULE,
 | |
| 	.llseek		= no_llseek,
 | |
| 	.write		= sq_write,
 | |
| 	.poll		= sq_poll,
 | |
| 	.unlocked_ioctl	= sq_unlocked_ioctl,
 | |
| 	.open		= sq_open,
 | |
| 	.release	= sq_release,
 | |
| };
 | |
| 
 | |
| static int sq_init(void)
 | |
| {
 | |
| 	const struct file_operations *fops = &sq_fops;
 | |
| #ifndef MODULE
 | |
| 	int sq_unit;
 | |
| #endif
 | |
| 
 | |
| 	sq_unit = register_sound_dsp(fops, -1);
 | |
| 	if (sq_unit < 0) {
 | |
| 		printk(KERN_ERR "dmasound_core: couldn't register fops\n") ;
 | |
| 		return sq_unit ;
 | |
| 	}
 | |
| 
 | |
| 	write_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.
 | |
| */
 | |
| 
 | |
| #define STAT_BUFF_LEN 768
 | |
| 
 | |
| /* 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 */
 | |
| 
 | |
| static 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;
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	ret = -EBUSY;
 | |
| 	if (state.busy)
 | |
| 		goto out;
 | |
| 
 | |
| 	ret = -ENODEV;
 | |
| 	if (!try_module_get(dmasound.mach.owner))
 | |
| 		goto out;
 | |
| 
 | |
| 	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) ;
 | |
| 	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 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;
 | |
| 	ret = 0;
 | |
| out:
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int state_release(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	mutex_lock(&dmasound_core_mutex);
 | |
| 	state.busy = 0;
 | |
| 	module_put(dmasound.mach.owner);
 | |
| 	mutex_unlock(&dmasound_core_mutex);
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| static const struct file_operations state_fops = {
 | |
| 	.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) ;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #ifdef MODULE
 | |
| 
 | |
| void dmasound_deinit(void)
 | |
| {
 | |
| 	if (irq_installed) {
 | |
| 		sound_silence();
 | |
| 		dmasound.mach.irqcleanup();
 | |
| 		irq_installed = 0;
 | |
| 	}
 | |
| 
 | |
| 	write_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]) {
 | |
| 	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 */
 | |
| 	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);
 | |
| EXPORT_SYMBOL(dmasound_catchRadius);
 | |
| #ifdef HAS_8BIT_TABLES
 | |
| EXPORT_SYMBOL(dmasound_ulaw2dma8);
 | |
| EXPORT_SYMBOL(dmasound_alaw2dma8);
 | |
| #endif
 |