/* $Id: audio.c,v 1.62 2001/10/08 22:19:50 davem Exp $ * drivers/sbus/audio/audio.c * * Copyright 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) * Copyright 1997,1998,1999 Derrick J. Brashear (shadow@dementia.org) * Copyright 1997 Brent Baccala (baccala@freesoft.org) * * Mixer code adapted from code contributed by and * Copyright 1998 Michael Mraka (michael@fi.muni.cz) * and with fixes from Michael Shuey (shuey@ecn.purdue.edu) * The mixer code cheats; Sparc hardware doesn't generally allow independent * line control, and this fakes it badly. * * SNDCTL_DSP_SETFMT based on code contributed by * Ion Badulescu (ionut@moisil.cs.columbia.edu) * * This is the audio midlayer that sits between the VFS character * devices and the low-level audio hardware device drivers. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef __AUDIO_DEBUG #define __AUDIO_ERROR #undef __AUDIO_TRACE #undef __AUDIO_OSSDEBUG #ifdef __AUDIO_DEBUG #define dprintk(x) printk x #else #define dprintk(x) #endif #ifdef __AUDIO_OSSDEBUG #define oprintk(x) printk x #else #define oprintk(x) #endif #ifdef __AUDIO_ERROR #define eprintk(x) printk x #else #define eprintk(x) #endif #ifdef __AUDIO_TRACE #define tprintk(x) printk x #else #define tprintk(x) #endif static short lis_get_elist_ent( strevent_t *list, pid_t pid ); static int lis_add_to_elist( strevent_t **list, pid_t pid, short events ); static int lis_del_from_elist( strevent_t **list, pid_t pid, short events ); static void lis_free_elist( strevent_t **list); static void kill_procs( struct strevent *elist, int sig, short e); static struct sparcaudio_driver *drivers[SPARCAUDIO_MAX_DEVICES]; static devfs_handle_t devfs_handle; void sparcaudio_output_done(struct sparcaudio_driver * drv, int status) { /* If !status, just restart current output. * If status & 1, a buffer is finished; make it available again. * If status & 2, a buffer was claimed for DMA and is still in use. * * The playing_count for non-DMA hardware should never be non-zero. * Value of status for non-DMA hardware should always be 1. */ if (status & 1) { if (drv->playing_count) { drv->playing_count--; } else { drv->output_count--; drv->output_size -= drv->output_sizes[drv->output_front]; if (drv->output_notify[drv->output_front] == 1) { drv->output_eof++; drv->output_notify[drv->output_front] = 0; kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); } drv->output_front = (drv->output_front + 1) % drv->num_output_buffers; } } if (status & 2) { drv->output_count--; drv->playing_count++; drv->output_size -= drv->output_sizes[drv->output_front]; if (drv->output_notify[drv->output_front] == 1) { drv->output_eof++; drv->output_notify[drv->output_front] = 0; kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); } drv->output_front = (drv->output_front + 1) % drv->num_output_buffers; } /* If we've played everything go inactive. */ if ((drv->output_count < 1) && (drv->playing_count < 1)) drv->output_active = 0; /* If we got back a buffer, see if anyone wants to write to it */ if ((status & 1) || ((drv->output_count + drv->playing_count) < drv->num_output_buffers)) { wake_up_interruptible(&drv->output_write_wait); } /* If the output queue is empty, shut down the driver. */ if ((drv->output_count < 1) && (drv->playing_count < 1)) { kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); /* Stop the lowlevel driver from outputing. */ /* drv->ops->stop_output(drv); Should not be necessary -- DJB 5/25/98 */ drv->output_active = 0; /* Wake up any waiting writers or syncers and return. */ wake_up_interruptible(&drv->output_write_wait); wake_up_interruptible(&drv->output_drain_wait); return; } /* Start next block of output if we have it */ if (drv->output_count > 0) { drv->ops->start_output(drv, drv->output_buffers[drv->output_front], drv->output_sizes[drv->output_front]); drv->output_active = 1; } else { drv->output_active = 0; } } void sparcaudio_input_done(struct sparcaudio_driver * drv, int status) { /* Deal with the weird case here */ if (drv->duplex == 2) { if (drv->input_count < drv->num_input_buffers) drv->input_count++; drv->ops->start_input(drv, drv->input_buffers[drv->input_front], drv->input_buffer_size); wake_up_interruptible(&drv->input_read_wait); return; } /* If status % 2, they filled a buffer for us. * If status & 2, they took a buffer from us. */ if ((status % 2) == 1) { drv->input_count++; drv->recording_count--; drv->input_size+=drv->input_buffer_size; } if (status > 1) { drv->recording_count++; drv->input_front = (drv->input_front + 1) % drv->num_input_buffers; } dprintk(("f%d r%d c%d u%d\n", drv->input_front, drv->input_rear, drv->input_count, drv->recording_count)); /* If the input queue is full, shutdown the driver. */ if ((drv->input_count + drv->recording_count) == drv->num_input_buffers) { kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); /* Stop the lowlevel driver from inputing. */ drv->ops->stop_input(drv); drv->input_active = 0; } else { /* Otherwise, give the driver the next buffer. */ drv->ops->start_input(drv, drv->input_buffers[drv->input_front], drv->input_buffer_size); } /* Wake up any tasks that are waiting. */ wake_up_interruptible(&drv->input_read_wait); } /* * VFS layer interface */ static unsigned int sparcaudio_poll(struct file *file, poll_table * wait) { unsigned int mask = 0; struct inode *inode = file->f_dentry->d_inode; struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >> SPARCAUDIO_DEVICE_SHIFT)]; poll_wait(file, &drv->input_read_wait, wait); poll_wait(file, &drv->output_write_wait, wait); if (((!file->f_flags & O_NONBLOCK) && drv->input_count) || (drv->input_size > drv->buffer_size)) { mask |= POLLIN | POLLRDNORM; } if ((drv->output_count + drv->playing_count) < (drv->num_output_buffers)) { mask |= POLLOUT | POLLWRNORM; } return mask; } static ssize_t sparcaudio_read(struct file * file, char *buf, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >> SPARCAUDIO_DEVICE_SHIFT)]; int bytes_to_copy, bytes_read = 0, err; if (! (file->f_mode & FMODE_READ)) return -EINVAL; if ((file->f_flags & O_NONBLOCK) && (drv->input_size < count)) return -EAGAIN; while (count > 0) { if (drv->input_count == 0) { /* This *should* never happen. */ if (file->f_flags & O_NONBLOCK) { printk("Warning: audio input leak!\n"); return -EAGAIN; } interruptible_sleep_on(&drv->input_read_wait); if (signal_pending(current)) return -EINTR; } bytes_to_copy = drv->input_buffer_size - drv->input_offset; if (bytes_to_copy > count) bytes_to_copy = count; err = verify_area(VERIFY_WRITE, buf, bytes_to_copy); if (err) return err; copy_to_user(buf, drv->input_buffers[drv->input_rear]+drv->input_offset, bytes_to_copy); drv->input_offset += bytes_to_copy; drv->input_size -= bytes_to_copy; buf += bytes_to_copy; count -= bytes_to_copy; bytes_read += bytes_to_copy; if (drv->input_offset >= drv->input_buffer_size) { drv->input_rear = (drv->input_rear + 1) % drv->num_input_buffers; drv->input_count--; drv->input_offset = 0; } /* If we're in "loop audio" mode, try waking up the other side * in case they're waiting for us to eat a block. */ if (drv->duplex == 2) wake_up_interruptible(&drv->output_write_wait); } return bytes_read; } static void sparcaudio_sync_output(struct sparcaudio_driver * drv) { unsigned long flags; /* If the low-level driver is not active, activate it. */ save_and_cli(flags); if ((!drv->output_active) && (drv->output_count > 0)) { drv->ops->start_output(drv, drv->output_buffers[drv->output_front], drv->output_sizes[drv->output_front]); drv->output_active = 1; } restore_flags(flags); } static ssize_t sparcaudio_write(struct file * file, const char *buf, size_t count, loff_t *ppos) { struct inode *inode = file->f_dentry->d_inode; struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >> SPARCAUDIO_DEVICE_SHIFT)]; int bytes_written = 0, bytes_to_copy, err; if (! (file->f_mode & FMODE_WRITE)) return -EINVAL; /* A signal they want notification when this is processed. Too bad * sys_write doesn't tell us unless you patch it, in 2.0 kernels. */ if (count == 0) { #ifndef notdef drv->output_eof++; kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); #else /* Nice code, but the world isn't ready yet... */ drv->output_notify[drv->output_rear] = 1; #endif } /* Loop until all output is written to device. */ while (count > 0) { /* Check to make sure that an output buffer is available. */ if (drv->num_output_buffers == (drv->output_count+drv->playing_count)) { /* We need buffers, so... */ sparcaudio_sync_output(drv); if (file->f_flags & O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&drv->output_write_wait); if (signal_pending(current)) return bytes_written > 0 ? bytes_written : -EINTR; } /* No buffers were freed. Go back to sleep */ if (drv->num_output_buffers == (drv->output_count+drv->playing_count)) continue; /* Deal with the weird case of a reader in the write area by trying to * let them keep ahead of us... Go to sleep until they start servicing. */ if ((drv->duplex == 2) && (drv->flags & SDF_OPEN_READ) && (drv->output_rear == drv->input_rear) && (drv->input_count > 0)) { if (file->f_flags & O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&drv->output_write_wait); if (signal_pending(current)) return bytes_written > 0 ? bytes_written : -EINTR; } /* Determine how much we can copy in this iteration. */ bytes_to_copy = count; if (bytes_to_copy > drv->output_buffer_size - drv->output_offset) bytes_to_copy = drv->output_buffer_size - drv->output_offset; err = verify_area(VERIFY_READ, buf, bytes_to_copy); if (err) return err; copy_from_user(drv->output_buffers[drv->output_rear]+drv->output_offset, buf, bytes_to_copy); /* Update the queue pointers. */ buf += bytes_to_copy; count -= bytes_to_copy; bytes_written += bytes_to_copy; /* A block can get orphaned in a flush and not cleaned up. */ if (drv->output_offset) drv->output_sizes[drv->output_rear] += bytes_to_copy; else drv->output_sizes[drv->output_rear] = bytes_to_copy; drv->output_notify[drv->output_rear] = 0; if (drv->output_sizes[drv->output_rear] == drv->output_buffer_size) { drv->output_rear = (drv->output_rear + 1) % drv->num_output_buffers; drv->output_count++; drv->output_offset = 0; } else { drv->output_offset += bytes_to_copy; } drv->output_size += bytes_to_copy; } sparcaudio_sync_output(drv); /* Return the number of bytes written to the caller. */ return bytes_written; } /* Add these in as new devices are supported. Belongs in audioio.h, actually */ #define MONO_DEVICES (SOUND_MASK_SPEAKER | SOUND_MASK_MIC) static int sparcaudio_mixer_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned int *arg) { struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >> SPARCAUDIO_DEVICE_SHIFT)]; unsigned long i = 0, j = 0, l = 0, m = 0; unsigned int k = 0; if (_SIOC_DIR(cmd) & _SIOC_WRITE) drv->mixer_modify_counter++; if(cmd == SOUND_MIXER_INFO) { audio_device_t tmp; mixer_info info; int retval = -EINVAL; if(drv->ops->sunaudio_getdev) { drv->ops->sunaudio_getdev(drv, &tmp); memset(&info, 0, sizeof(info)); strncpy(info.id, tmp.name, sizeof(info.id)); strncpy(info.name, "Sparc Audio", sizeof(info.name)); info.modify_counter = drv->mixer_modify_counter; if(copy_to_user((char *)arg, &info, sizeof(info))) retval = -EFAULT; else retval = 0; } return retval; } switch (cmd) { case SOUND_MIXER_WRITE_RECLEV: if (get_user(k, (int *)arg)) return -EFAULT; iretry: oprintk(("setting input volume (0x%x)", k)); if (drv->ops->get_input_channels) j = drv->ops->get_input_channels(drv); if (drv->ops->get_input_volume) l = drv->ops->get_input_volume(drv); if (drv->ops->get_input_balance) m = drv->ops->get_input_balance(drv); i = OSS_TO_GAIN(k); j = OSS_TO_BAL(k); oprintk((" for stereo to do %d (bal %d):", i, j)); if (drv->ops->set_input_volume) drv->ops->set_input_volume(drv, i); if (drv->ops->set_input_balance) drv->ops->set_input_balance(drv, j); case SOUND_MIXER_READ_RECLEV: if (drv->ops->get_input_volume) i = drv->ops->get_input_volume(drv); if (drv->ops->get_input_balance) j = drv->ops->get_input_balance(drv); oprintk((" got (0x%x)\n", BAL_TO_OSS(i,j))); i = BAL_TO_OSS(i,j); /* Try to be reasonable about volume changes */ if ((cmd == SOUND_MIXER_WRITE_RECLEV) && (i != k) && (i == BAL_TO_OSS(l,m))) { k += (OSS_LEFT(k) > OSS_LEFT(i)) ? 256 : -256; k += (OSS_RIGHT(k) > OSS_RIGHT(i)) ? 1 : -1; oprintk((" try 0x%x\n", k)); goto iretry; } return put_user(i, (int *)arg); case SOUND_MIXER_WRITE_VOLUME: if (get_user(k, (int *)arg)) return -EFAULT; if (drv->ops->get_output_muted && drv->ops->set_output_muted) { i = drv->ops->get_output_muted(drv); if ((k == 0) || ((i == 0) && (OSS_LEFT(k) < 100))) drv->ops->set_output_muted(drv, 1); else drv->ops->set_output_muted(drv, 0); } case SOUND_MIXER_READ_VOLUME: if (drv->ops->get_output_muted) i = drv->ops->get_output_muted(drv); k = 0x6464 * (1 - i); return put_user(k, (int *)arg); case SOUND_MIXER_WRITE_PCM: if (get_user(k, (int *)arg)) return -EFAULT; oretry: oprintk(("setting output volume (0x%x)\n", k)); if (drv->ops->get_output_channels) j = drv->ops->get_output_channels(drv); if (drv->ops->get_output_volume) l = drv->ops->get_output_volume(drv); if (drv->ops->get_output_balance) m = drv->ops->get_output_balance(drv); oprintk((" started as (0x%x)\n", BAL_TO_OSS(l,m))); i = OSS_TO_GAIN(k); j = OSS_TO_BAL(k); oprintk((" for stereo to %d (bal %d)\n", i, j)); if (drv->ops->set_output_volume) drv->ops->set_output_volume(drv, i); if (drv->ops->set_output_balance) drv->ops->set_output_balance(drv, j); case SOUND_MIXER_READ_PCM: if (drv->ops->get_output_volume) i = drv->ops->get_output_volume(drv); if (drv->ops->get_output_balance) j = drv->ops->get_output_balance(drv); oprintk((" got 0x%x\n", BAL_TO_OSS(i,j))); i = BAL_TO_OSS(i,j); /* Try to be reasonable about volume changes */ if ((cmd == SOUND_MIXER_WRITE_PCM) && (i != k) && (i == BAL_TO_OSS(l,m))) { k += (OSS_LEFT(k) > OSS_LEFT(i)) ? 256 : -256; k += (OSS_RIGHT(k) > OSS_RIGHT(i)) ? 1 : -1; oprintk((" try 0x%x\n", k)); goto oretry; } return put_user(i, (int *)arg); case SOUND_MIXER_READ_SPEAKER: k = OSS_PORT_AUDIO(drv, AUDIO_SPEAKER); return put_user(k, (int *)arg); case SOUND_MIXER_READ_MIC: k = OSS_IPORT_AUDIO(drv, AUDIO_MICROPHONE); return put_user(k, (int *)arg); case SOUND_MIXER_READ_CD: k = OSS_IPORT_AUDIO(drv, AUDIO_CD); return put_user(k, (int *)arg); case SOUND_MIXER_READ_LINE: k = OSS_IPORT_AUDIO(drv, AUDIO_LINE_IN); return put_user(k, (int *)arg); case SOUND_MIXER_READ_LINE1: k = OSS_PORT_AUDIO(drv, AUDIO_HEADPHONE); return put_user(k, (int *)arg); case SOUND_MIXER_READ_LINE2: k = OSS_PORT_AUDIO(drv, AUDIO_LINE_OUT); return put_user(k, (int *)arg); case SOUND_MIXER_WRITE_MIC: case SOUND_MIXER_WRITE_CD: case SOUND_MIXER_WRITE_LINE: case SOUND_MIXER_WRITE_LINE1: case SOUND_MIXER_WRITE_LINE2: case SOUND_MIXER_WRITE_SPEAKER: if (get_user(k, (int *)arg)) return -EFAULT; OSS_TWIDDLE_IPORT(drv, cmd, SOUND_MIXER_WRITE_LINE, AUDIO_LINE_IN, k); OSS_TWIDDLE_IPORT(drv, cmd, SOUND_MIXER_WRITE_MIC, AUDIO_MICROPHONE, k); OSS_TWIDDLE_IPORT(drv, cmd, SOUND_MIXER_WRITE_CD, AUDIO_CD, k); OSS_TWIDDLE_PORT(drv, cmd, SOUND_MIXER_WRITE_SPEAKER, AUDIO_SPEAKER, k); OSS_TWIDDLE_PORT(drv, cmd, SOUND_MIXER_WRITE_LINE1, AUDIO_HEADPHONE, k); OSS_TWIDDLE_PORT(drv, cmd, SOUND_MIXER_WRITE_LINE2, AUDIO_LINE_OUT, k); return put_user(k, (int *)arg); case SOUND_MIXER_READ_RECSRC: if (drv->ops->get_input_port) i = drv->ops->get_input_port(drv); /* only one should ever be selected */ if (i & AUDIO_CD) j = SOUND_MASK_CD; if (i & AUDIO_LINE_IN) j = SOUND_MASK_LINE; if (i & AUDIO_MICROPHONE) j = SOUND_MASK_MIC; return put_user(j, (int *)arg); case SOUND_MIXER_WRITE_RECSRC: if (!drv->ops->set_input_port) return -EINVAL; if (get_user(k, (int *)arg)) return -EFAULT; /* only one should ever be selected */ if (k & SOUND_MASK_CD) j = AUDIO_CD; if (k & SOUND_MASK_LINE) j = AUDIO_LINE_IN; if (k & SOUND_MASK_MIC) j = AUDIO_MICROPHONE; oprintk(("setting inport to %d\n", j)); i = drv->ops->set_input_port(drv, j); return put_user(i, (int *)arg); case SOUND_MIXER_READ_RECMASK: if (drv->ops->get_input_ports) i = drv->ops->get_input_ports(drv); /* what do we support? */ if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC; if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE; if (i & AUDIO_CD) j |= SOUND_MASK_CD; return put_user(j, (int *)arg); case SOUND_MIXER_READ_CAPS: /* mixer capabilities */ i = SOUND_CAP_EXCL_INPUT; return put_user(i, (int *)arg); case SOUND_MIXER_READ_DEVMASK: /* all supported devices */ if (drv->ops->get_input_ports) i = drv->ops->get_input_ports(drv); /* what do we support? */ if (i & AUDIO_MICROPHONE) j |= SOUND_MASK_MIC; if (i & AUDIO_LINE_IN) j |= SOUND_MASK_LINE; if (i & AUDIO_CD) j |= SOUND_MASK_CD; if (drv->ops->get_output_ports) i = drv->ops->get_output_ports(drv); if (i & AUDIO_SPEAKER) j |= SOUND_MASK_SPEAKER; if (i & AUDIO_HEADPHONE) j |= SOUND_MASK_LINE1; if (i & AUDIO_LINE_OUT) j |= SOUND_MASK_LINE2; j |= SOUND_MASK_VOLUME; case SOUND_MIXER_READ_STEREODEVS: /* what supports stereo */ j |= SOUND_MASK_PCM|SOUND_MASK_RECLEV; if (cmd == SOUND_MIXER_READ_STEREODEVS) j &= ~(MONO_DEVICES); return put_user(j, (int *)arg); default: return -EINVAL; }; } /* AUDIO_SETINFO uses these to set values if possible. */ static __inline__ int __sparcaudio_if_set_do(struct sparcaudio_driver *drv, int (*set_function)(struct sparcaudio_driver *, int), int (*get_function)(struct sparcaudio_driver *), unsigned int value) { if (set_function && Modify(value)) return (int) set_function(drv, value); else if (get_function) return (int) get_function(drv); else return 0; } static __inline__ int __sparcaudio_if_setc_do(struct sparcaudio_driver *drv, int (*set_function)(struct sparcaudio_driver *, int), int (*get_function)(struct sparcaudio_driver *), unsigned char value) { if (set_function && Modifyc(value)) return (char) set_function(drv, (int)value); else if (get_function) return (char) get_function(drv); else return 0; } /* I_FLUSH, I_{G,S}ETSIG, I_NREAD provided for SunOS compatibility * * I must admit I'm quite ashamed of the state of the ioctl handling, * but I do have several optimizations which I'm planning. -- DJB */ static int sparcaudio_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { int retval = 0, i, j, k; int minor = MINOR(inode->i_rdev); struct audio_info ainfo; audio_buf_info binfo; count_info cinfo; struct sparcaudio_driver *drv = drivers[(minor >> SPARCAUDIO_DEVICE_SHIFT)]; switch (minor & 0xf) { case SPARCAUDIO_MIXER_MINOR: return sparcaudio_mixer_ioctl(inode, file, cmd, (unsigned int *)arg); case SPARCAUDIO_DSP16_MINOR: case SPARCAUDIO_DSP_MINOR: case SPARCAUDIO_AUDIO_MINOR: case SPARCAUDIO_AUDIOCTL_MINOR: /* According to the OSS prog int, you can mixer ioctl /dev/dsp */ if (_IOC_TYPE(cmd) == 'M') return sparcaudio_mixer_ioctl(inode, file, cmd, (unsigned int *)arg); switch (cmd) { case I_GETSIG: case I_GETSIG_SOLARIS: j = (int) lis_get_elist_ent(drv->sd_siglist,current->pid); put_user(j, (int *)arg); retval = drv->input_count; break; case I_SETSIG: case I_SETSIG_SOLARIS: if ((minor & 0xf) == SPARCAUDIO_AUDIOCTL_MINOR) { if (!arg) { if (lis_del_from_elist(&(drv->sd_siglist), current->pid,S_ALL)) { retval = -EINVAL; } else if (!drv->sd_siglist) { drv->sd_sigflags=0; } } else if (lis_add_to_elist(&(drv->sd_siglist), current->pid, (short)arg)) { retval = -EAGAIN; } else { ((drv->sd_sigflags) |= (arg)); } } break; case I_NREAD: case I_NREAD_SOLARIS: /* According to the Solaris man page, this copies out * the size of the first streams buffer and returns * the number of streams messages on the read queue as * as its retval. (streamio(7I)) This should work. */ j = (drv->input_count > 0) ? drv->input_buffer_size : 0; put_user(j, (int *)arg); retval = drv->input_count; break; /* A poor substitute until we do true resizable buffers. */ case SNDCTL_DSP_GETISPACE: binfo.fragstotal = drv->num_input_buffers; binfo.fragments = drv->num_input_buffers - (drv->input_count + drv->recording_count); binfo.fragsize = drv->input_buffer_size; binfo.bytes = binfo.fragments*binfo.fragsize; retval = verify_area(VERIFY_WRITE, (int *)arg, sizeof(binfo)); if (retval) break; copy_to_user(&((char *)arg)[0], (char *)&binfo, sizeof(binfo)); break; case SNDCTL_DSP_GETOSPACE: binfo.fragstotal = drv->num_output_buffers; binfo.fragments = drv->num_output_buffers - (drv->output_count + drv->playing_count + (drv->output_offset ? 1 : 0)); binfo.fragsize = drv->output_buffer_size; binfo.bytes = binfo.fragments*binfo.fragsize + (drv->output_buffer_size - drv->output_offset); retval = verify_area(VERIFY_WRITE, (int *)arg, sizeof(binfo)); if (retval) break; copy_to_user(&((char *)arg)[0], (char *)&binfo, sizeof(binfo)); break; case SNDCTL_DSP_GETIPTR: case SNDCTL_DSP_GETOPTR: /* int bytes (number of bytes read/written since last) * int blocks (number of frags read/wrote since last call) * int ptr (current position of dma in buffer) */ retval = 0; cinfo.bytes = 0; cinfo.ptr = 0; cinfo.blocks = 0; cinfo.bytes += cinfo.ptr; retval = verify_area(VERIFY_WRITE, (int *)arg, sizeof(cinfo)); if (retval) break; copy_to_user(&((char *)arg)[0], (char *)&cinfo, sizeof(cinfo)); break; case SNDCTL_DSP_SETFRAGMENT: /* XXX Small hack to get ESD/Enlightenment to work. --DaveM */ retval = 0; break; case SNDCTL_DSP_SUBDIVIDE: /* I don't understand what I need to do yet. */ retval = -EINVAL; break; case SNDCTL_DSP_SETTRIGGER: /* This may not be 100% correct */ if ((arg & PCM_ENABLE_INPUT) && drv->ops->get_input_pause && drv->ops->set_input_pause) { if (drv->ops->get_input_pause(drv)) drv->ops->set_input_pause(drv, 0); } else { if (!drv->ops->get_input_pause(drv)) drv->ops->set_input_pause(drv, 1); } if ((arg & PCM_ENABLE_OUTPUT) && drv->ops->get_output_pause && drv->ops->set_output_pause) { if (drv->ops->get_output_pause(drv)) drv->ops->set_output_pause(drv, 0); } else { if (!drv->ops->get_output_pause(drv)) drv->ops->set_output_pause(drv, 1); } break; case SNDCTL_DSP_GETTRIGGER: j = 0; if (drv->ops->get_input_pause) { if (drv->ops->get_input_pause(drv)) j = PCM_ENABLE_INPUT; } if (drv->ops->get_output_pause) { if (drv->ops->get_output_pause(drv)) j |= PCM_ENABLE_OUTPUT; } put_user(j, (int *)arg); break; case SNDCTL_DSP_GETBLKSIZE: j = drv->input_buffer_size; put_user(j, (int *)arg); break; case SNDCTL_DSP_SPEED: if ((!drv->ops->set_output_rate) && (!drv->ops->set_input_rate)) { retval = -EINVAL; break; } get_user(i, (int *)arg) tprintk(("setting speed to %d\n", i)); drv->ops->set_input_rate(drv, i); drv->ops->set_output_rate(drv, i); j = drv->ops->get_output_rate(drv); put_user(j, (int *)arg); break; case SNDCTL_DSP_GETCAPS: /* All Sparc audio hardware is full duplex. * 4231 supports DMA pointer reading, 7930 is byte at a time. * Pause functionality emulates trigger */ j = DSP_CAP_DUPLEX | DSP_CAP_TRIGGER | DSP_CAP_REALTIME; put_user(j, (int *)arg); break; case SNDCTL_DSP_GETFMTS: if (drv->ops->get_formats) { j = drv->ops->get_formats(drv); put_user(j, (int *)arg); } else { retval = -EINVAL; } break; case SNDCTL_DSP_SETFMT: /* need to decode into encoding, precision */ get_user(i, (int *)arg); /* handle special case here */ if (i == AFMT_QUERY) { j = drv->ops->get_output_encoding(drv); k = drv->ops->get_output_precision(drv); if (j == AUDIO_ENCODING_DVI) { i = AFMT_IMA_ADPCM; } else if (k == 8) { switch (j) { case AUDIO_ENCODING_ULAW: i = AFMT_MU_LAW; break; case AUDIO_ENCODING_ALAW: i = AFMT_A_LAW; break; case AUDIO_ENCODING_LINEAR8: i = AFMT_U8; break; }; } else if (k == 16) { switch (j) { case AUDIO_ENCODING_LINEAR: i = AFMT_S16_BE; break; case AUDIO_ENCODING_LINEARLE: i = AFMT_S16_LE; break; }; } put_user(i, (int *)arg); break; } /* Without these there's no point in trying */ if (!drv->ops->set_input_precision || !drv->ops->set_input_encoding || !drv->ops->set_output_precision || !drv->ops->set_output_encoding) { eprintk(("missing set routines: failed\n")); retval = -EINVAL; break; } if (drv->ops->get_formats) { if (!(drv->ops->get_formats(drv) & i)) { dprintk(("format not supported\n")); return -EINVAL; } } switch (i) { case AFMT_S16_LE: ainfo.record.precision = ainfo.play.precision = 16; ainfo.record.encoding = ainfo.play.encoding = AUDIO_ENCODING_LINEARLE; break; case AFMT_S16_BE: ainfo.record.precision = ainfo.play.precision = 16; ainfo.record.encoding = ainfo.play.encoding = AUDIO_ENCODING_LINEAR; break; case AFMT_MU_LAW: ainfo.record.precision = ainfo.play.precision = 8; ainfo.record.encoding = ainfo.play.encoding = AUDIO_ENCODING_ULAW; break; case AFMT_A_LAW: ainfo.record.precision = ainfo.play.precision = 8; ainfo.record.encoding = ainfo.play.encoding = AUDIO_ENCODING_ALAW; break; case AFMT_U8: ainfo.record.precision = ainfo.play.precision = 8; ainfo.record.encoding = ainfo.play.encoding = AUDIO_ENCODING_LINEAR8; break; }; tprintk(("setting fmt to enc %d pr %d\n", ainfo.play.encoding, ainfo.play.precision)); if ((drv->ops->set_input_precision(drv, ainfo.record.precision) < 0) || (drv->ops->set_output_precision(drv, ainfo.play.precision) < 0) || (drv->ops->set_input_encoding(drv, ainfo.record.encoding) < 0) || (drv->ops->set_output_encoding(drv, ainfo.play.encoding) < 0)) { dprintk(("setting format: failed\n")); return -EINVAL; } put_user(i, (int *)arg); break; case SNDCTL_DSP_CHANNELS: if ((!drv->ops->set_output_channels) && (!drv->ops->set_input_channels)) { retval = -EINVAL; break; } get_user(i, (int *)arg); drv->ops->set_input_channels(drv, i); drv->ops->set_output_channels(drv, i); i = drv->ops->get_output_channels(drv); put_user(i, (int *)arg); break; case SNDCTL_DSP_STEREO: if ((!drv->ops->set_output_channels) && (!drv->ops->set_input_channels)) { retval = -EINVAL; break; } get_user(i, (int *)arg); drv->ops->set_input_channels(drv, (i + 1)); drv->ops->set_output_channels(drv, (i + 1)); i = ((drv->ops->get_output_channels(drv)) - 1); put_user(i, (int *)arg); break; case SNDCTL_DSP_POST: case SNDCTL_DSP_SYNC: case AUDIO_DRAIN: /* Deal with weirdness so we can fill buffers */ if (drv->output_offset) { drv->output_offset = 0; drv->output_rear = (drv->output_rear + 1) % drv->num_output_buffers; drv->output_count++; } if (drv->output_count > 0) { sparcaudio_sync_output(drv); /* Only pause for DRAIN/SYNC, not POST */ if (cmd != SNDCTL_DSP_POST) { interruptible_sleep_on(&drv->output_drain_wait); retval = (signal_pending(current)) ? -EINTR : 0; } } break; case I_FLUSH: case I_FLUSH_SOLARIS: if (((unsigned int)arg == FLUSHW) || ((unsigned int)arg == FLUSHRW)) { if (file->f_mode & FMODE_WRITE) { sparcaudio_sync_output(drv); if (drv->output_active) { wake_up_interruptible(&drv->output_write_wait); drv->ops->stop_output(drv); } drv->output_offset = 0; drv->output_active = 0; drv->output_front = 0; drv->output_rear = 0; drv->output_count = 0; drv->output_size = 0; drv->playing_count = 0; drv->output_eof = 0; } } if (((unsigned int)arg == FLUSHR) || ((unsigned int)arg == FLUSHRW)) { if (drv->input_active && (file->f_mode & FMODE_READ)) { wake_up_interruptible(&drv->input_read_wait); drv->ops->stop_input(drv); drv->input_active = 0; drv->input_front = 0; drv->input_rear = 0; drv->input_count = 0; drv->input_size = 0; drv->input_offset = 0; drv->recording_count = 0; } if ((file->f_mode & FMODE_READ) && (drv->flags & SDF_OPEN_READ)) { if (drv->duplex == 2) drv->input_count = drv->output_count; drv->ops->start_input(drv, drv->input_buffers[drv->input_front], drv->input_buffer_size); drv->input_active = 1; } } if (((unsigned int)arg == FLUSHW) || ((unsigned int)arg == FLUSHRW)) { if ((file->f_mode & FMODE_WRITE) && !(drv->flags & SDF_OPEN_WRITE)) { kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); sparcaudio_sync_output(drv); } } break; case SNDCTL_DSP_RESET: case AUDIO_FLUSH: if (drv->output_active && (file->f_mode & FMODE_WRITE)) { wake_up_interruptible(&drv->output_write_wait); drv->ops->stop_output(drv); drv->output_active = 0; drv->output_front = 0; drv->output_rear = 0; drv->output_count = 0; drv->output_size = 0; drv->playing_count = 0; drv->output_offset = 0; drv->output_eof = 0; } if (drv->input_active && (file->f_mode & FMODE_READ)) { wake_up_interruptible(&drv->input_read_wait); drv->ops->stop_input(drv); drv->input_active = 0; drv->input_front = 0; drv->input_rear = 0; drv->input_count = 0; drv->input_size = 0; drv->input_offset = 0; drv->recording_count = 0; } if ((file->f_mode & FMODE_READ) && !(drv->flags & SDF_OPEN_READ)) { drv->ops->start_input(drv, drv->input_buffers[drv->input_front], drv->input_buffer_size); drv->input_active = 1; } if ((file->f_mode & FMODE_WRITE) && !(drv->flags & SDF_OPEN_WRITE)) { sparcaudio_sync_output(drv); } break; case AUDIO_GETDEV: if (drv->ops->sunaudio_getdev) { audio_device_t tmp; retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(audio_device_t)); if (!retval) drv->ops->sunaudio_getdev(drv, &tmp); copy_to_user((audio_device_t *)arg, &tmp, sizeof(tmp)); } else { retval = -EINVAL; } break; case AUDIO_GETDEV_SUNOS: if (drv->ops->sunaudio_getdev_sunos) { int tmp = drv->ops->sunaudio_getdev_sunos(drv); retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(int)); if (!retval) copy_to_user((int *)arg, &tmp, sizeof(tmp)); } else { retval = -EINVAL; } break; case AUDIO_GETINFO: AUDIO_INITINFO(&ainfo); if (drv->ops->get_input_rate) ainfo.record.sample_rate = drv->ops->get_input_rate(drv); else ainfo.record.sample_rate = (8000); if (drv->ops->get_input_channels) ainfo.record.channels = drv->ops->get_input_channels(drv); else ainfo.record.channels = (1); if (drv->ops->get_input_precision) ainfo.record.precision = drv->ops->get_input_precision(drv); else ainfo.record.precision = (8); if (drv->ops->get_input_encoding) ainfo.record.encoding = drv->ops->get_input_encoding(drv); else ainfo.record.encoding = (AUDIO_ENCODING_ULAW); if (drv->ops->get_input_volume) ainfo.record.gain = drv->ops->get_input_volume(drv); else ainfo.record.gain = (0); if (drv->ops->get_input_port) ainfo.record.port = drv->ops->get_input_port(drv); else ainfo.record.port = (0); if (drv->ops->get_input_ports) ainfo.record.avail_ports = drv->ops->get_input_ports(drv); else ainfo.record.avail_ports = (0); /* To make e.g. vat happy, we let them think they control this */ ainfo.record.buffer_size = drv->buffer_size; if (drv->ops->get_input_samples) ainfo.record.samples = drv->ops->get_input_samples(drv); else ainfo.record.samples = 0; /* This is undefined in the record context in Solaris */ ainfo.record.eof = 0; if (drv->ops->get_input_pause) ainfo.record.pause = drv->ops->get_input_pause(drv); else ainfo.record.pause = 0; if (drv->ops->get_input_error) ainfo.record.error = (unsigned char) drv->ops->get_input_error(drv); else ainfo.record.error = 0; ainfo.record.waiting = 0; if (drv->ops->get_input_balance) ainfo.record.balance = (unsigned char) drv->ops->get_input_balance(drv); else ainfo.record.balance = (unsigned char)(AUDIO_MID_BALANCE); ainfo.record.minordev = 4 + (minor << SPARCAUDIO_DEVICE_SHIFT); ainfo.record.open = (drv->flags & SDF_OPEN_READ); ainfo.record.active = 0; if (drv->ops->get_output_rate) ainfo.play.sample_rate = drv->ops->get_output_rate(drv); else ainfo.play.sample_rate = (8000); if (drv->ops->get_output_channels) ainfo.play.channels = drv->ops->get_output_channels(drv); else ainfo.play.channels = (1); if (drv->ops->get_output_precision) ainfo.play.precision = drv->ops->get_output_precision(drv); else ainfo.play.precision = (8); if (drv->ops->get_output_encoding) ainfo.play.encoding = drv->ops->get_output_encoding(drv); else ainfo.play.encoding = (AUDIO_ENCODING_ULAW); if (drv->ops->get_output_volume) ainfo.play.gain = drv->ops->get_output_volume(drv); else ainfo.play.gain = (0); if (drv->ops->get_output_port) ainfo.play.port = drv->ops->get_output_port(drv); else ainfo.play.port = (0); if (drv->ops->get_output_ports) ainfo.play.avail_ports = drv->ops->get_output_ports(drv); else ainfo.play.avail_ports = (0); /* This is not defined in the play context in Solaris */ ainfo.play.buffer_size = 0; if (drv->ops->get_output_samples) ainfo.play.samples = drv->ops->get_output_samples(drv); else ainfo.play.samples = 0; ainfo.play.eof = drv->output_eof; if (drv->ops->get_output_pause) ainfo.play.pause = drv->ops->get_output_pause(drv); else ainfo.play.pause = 0; if (drv->ops->get_output_error) ainfo.play.error = (unsigned char)drv->ops->get_output_error(drv); else ainfo.play.error = 0; ainfo.play.waiting = waitqueue_active(&drv->open_wait); if (drv->ops->get_output_balance) ainfo.play.balance = (unsigned char)drv->ops->get_output_balance(drv); else ainfo.play.balance = (unsigned char)(AUDIO_MID_BALANCE); ainfo.play.minordev = 4 + (minor << SPARCAUDIO_DEVICE_SHIFT); ainfo.play.open = (drv->flags & SDF_OPEN_WRITE); ainfo.play.active = drv->output_active; if (drv->ops->get_monitor_volume) ainfo.monitor_gain = drv->ops->get_monitor_volume(drv); else ainfo.monitor_gain = (0); if (drv->ops->get_output_muted) ainfo.output_muted = (unsigned char)drv->ops->get_output_muted(drv); else ainfo.output_muted = (unsigned char)(0); retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct audio_info)); if (retval < 0) break; copy_to_user((struct audio_info *)arg, &ainfo, sizeof(ainfo)); break; case AUDIO_SETINFO: { audio_info_t curinfo, newinfo; if (verify_area(VERIFY_READ, (audio_info_t *)arg, sizeof(audio_info_t))) { dprintk(("verify_area failed\n")); return -EINVAL; } copy_from_user(&ainfo, (audio_info_t *)arg, sizeof(audio_info_t)); /* Without these there's no point in trying */ if (!drv->ops->get_input_precision || !drv->ops->get_input_channels || !drv->ops->get_input_rate || !drv->ops->get_input_encoding || !drv->ops->get_output_precision || !drv->ops->get_output_channels || !drv->ops->get_output_rate || !drv->ops->get_output_encoding) { eprintk(("missing get routines: failed\n")); retval = -EINVAL; break; } /* Do bounds checking for things which always apply. * Follow with enforcement of basic tenets of certain * encodings. Everything over and above generic is * enforced by the driver, which can assume that * Martian cases are taken care of here. */ if (Modify(ainfo.play.gain) && ((ainfo.play.gain > AUDIO_MAX_GAIN) || (ainfo.play.gain < AUDIO_MIN_GAIN))) { /* Need to differentiate this from e.g. the above error */ eprintk(("play gain bounds: failed %d\n", ainfo.play.gain)); retval = -EINVAL; break; } if (Modify(ainfo.record.gain) && ((ainfo.record.gain > AUDIO_MAX_GAIN) || (ainfo.record.gain < AUDIO_MIN_GAIN))) { eprintk(("rec gain bounds: failed %d\n", ainfo.record.gain)); retval = -EINVAL; break; } if (Modify(ainfo.monitor_gain) && ((ainfo.monitor_gain > AUDIO_MAX_GAIN) || (ainfo.monitor_gain < AUDIO_MIN_GAIN))) { eprintk(("monitor gain bounds: failed\n")); retval = -EINVAL; break; } /* Don't need to check less than zero on these */ if (Modifyc(ainfo.play.balance) && (ainfo.play.balance > AUDIO_RIGHT_BALANCE)) { eprintk(("play balance bounds: %d failed\n", (int)ainfo.play.balance)); retval = -EINVAL; break; } if (Modifyc(ainfo.record.balance) && (ainfo.record.balance > AUDIO_RIGHT_BALANCE)) { eprintk(("rec balance bounds: failed\n")); retval = -EINVAL; break; } /* If any of these changed, record them all, then make * changes atomically. If something fails, back it all out. */ if (Modify(ainfo.record.precision) || Modify(ainfo.record.sample_rate) || Modify(ainfo.record.channels) || Modify(ainfo.record.encoding) || Modify(ainfo.play.precision) || Modify(ainfo.play.sample_rate) || Modify(ainfo.play.channels) || Modify(ainfo.play.encoding)) { /* If they're trying to change something we * have no routine for, they lose. */ if ((!drv->ops->set_input_encoding && Modify(ainfo.record.encoding)) || (!drv->ops->set_input_rate && Modify(ainfo.record.sample_rate)) || (!drv->ops->set_input_precision && Modify(ainfo.record.precision)) || (!drv->ops->set_input_channels && Modify(ainfo.record.channels))) { eprintk(("rec set no routines: failed\n")); retval = -EINVAL; break; } curinfo.record.encoding = drv->ops->get_input_encoding(drv); curinfo.record.sample_rate = drv->ops->get_input_rate(drv); curinfo.record.precision = drv->ops->get_input_precision(drv); curinfo.record.channels = drv->ops->get_input_channels(drv); newinfo.record.encoding = Modify(ainfo.record.encoding) ? ainfo.record.encoding : curinfo.record.encoding; newinfo.record.sample_rate = Modify(ainfo.record.sample_rate) ? ainfo.record.sample_rate : curinfo.record.sample_rate; newinfo.record.precision = Modify(ainfo.record.precision) ? ainfo.record.precision : curinfo.record.precision; newinfo.record.channels = Modify(ainfo.record.channels) ? ainfo.record.channels : curinfo.record.channels; switch (newinfo.record.encoding) { case AUDIO_ENCODING_ALAW: case AUDIO_ENCODING_ULAW: if (newinfo.record.precision != 8) { eprintk(("rec law precision bounds: " "failed\n")); retval = -EINVAL; break; } if (newinfo.record.channels != 1) { eprintk(("rec law channel bounds: " "failed\n")); retval = -EINVAL; break; } break; case AUDIO_ENCODING_LINEAR: case AUDIO_ENCODING_LINEARLE: if (newinfo.record.precision != 16) { eprintk(("rec lin precision bounds: " "failed\n")); retval = -EINVAL; break; } if (newinfo.record.channels != 1 && newinfo.record.channels != 2) { eprintk(("rec lin channel bounds: " "failed\n")); retval = -EINVAL; break; } break; case AUDIO_ENCODING_LINEAR8: if (newinfo.record.precision != 8) { eprintk(("rec lin8 precision bounds: " "failed\n")); retval = -EINVAL; break; } if (newinfo.record.channels != 1 && newinfo.record.channels != 2) { eprintk(("rec lin8 channel bounds: " "failed\n")); retval = -EINVAL; break; } }; if (retval < 0) break; /* If they're trying to change something we * have no routine for, they lose. */ if ((!drv->ops->set_output_encoding && Modify(ainfo.play.encoding)) || (!drv->ops->set_output_rate && Modify(ainfo.play.sample_rate)) || (!drv->ops->set_output_precision && Modify(ainfo.play.precision)) || (!drv->ops->set_output_channels && Modify(ainfo.play.channels))) { eprintk(("play set no routine: failed\n")); retval = -EINVAL; break; } curinfo.play.encoding = drv->ops->get_output_encoding(drv); curinfo.play.sample_rate = drv->ops->get_output_rate(drv); curinfo.play.precision = drv->ops->get_output_precision(drv); curinfo.play.channels = drv->ops->get_output_channels(drv); newinfo.play.encoding = Modify(ainfo.play.encoding) ? ainfo.play.encoding : curinfo.play.encoding; newinfo.play.sample_rate = Modify(ainfo.play.sample_rate) ? ainfo.play.sample_rate : curinfo.play.sample_rate; newinfo.play.precision = Modify(ainfo.play.precision) ? ainfo.play.precision : curinfo.play.precision; newinfo.play.channels = Modify(ainfo.play.channels) ? ainfo.play.channels : curinfo.play.channels; switch (newinfo.play.encoding) { case AUDIO_ENCODING_ALAW: case AUDIO_ENCODING_ULAW: if (newinfo.play.precision != 8) { eprintk(("play law precision bounds: " "failed\n")); retval = -EINVAL; break; } if (newinfo.play.channels != 1) { eprintk(("play law channel bounds: " "failed\n")); retval = -EINVAL; break; } break; case AUDIO_ENCODING_LINEAR: case AUDIO_ENCODING_LINEARLE: if (newinfo.play.precision != 16) { eprintk(("play lin precision bounds: " "failed\n")); retval = -EINVAL; break; } if (newinfo.play.channels != 1 && newinfo.play.channels != 2) { eprintk(("play lin channel bounds: " "failed\n")); retval = -EINVAL; break; } break; case AUDIO_ENCODING_LINEAR8: if (newinfo.play.precision != 8) { eprintk(("play lin8 precision bounds: " "failed\n")); retval = -EINVAL; break; } if (newinfo.play.channels != 1 && newinfo.play.channels != 2) { eprintk(("play lin8 channel bounds: " "failed\n")); retval = -EINVAL; break; } }; if (retval < 0) break; /* If we got this far, we're at least sane with * respect to generics. Try the changes. */ if ((drv->ops->set_input_channels && (drv->ops->set_input_channels(drv, newinfo.record.channels) < 0)) || (drv->ops->set_output_channels && (drv->ops->set_output_channels(drv, newinfo.play.channels) < 0)) || (drv->ops->set_input_rate && (drv->ops->set_input_rate(drv, newinfo.record.sample_rate) < 0)) || (drv->ops->set_output_rate && (drv->ops->set_output_rate(drv, newinfo.play.sample_rate) < 0)) || (drv->ops->set_input_precision && (drv->ops->set_input_precision(drv, newinfo.record.precision) < 0)) || (drv->ops->set_output_precision && (drv->ops->set_output_precision(drv, newinfo.play.precision) < 0)) || (drv->ops->set_input_encoding && (drv->ops->set_input_encoding(drv, newinfo.record.encoding) < 0)) || (drv->ops->set_output_encoding && (drv->ops->set_output_encoding(drv, newinfo.play.encoding) < 0))) { dprintk(("setting format: failed\n")); /* Pray we can set it all back. If not, uh... */ if (drv->ops->set_input_channels) drv->ops->set_input_channels(drv, curinfo.record.channels); if (drv->ops->set_output_channels) drv->ops->set_output_channels(drv, curinfo.play.channels); if (drv->ops->set_input_rate) drv->ops->set_input_rate(drv, curinfo.record.sample_rate); if (drv->ops->set_output_rate) drv->ops->set_output_rate(drv, curinfo.play.sample_rate); if (drv->ops->set_input_precision) drv->ops->set_input_precision(drv, curinfo.record.precision); if (drv->ops->set_output_precision) drv->ops->set_output_precision(drv, curinfo.play.precision); if (drv->ops->set_input_encoding) drv->ops->set_input_encoding(drv, curinfo.record.encoding); if (drv->ops->set_output_encoding) drv->ops->set_output_encoding(drv, curinfo.play.encoding); retval = -EINVAL; break; } } if (retval < 0) break; newinfo.record.balance = __sparcaudio_if_setc_do(drv, drv->ops->set_input_balance, drv->ops->get_input_balance, ainfo.record.balance); newinfo.play.balance = __sparcaudio_if_setc_do(drv, drv->ops->set_output_balance, drv->ops->get_output_balance, ainfo.play.balance); newinfo.record.error = __sparcaudio_if_setc_do(drv, drv->ops->set_input_error, drv->ops->get_input_error, ainfo.record.error); newinfo.play.error = __sparcaudio_if_setc_do(drv, drv->ops->set_output_error, drv->ops->get_output_error, ainfo.play.error); newinfo.output_muted = __sparcaudio_if_setc_do(drv, drv->ops->set_output_muted, drv->ops->get_output_muted, ainfo.output_muted); newinfo.record.gain = __sparcaudio_if_set_do(drv, drv->ops->set_input_volume, drv->ops->get_input_volume, ainfo.record.gain); newinfo.play.gain = __sparcaudio_if_set_do(drv, drv->ops->set_output_volume, drv->ops->get_output_volume, ainfo.play.gain); newinfo.record.port = __sparcaudio_if_set_do(drv, drv->ops->set_input_port, drv->ops->get_input_port, ainfo.record.port); newinfo.play.port = __sparcaudio_if_set_do(drv, drv->ops->set_output_port, drv->ops->get_output_port, ainfo.play.port); newinfo.record.samples = __sparcaudio_if_set_do(drv, drv->ops->set_input_samples, drv->ops->get_input_samples, ainfo.record.samples); newinfo.play.samples = __sparcaudio_if_set_do(drv, drv->ops->set_output_samples, drv->ops->get_output_samples, ainfo.play.samples); newinfo.monitor_gain = __sparcaudio_if_set_do(drv, drv->ops->set_monitor_volume, drv->ops->get_monitor_volume, ainfo.monitor_gain); if (Modify(ainfo.record.buffer_size)) { /* Should sanity check this */ newinfo.record.buffer_size = ainfo.record.buffer_size; drv->buffer_size = ainfo.record.buffer_size; } else { newinfo.record.buffer_size = drv->buffer_size; } if (Modify(ainfo.play.eof)) { ainfo.play.eof = newinfo.play.eof; newinfo.play.eof = drv->output_eof; drv->output_eof = ainfo.play.eof; } else { newinfo.play.eof = drv->output_eof; } if (drv->flags & SDF_OPEN_READ) { newinfo.record.pause = __sparcaudio_if_setc_do(drv, drv->ops->set_input_pause, drv->ops->get_input_pause, ainfo.record.pause); } else if (drv->ops->get_input_pause) { newinfo.record.pause = drv->ops->get_input_pause(drv); } else { newinfo.record.pause = 0; } if (drv->flags & SDF_OPEN_WRITE) { newinfo.play.pause = __sparcaudio_if_setc_do(drv, drv->ops->set_output_pause, drv->ops->get_output_pause, ainfo.play.pause); } else if (drv->ops->get_output_pause) { newinfo.play.pause = drv->ops->get_output_pause(drv); } else { newinfo.play.pause = 0; } retval = verify_area(VERIFY_WRITE, (void *)arg, sizeof(struct audio_info)); /* Even if we fail, if we made changes let's try notification */ if (!retval) copy_to_user((struct audio_info *)arg, &newinfo, sizeof(newinfo)); #ifdef REAL_AUDIO_SIGNALS kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); #endif break; } default: if (drv->ops->ioctl) retval = drv->ops->ioctl(inode,file,cmd,arg,drv); else retval = -EINVAL; }; break; case SPARCAUDIO_STATUS_MINOR: eprintk(("status minor not yet implemented\n")); retval = -EINVAL; default: eprintk(("unknown minor device number\n")); retval = -EINVAL; } return retval; } static struct file_operations sparcaudioctl_fops = { owner: THIS_MODULE, poll: sparcaudio_poll, ioctl: sparcaudio_ioctl, }; static int sparcaudio_open(struct inode * inode, struct file * file) { int minor = MINOR(inode->i_rdev); struct sparcaudio_driver *drv = drivers[(minor >> SPARCAUDIO_DEVICE_SHIFT)]; int err; /* A low-level audio driver must exist. */ if (!drv) return -ENODEV; #ifdef S_ZERO_WR /* This is how 2.0 ended up dealing with 0 len writes */ inode->i_flags |= S_ZERO_WR; #endif switch (minor & 0xf) { case SPARCAUDIO_AUDIOCTL_MINOR: file->f_op = &sparcaudioctl_fops; break; case SPARCAUDIO_DSP16_MINOR: case SPARCAUDIO_DSP_MINOR: case SPARCAUDIO_AUDIO_MINOR: /* If the driver is busy, then wait to get through. */ retry_open: if (file->f_mode & FMODE_READ && drv->flags & SDF_OPEN_READ) { if (file->f_flags & O_NONBLOCK) return -EBUSY; /* If something is now waiting, signal control device */ kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); interruptible_sleep_on(&drv->open_wait); if (signal_pending(current)) return -EINTR; goto retry_open; } if (file->f_mode & FMODE_WRITE && drv->flags & SDF_OPEN_WRITE) { if (file->f_flags & O_NONBLOCK) return -EBUSY; /* If something is now waiting, signal control device */ kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); interruptible_sleep_on(&drv->open_wait); if (signal_pending(current)) return -EINTR; goto retry_open; } /* Allow the low-level driver to initialize itself. */ if (drv->ops->open) { err = drv->ops->open(inode,file,drv); if (err < 0) return err; } /* Mark the driver as locked for read and/or write. */ if (file->f_mode & FMODE_READ) { drv->input_offset = 0; drv->input_front = 0; drv->input_rear = 0; drv->input_count = 0; drv->input_size = 0; drv->recording_count = 0; /* Clear pause */ if (drv->ops->set_input_pause) drv->ops->set_input_pause(drv, 0); drv->ops->start_input(drv, drv->input_buffers[drv->input_front], drv->input_buffer_size); drv->input_active = 1; drv->flags |= SDF_OPEN_READ; } if (file->f_mode & FMODE_WRITE) { drv->output_offset = 0; drv->output_eof = 0; drv->playing_count = 0; drv->output_size = 0; drv->output_front = 0; drv->output_rear = 0; drv->output_count = 0; drv->output_active = 0; /* Clear pause */ if (drv->ops->set_output_pause) drv->ops->set_output_pause(drv, 0); drv->flags |= SDF_OPEN_WRITE; } break; case SPARCAUDIO_MIXER_MINOR: file->f_op = &sparcaudioctl_fops; break; default: return -ENXIO; }; /* From the dbri driver: * SunOS 5.5.1 audio(7I) man page says: * "Upon the initial open() of the audio device, the driver * will reset the data format of the device to the default * state of 8-bit, 8KHz, mono u-law data." * * Of course, we only do this for /dev/audio, and assume * OSS semantics on /dev/dsp */ if ((minor & 0xf) == SPARCAUDIO_AUDIO_MINOR) { if (file->f_mode & FMODE_WRITE) { if (drv->ops->set_output_channels) drv->ops->set_output_channels(drv, 1); if (drv->ops->set_output_encoding) drv->ops->set_output_encoding(drv, AUDIO_ENCODING_ULAW); if (drv->ops->set_output_rate) drv->ops->set_output_rate(drv, 8000); } if (file->f_mode & FMODE_READ) { if (drv->ops->set_input_channels) drv->ops->set_input_channels(drv, 1); if (drv->ops->set_input_encoding) drv->ops->set_input_encoding(drv, AUDIO_ENCODING_ULAW); if (drv->ops->set_input_rate) drv->ops->set_input_rate(drv, 8000); } } /* Success! */ return 0; } static int sparcaudio_release(struct inode * inode, struct file * file) { struct sparcaudio_driver *drv = drivers[(MINOR(inode->i_rdev) >> SPARCAUDIO_DEVICE_SHIFT)]; lock_kernel(); if (file->f_mode & FMODE_READ) { /* Stop input */ drv->ops->stop_input(drv); drv->input_active = 0; } if (file->f_mode & FMODE_WRITE) { /* Anything in the queue? */ if (drv->output_offset) { drv->output_offset = 0; drv->output_rear = (drv->output_rear + 1) % drv->num_output_buffers; drv->output_count++; } sparcaudio_sync_output(drv); /* Wait for any output still in the queue to be played. */ if ((drv->output_count > 0) || (drv->playing_count > 0)) interruptible_sleep_on(&drv->output_drain_wait); /* Force any output to be stopped. */ drv->ops->stop_output(drv); drv->output_active = 0; drv->playing_count = 0; drv->output_eof = 0; } /* Let the low-level driver do any release processing. */ if (drv->ops->release) drv->ops->release(inode,file,drv); if (file->f_mode & FMODE_READ) drv->flags &= ~(SDF_OPEN_READ); if (file->f_mode & FMODE_WRITE) drv->flags &= ~(SDF_OPEN_WRITE); /* Status changed. Signal control device */ kill_procs(drv->sd_siglist,SIGPOLL,S_MSG); wake_up_interruptible(&drv->open_wait); unlock_kernel(); return 0; } static struct file_operations sparcaudio_fops = { owner: THIS_MODULE, llseek: no_llseek, read: sparcaudio_read, write: sparcaudio_write, poll: sparcaudio_poll, ioctl: sparcaudio_ioctl, open: sparcaudio_open, release: sparcaudio_release, }; static struct { unsigned short minor; char *name; umode_t mode; } dev_list[] = { { SPARCAUDIO_MIXER_MINOR, "mixer", S_IWUSR | S_IRUGO }, { SPARCAUDIO_DSP_MINOR, "dsp", S_IWUGO | S_IRUSR | S_IRGRP }, { SPARCAUDIO_AUDIO_MINOR, "audio", S_IWUGO | S_IRUSR | S_IRGRP }, { SPARCAUDIO_DSP16_MINOR, "dspW", S_IWUGO | S_IRUSR | S_IRGRP }, { SPARCAUDIO_STATUS_MINOR, "status", S_IRUGO }, { SPARCAUDIO_AUDIOCTL_MINOR, "audioctl", S_IRUGO } }; static void sparcaudio_mkname (char *buf, char *name, int dev) { if (dev) sprintf (buf, "%s%d", name, dev); else sprintf (buf, "%s", name); } int register_sparcaudio_driver(struct sparcaudio_driver *drv, int duplex) { int i, dev; unsigned short minor; char name_buf[32]; /* If we've used up SPARCAUDIO_MAX_DEVICES, fail */ for (dev = 0; dev < SPARCAUDIO_MAX_DEVICES; dev++) { if (drivers[dev] == NULL) break; } if (drivers[dev]) return -EIO; /* Ensure that the driver has a proper operations structure. */ if (!drv->ops || !drv->ops->start_output || !drv->ops->stop_output || !drv->ops->start_input || !drv->ops->stop_input) return -EINVAL; /* Register ourselves with devfs */ for (i=0; i < sizeof (dev_list) / sizeof (*dev_list); i++) { sparcaudio_mkname (name_buf, dev_list[i].name, dev); minor = (dev << SPARCAUDIO_DEVICE_SHIFT) | dev_list[i].minor; devfs_register (devfs_handle, name_buf, DEVFS_FL_NONE, SOUND_MAJOR, minor, S_IFCHR | dev_list[i].mode, &sparcaudio_fops, NULL); } /* Setup the circular queues of output and input buffers * * Each buffer is a single page, but output buffers might * be partially filled (by a write with count < output_buffer_size), * so each output buffer also has a paired output size. * * Input buffers, on the other hand, always fill completely, * so we don't need input counts - each contains input_buffer_size * bytes of audio data. * * TODO: Make number of input/output buffers tunable parameters */ init_waitqueue_head(&drv->open_wait); init_waitqueue_head(&drv->output_write_wait); init_waitqueue_head(&drv->output_drain_wait); init_waitqueue_head(&drv->input_read_wait); drv->num_output_buffers = 8; drv->output_buffer_size = (4096 * 2); drv->playing_count = 0; drv->output_offset = 0; drv->output_eof = 0; drv->output_front = 0; drv->output_rear = 0; drv->output_count = 0; drv->output_active = 0; drv->output_buffers = kmalloc(drv->num_output_buffers * sizeof(__u8 *), GFP_KERNEL); drv->output_sizes = kmalloc(drv->num_output_buffers * sizeof(size_t), GFP_KERNEL); drv->output_notify = kmalloc(drv->num_output_buffers * sizeof(char), GFP_KERNEL); if (!drv->output_buffers || !drv->output_sizes || !drv->output_notify) goto kmalloc_failed1; drv->output_buffer = kmalloc((drv->output_buffer_size * drv->num_output_buffers), GFP_KERNEL); if (!drv->output_buffer) goto kmalloc_failed2; /* Allocate the pages for each output buffer. */ for (i = 0; i < drv->num_output_buffers; i++) { drv->output_buffers[i] = (void *)(drv->output_buffer + (i * drv->output_buffer_size)); drv->output_sizes[i] = 0; drv->output_notify[i] = 0; } /* Setup the circular queue of input buffers. */ drv->num_input_buffers = 8; drv->input_buffer_size = (4096 * 2); drv->recording_count = 0; drv->input_front = 0; drv->input_rear = 0; drv->input_count = 0; drv->input_offset = 0; drv->input_size = 0; drv->input_active = 0; drv->input_buffers = kmalloc(drv->num_input_buffers * sizeof(__u8 *), GFP_KERNEL); drv->input_sizes = kmalloc(drv->num_input_buffers * sizeof(size_t), GFP_KERNEL); if (!drv->input_buffers || !drv->input_sizes) goto kmalloc_failed3; /* Allocate the pages for each input buffer. */ if (duplex == 1) { drv->input_buffer = kmalloc((drv->input_buffer_size * drv->num_input_buffers), GFP_DMA); if (!drv->input_buffer) goto kmalloc_failed4; for (i = 0; i < drv->num_input_buffers; i++) drv->input_buffers[i] = (void *)(drv->input_buffer + (i * drv->input_buffer_size)); } else { if (duplex == 2) { drv->input_buffer = drv->output_buffer; drv->input_buffer_size = drv->output_buffer_size; drv->num_input_buffers = drv->num_output_buffers; for (i = 0; i < drv->num_input_buffers; i++) drv->input_buffers[i] = drv->output_buffers[i]; } else { for (i = 0; i < drv->num_input_buffers; i++) drv->input_buffers[i] = NULL; } } /* Take note of our duplexity */ drv->duplex = duplex; /* Ensure that the driver is marked as not being open. */ drv->flags = 0; MOD_INC_USE_COUNT; /* Take driver slot, note which we took */ drv->index = dev; drivers[dev] = drv; return 0; kmalloc_failed4: kfree(drv->input_buffer); kmalloc_failed3: if (drv->input_sizes) kfree(drv->input_sizes); if (drv->input_buffers) kfree(drv->input_buffers); i = drv->num_output_buffers; kmalloc_failed2: kfree(drv->output_buffer); kmalloc_failed1: if (drv->output_buffers) kfree(drv->output_buffers); if (drv->output_sizes) kfree(drv->output_sizes); if (drv->output_notify) kfree(drv->output_notify); return -ENOMEM; } int unregister_sparcaudio_driver(struct sparcaudio_driver *drv, int duplex) { devfs_handle_t de; int i; char name_buf[32]; /* Figure out which driver is unregistering */ if (drivers[drv->index] != drv) return -EIO; /* Deallocate the queue of output buffers. */ kfree(drv->output_buffer); kfree(drv->output_buffers); kfree(drv->output_sizes); kfree(drv->output_notify); /* Deallocate the queue of input buffers. */ if (duplex == 1) { kfree(drv->input_buffer); kfree(drv->input_sizes); } kfree(drv->input_buffers); if (&(drv->sd_siglist) != NULL) lis_free_elist( &(drv->sd_siglist) ); /* Unregister ourselves with devfs */ for (i=0; i < sizeof (dev_list) / sizeof (*dev_list); i++) { sparcaudio_mkname (name_buf, dev_list[i].name, drv->index); de = devfs_find_handle (devfs_handle, name_buf, 0, 0, DEVFS_SPECIAL_CHR, 0); devfs_unregister (de); } MOD_DEC_USE_COUNT; /* Null the appropriate driver */ drivers[drv->index] = NULL; return 0; } EXPORT_SYMBOL(register_sparcaudio_driver); EXPORT_SYMBOL(unregister_sparcaudio_driver); EXPORT_SYMBOL(sparcaudio_output_done); EXPORT_SYMBOL(sparcaudio_input_done); static int __init sparcaudio_init(void) { /* Register our character device driver with the VFS. */ if (devfs_register_chrdev(SOUND_MAJOR, "sparcaudio", &sparcaudio_fops)) return -EIO; devfs_handle = devfs_mk_dir (NULL, "sound", NULL); return 0; } static void __exit sparcaudio_exit(void) { devfs_unregister_chrdev(SOUND_MAJOR, "sparcaudio"); devfs_unregister (devfs_handle); } module_init(sparcaudio_init); module_exit(sparcaudio_exit); MODULE_LICENSE("GPL"); /* * Code from Linux Streams, Copyright 1995 by * Graham Wheeler, Francisco J. Ballesteros, Denis Froschauer * and available under GPL */ static int lis_add_to_elist( strevent_t **list, pid_t pid, short events ) { strevent_t *ev = NULL; if (*list != NULL) { for (ev = (*list)->se_next; ev != *list && ev->se_pid < pid; ev = ev->se_next) ; } if (ev == NULL || ev == *list) { /* no slot for pid in list */ ev = (strevent_t *) kmalloc(sizeof(strevent_t), GFP_KERNEL); if (ev == NULL) return(-ENOMEM); if (!*list) { /* create dummy head node */ strevent_t *hd; hd = (strevent_t *) kmalloc(sizeof(strevent_t), GFP_KERNEL); if (hd == NULL) { kfree(ev); return(-ENOMEM); } (*list = hd)->se_pid = 0; hd->se_next = hd->se_prev = hd; /* empty list */ } /* link node last in the list */ ev->se_prev = (*list)->se_prev; (*list)->se_prev->se_next = ev; ((*list)->se_prev = ev)->se_next = *list; ev->se_pid = pid; ev->se_evs = 0; } else if (ev->se_pid != pid) { /* link node in the middle of the list */ strevent_t *new; new = (strevent_t *) kmalloc(sizeof(strevent_t), GFP_KERNEL); if (new == NULL) return -ENOMEM; new->se_prev = ev->se_prev; new->se_next = ev; ev->se_prev->se_next = new; ev->se_prev = new; ev = new; /* use new element */ ev->se_pid = pid; ev->se_evs = 0; } ev->se_evs |= events; return 0; } static int lis_del_from_elist( strevent_t **list, pid_t pid, short events ) { strevent_t *ev = NULL; if (*list != NULL) { for (ev = (*list)->se_next; ev != *list && ev->se_pid < pid; ev = ev->se_next) ; } if (ev == NULL || ev == *list || ev->se_pid != pid) return 1; if ((ev->se_evs &= ~events) == 0) { /* unlink */ if (ev->se_next) /* should always be true */ ev->se_next->se_prev = ev->se_prev; if (ev->se_prev) /* should always be true */ ev->se_prev->se_next = ev->se_next; kfree(ev); } return 0; } static void lis_free_elist( strevent_t **list ) { strevent_t *ev; strevent_t *nxt; for (ev = *list; ev != NULL; ) { nxt = ev->se_next; kfree(ev); ev = nxt; if (ev == *list) break; /* all done */ } *list = NULL; } static short lis_get_elist_ent( strevent_t *list, pid_t pid ) { strevent_t *ev = NULL; if (list == NULL) return 0; for(ev = list->se_next ; ev != list && ev->se_pid < pid; ev = ev->se_next) ; if (ev != list && ev->se_pid == pid) return ev->se_evs; else return 0; } static void kill_procs( struct strevent *elist, int sig, short e) { strevent_t *ev; int res; if (elist) { for(ev = elist->se_next ; ev != elist; ev = ev->se_next) if ((ev->se_evs & e) != 0) { res = kill_proc(ev->se_pid, SIGPOLL, 1); if (res < 0) { if (res == -3) { lis_del_from_elist(&elist, ev->se_pid, S_ALL); continue; } dprintk(("kill_proc: errno %d\n",res)); } } } } /* * Overrides for Emacs so that we follow Linus's tabbing style. * Emacs will notice this stuff at the end of the file and automatically * adjust the settings for this buffer only. This must remain at the end * of the file. * --------------------------------------------------------------------------- * Local variables: * c-indent-level: 4 * c-brace-imaginary-offset: 0 * c-brace-offset: -4 * c-argdecl-indent: 4 * c-label-offset: -4 * c-continued-statement-offset: 4 * c-continued-brace-offset: 0 * indent-tabs-mode: nil * tab-width: 8 * End: */