aboutsummaryrefslogtreecommitdiff
path: root/sound/oss/dmasound/dmasound_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/dmasound/dmasound_core.c')
-rw-r--r--sound/oss/dmasound/dmasound_core.c1829
1 files changed, 1829 insertions, 0 deletions
diff --git a/sound/oss/dmasound/dmasound_core.c b/sound/oss/dmasound/dmasound_core.c
new file mode 100644
index 00000000000..c9302a1e515
--- /dev/null
+++ b/sound/oss/dmasound/dmasound_core.c
@@ -0,0 +1,1829 @@
+/*
+ * 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/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;
+MODULE_PARM(dmasound_catchRadius, "i");
+
+static unsigned int numWriteBufs = DEFAULT_N_BUFFERS;
+MODULE_PARM(numWriteBufs, "i");
+static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */
+MODULE_PARM(writeBufSize, "i");
+
+#ifdef HAS_RECORD
+static unsigned int numReadBufs = DEFAULT_N_BUFFERS;
+MODULE_PARM(numReadBufs, "i");
+static unsigned int readBufSize = DEFAULT_BUFF_SIZE; /* in bytes */
+MODULE_PARM(readBufSize, "i");
+#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;
+}
+
+static struct file_operations mixer_fops =
+{
+ .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)
+ rc = sq_fsync(file, file->f_dentry);
+
+ 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) {