summaryrefslogtreecommitdiff
path: root/kernel/framework/audio/oss_audio_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/framework/audio/oss_audio_core.c')
-rw-r--r--kernel/framework/audio/oss_audio_core.c6639
1 files changed, 6639 insertions, 0 deletions
diff --git a/kernel/framework/audio/oss_audio_core.c b/kernel/framework/audio/oss_audio_core.c
new file mode 100644
index 0000000..4eeba4a
--- /dev/null
+++ b/kernel/framework/audio/oss_audio_core.c
@@ -0,0 +1,6639 @@
+/*
+ * Purpose: Audio core functionality of OSS
+ */
+
+/*
+ *
+ * This file is part of Open Sound System.
+ *
+ * Copyright (C) 4Front Technologies 1996-2008.
+ *
+ * This this source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ */
+
+#undef NO_COOKED_MODE
+#define AUDIO_C
+
+#define GRC 3
+
+/* Some debugging macros (for future use) */
+#define UP_STATUS(x)
+#define DOWN_STATUS(x)
+
+#include <oss_config.h>
+
+extern int src_quality;
+extern int vmix_disabled;
+extern int excl_policy;
+
+oss_mutex_t audio_global_mutex;
+
+/*
+ * Resizeable audio device tables
+ */
+static oss_memblk_t *audio_global_memblk=NULL;
+int oss_max_audio_devfiles=4;
+int oss_max_audio_engines=8;
+#define AUDIO_MALLOC(osdev, size) oss_memblk_malloc(&audio_global_memblk, size)
+#define AUDIO_FREE(osdev, addr) oss_memblk_free(&audio_global_memblk, addr)
+#define AUDIO_ENGINE_INCREMENT (oss_max_audio_engines/2)
+#define AUDIO_DEVFILE_INCREMENT (oss_max_audio_devfiles/2)
+
+/*
+ * List of audio devices in the system
+ */
+adev_t **audio_engines = NULL;
+int num_audio_engines = 0;
+
+adev_t **audio_devfiles = NULL;
+int num_audio_devfiles = 0;
+extern int flat_device_model;
+
+#if 0
+/*
+ * Device lists (input, output, duplex) for /dev/dsp.
+ */
+
+oss_devlist_t dspinlist = { 0 };
+oss_devlist_t dspoutlist = { 0 };
+oss_devlist_t dspinoutlist = { 0 };
+oss_devlist_t dspoutlist2 = { 0 }; /* 2nd priority output devices */
+#endif
+
+/*
+ * Applications known to require special handling (mmap, etc.). These
+ * applications are listed here because they use the OSS API incorrectly and
+ * some workarounds by OSS are required to make them to work.
+ */
+typedef struct
+{
+ char *name;
+ int open_flags;
+} oss_specialapp_t;
+
+oss_specialapp_t special_apps[] = {
+ {"quake", OF_MMAP},
+ {"quake2", OF_MMAP},
+ {"q3demo.x86", OF_MMAP},
+ {"wolfsp.x86", OF_MMAP},
+ {"wolfmp.x86", OF_MMAP},
+ {"quake3", OF_MMAP},
+ {"wolf3d", OF_MMAP},
+ {"rtcw", OF_MMAP},
+ {"artsd", OF_BLOCK | OF_NOCONV},
+ {"realplay.bin", OF_SMALLFRAGS},
+ {"hxplay.bin", OF_SMALLFRAGS},
+#if 1
+ {"auserver", OF_NOCONV}, /* Win4lin */
+#endif
+ {"quake3.x86", OF_MMAP},
+ {"wolf.x86", OF_MMAP},
+ {"et.x86", OF_MMAP},
+ {"doom.x86", OF_MMAP},
+ // {"mozilla-bin", OF_MEDIUMBUF}, /* For the flash plugin */
+ {NULL, 0}
+};
+
+#ifdef APPLIST_SUPPORT
+/*
+ * Lookup tables for applications. Use these lists to assign
+ * fixed target devices for given applications.
+ */
+
+app_routing_t oss_applist[APPLIST_SIZE];
+int oss_applist_size = 0;
+
+static int
+app_lookup (int mode, const char *appname, int *open_flags)
+{
+ int i, dev;
+
+ if (appname == NULL)
+ return -2;
+
+ for (i = 0; i < oss_applist_size; i++)
+ {
+ if (oss_applist[i].mode == mode)
+ if (strncmp (appname, oss_applist[i].name, 32) == 0)
+ {
+ dev = oss_applist[i].dev;
+
+ if (dev < -1 || dev >= num_audio_devfiles)
+ return -1;
+ *open_flags |= oss_applist[i].open_flags;
+ return dev;
+ }
+ }
+
+ return -2;
+}
+#endif
+
+/*ARGSUSED*/
+static int
+get_open_flags (int mode, int open_flags, struct fileinfo *file)
+{
+ char *name;
+ int i;
+
+ if ((name = GET_PROCESS_NAME (file)) != NULL)
+ {
+#ifdef APPLIST_SUPPORT
+ if (app_lookup (mode, name, &open_flags) >= -1)
+ return open_flags;
+#endif
+
+ for (i = 0; special_apps[i].name != NULL; i++)
+ if (strcmp (special_apps[i].name, name) == 0)
+ {
+ open_flags |= special_apps[i].open_flags;
+ return open_flags;
+ }
+ }
+
+ return open_flags;
+}
+
+#define BIGWRITE_SIZE 1024
+
+#define NEUTRAL8 0x80
+#define NEUTRAL16 0x00
+#define OSS_PLUGIN_VERSION (0x100|oss_sample_bits)
+
+int always_cooked = 0;
+static unsigned long sync_seed = 0;
+
+#define COOKED_BLOCK_SIZE 4096
+
+extern oss_uint64_t oss_tmp_timer;
+
+/*
+ * Structure used by oss_audio_register_client()
+ */
+typedef struct
+{
+ oss_audio_startup_func func;
+ void *devc;
+ oss_device_t *osdev;
+} audio_startup_t;
+
+#define MAX_STARTUPS 5
+static audio_startup_t audio_startups[MAX_STARTUPS];
+static int num_audio_startups = 0;
+
+int
+dmap_get_qlen (dmap_p dmap)
+{
+ int len;
+
+ if (dmap->dma_mode == PCM_ENABLE_OUTPUT)
+ {
+ if (dmap->fragment_size == 0)
+ {
+ cmn_err (CE_WARN, "dmap (out) fragment_size=0, dev=%s\n",
+ dmap->adev->name);
+ return 0;
+ }
+
+ len =
+ (int) ((dmap->user_counter -
+ dmap->byte_counter) / dmap->fragment_size);
+ if (len < 0)
+ len = 0;
+ return len;
+ }
+
+ if (dmap->dma_mode == PCM_ENABLE_INPUT)
+ {
+ if (dmap->fragment_size == 0)
+ {
+ cmn_err (CE_WARN, "dmap (in) fragment_size=0, dev=%s\n",
+ dmap->adev->name);
+ return 0;
+ }
+
+ len =
+ (int) ((dmap->byte_counter -
+ dmap->user_counter) / dmap->fragment_size);
+ if (len < 0)
+ len = 0;
+ return len;
+ }
+
+ return 0;
+}
+
+int
+dmap_get_qhead (dmap_p dmap)
+{
+ unsigned int ptr;
+
+ if (dmap->fragment_size == 0)
+ return 0;
+
+ if (dmap->dma_mode == PCM_ENABLE_OUTPUT)
+ {
+ ptr = (int) (dmap->byte_counter % dmap->bytes_in_use);
+ return ptr / dmap->fragment_size;
+ }
+
+ if (dmap->dma_mode == PCM_ENABLE_INPUT)
+ {
+ ptr = (int) (dmap->user_counter % dmap->bytes_in_use);
+ return ptr / dmap->fragment_size;
+ }
+
+ return 0;
+}
+
+int
+dmap_get_qtail (dmap_p dmap)
+{
+ unsigned int ptr;
+
+ if (dmap->fragment_size == 0)
+ return 0;
+
+ if (dmap->dma_mode == PCM_ENABLE_INPUT)
+ {
+ ptr = (int) (dmap->byte_counter % dmap->bytes_in_use);
+ return ptr / dmap->fragment_size;
+ }
+
+ if (dmap->dma_mode == PCM_ENABLE_OUTPUT)
+ {
+ ptr = (int) (dmap->user_counter % dmap->bytes_in_use);
+ return ptr / dmap->fragment_size;
+ }
+
+ return 0;
+}
+
+int
+oss_audio_set_format (int dev, int fmt, int format_mask)
+{
+ adev_p adev;
+ int ret, newfmt;
+ unsigned char neutral_byte;
+ audio_format_info_p fmt_info;
+
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (!adev->enabled)
+ return OSS_ENXIO;
+
+ if (fmt == 0)
+ return adev->user_parms.fmt;
+
+ if (fmt == AFMT_AC3)
+ {
+ /*
+ * AC3 cannot tolerate any format/rate conversions so disable them.
+ */
+ adev->cooked_enable = 0;
+
+ /*
+ * Report EIO error if the application tries to do something stupid.
+ * Otherwise buggy applications may produce loud helicopter sound.
+ */
+
+ if (adev->flags & ADEV_VMIX)
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1018,
+ "AFMT_AC3 used with virtual mixing device."),
+ 0);
+ /*
+ * Errordesc:
+ * Devices that support virtual or hardware mixing are
+ * probably not capable to play AC3 streams properly.
+ *
+ * Virtual mixing is enabled on the audio device. The virtual
+ * mixer driver will do voolume control that corrupts the AC3
+ * bitstream. Applications using AC3 should
+ * open the audio device with O_EXCL to make sure that virtual
+ * mixing is properly bypassed.
+ */
+ return OSS_EIO;
+ }
+
+ if (adev->flags & ADEV_HWMIX)
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1019,
+ "AFMT_AC3 used with hardware mixing device."),
+ 0);
+ /*
+ * Errordesc:
+ * Devices that support virtual or hardware mixing are
+ * probably not capable to play AC3 streams properly.
+ *
+ * This error may be caused by user who selected a wrong audio
+ * device.
+ */
+ return OSS_EIO;
+ }
+
+ if (adev->dmask & DMASK_OUT)
+ if (adev->dmap_out->flags & DMAP_COOKED)
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1020,
+ "AFMT_AC3 used with format conversions enabled."),
+ 0);
+ /*
+ * Errordesc:
+ * AC3 audio format (AFMT_AC3) bitstreams don't tolerate any
+ * kind of sample rate or format conversions. However it looks
+ * like conversions would be needed. The reason may be that the
+ * application had earlier requested some sample rate that is
+ * not supported by the device.
+ *
+ * Applications using AC3 should call SNDCTL_DSP_COOKEDMODE to
+ * disable format conversions.
+ */
+ return OSS_EIO;
+ }
+ }
+
+ ret = adev->d->adrv_set_format (dev, fmt);
+
+ adev->user_parms.fmt = ret;
+ adev->hw_parms.fmt = ret;
+
+ if ((fmt_info = oss_find_format (ret)) == NULL)
+ neutral_byte = 0;
+ else
+ {
+ neutral_byte = fmt_info->neutral_byte;
+ }
+
+ if (adev->dmask & DMASK_OUT)
+ adev->dmap_out->neutral_byte = neutral_byte;
+ if (adev->dmask & DMASK_IN)
+ adev->dmap_in->neutral_byte = neutral_byte;
+
+ /* Disable format conversions if mmap() */
+ if ((adev->dmap_out->mapping_flags & DMA_MAP_MAPPED) ||
+ (adev->dmap_in->mapping_flags & DMA_MAP_MAPPED) ||
+ (adev->open_flags & OF_MMAP))
+ return ret;
+
+#ifdef NO_COOKED_MODE
+ return ret;
+#else
+
+ if (ret == fmt)
+ return ret;
+
+ if (!adev->cooked_enable)
+ return ret;
+
+/*
+ * We need to perform format conversions because the device
+ * doesn't support the requested format.
+ */
+
+ /* Convertable format? */
+ if (!(fmt & CONVERTABLE_FORMATS))
+ {
+ return ret;
+ }
+
+ adev->user_parms.fmt = fmt;
+ if (adev->dmask & DMASK_OUT)
+ {
+ adev->dmap_out->flags |= DMAP_COOKED;
+ }
+
+ if (adev->dmask & DMASK_IN)
+ {
+ adev->dmap_in->flags |= DMAP_COOKED;
+ }
+
+ /* If the device is in 16 bit format then just return */
+ if (ret & (AFMT_S16_LE | AFMT_S16_BE))
+ return fmt;
+
+ /* Try to find a suitable format */
+
+ if (fmt == AFMT_MU_LAW && ret == AFMT_U8) /* mu-Law <-> 8 bit linear */
+ return fmt;
+
+ if (format_mask & AFMT_S16_LE)
+ {
+ newfmt = AFMT_S16_LE;
+ goto got_it;
+ }
+
+ if (format_mask & AFMT_S16_BE)
+ {
+ newfmt = AFMT_S16_BE;
+ goto got_it;
+ }
+
+ return fmt; /* Nothing better than the one suggested by the device */
+got_it:
+
+ ret = adev->d->adrv_set_format (dev, newfmt);
+
+ adev->hw_parms.fmt = ret;
+
+ return fmt;
+#endif
+}
+
+int
+oss_audio_set_channels (int dev, int ch)
+{
+ adev_p adev;
+ int ret;
+
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (!adev->enabled)
+ return OSS_ENXIO;
+
+ if (ch == 0)
+ return adev->user_parms.channels;
+
+ ret = adev->d->adrv_set_channels (dev, ch);
+
+ if (ret < 1)
+ {
+ cmn_err (CE_WARN,
+ "Audio engine %d: Internal error in channel setup, err=%d, ch=%d\n",
+ dev, ret, ch);
+ return OSS_EIO;
+ }
+
+ if (ch > 2 && ch > ret) /* Requested multi channel mode not possible */
+ ch = ret;
+
+ adev->user_parms.channels = ret;
+ adev->hw_parms.channels = ret;
+
+ /* Disable format conversions if mmap() */
+ if ((adev->dmap_out->mapping_flags & DMA_MAP_MAPPED) ||
+ (adev->dmap_in->mapping_flags & DMA_MAP_MAPPED) ||
+ (adev->open_flags & OF_MMAP))
+ return ret;
+
+ if (ret > 1 && (adev->flags & ADEV_NONINTERLEAVED))
+ {
+ if (adev->dmask & DMASK_OUT)
+ adev->dmap_out->flags |= DMAP_COOKED;
+
+ if (adev->dmask & DMASK_IN)
+ adev->dmap_in->flags |= DMAP_COOKED;
+ }
+
+
+#ifdef NO_COOKED_MODE
+ return ret;
+#else
+
+ if (!adev->cooked_enable)
+ return ret;
+
+ if (ret == ch)
+ return ret;
+
+ /* For the time being only stereo <->mono is possible */
+ if (ch != ret)
+ if (!((ch == 1 && ret == 2) || (ch == 2 && ret == 1)))
+ return ret;
+
+/*
+ * Needs to perform format conversions
+ */
+
+ adev->user_parms.channels = ch;
+ if (adev->dmask & DMASK_OUT)
+ {
+ adev->dmap_out->flags |= DMAP_COOKED;
+ }
+
+ if (adev->dmask & DMASK_IN)
+ {
+ adev->dmap_in->flags |= DMAP_COOKED;
+ }
+
+ return ch;
+#endif
+}
+
+int
+oss_audio_set_rate (int dev, int rate)
+{
+ adev_p adev;
+ int ret;
+
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (!adev->enabled)
+ return OSS_ENXIO;
+
+ if (rate == 0)
+ return adev->user_parms.rate;
+
+ ret = adev->d->adrv_set_rate (dev, rate);
+ if (ret < 0)
+ return ret;
+
+ adev->user_parms.rate = ret;
+ adev->hw_parms.rate = ret;
+
+ /* Disable format conversions if mmap() */
+ if ((adev->dmap_out->mapping_flags & DMA_MAP_MAPPED) ||
+ (adev->dmap_in->mapping_flags & DMA_MAP_MAPPED) ||
+ (adev->flags & ADEV_NOSRC) || (adev->open_flags & OF_MMAP))
+ return ret;
+
+ if (!adev->cooked_enable)
+ return ret;
+
+#if defined(NO_COOKED_MODE) || GRC == 0
+ return ret;
+#else
+
+ if (ret == rate) /* No SRC needed */
+ return ret;
+
+/*
+ * Needs to perform format conversions
+ */
+
+ adev->user_parms.rate = rate;
+ if (adev->dmask & DMASK_OUT)
+ {
+ adev->dmap_out->flags |= DMAP_COOKED;
+ }
+
+ if (adev->dmask & DMASK_IN)
+ {
+ adev->dmap_in->flags |= DMAP_COOKED;
+ }
+
+ return rate;
+#endif
+}
+
+static void
+reset_dmap (dmap_p dmap)
+{
+#ifdef DO_TIMINGS
+ oss_do_timing ("Reset dmap");
+#endif
+ dmap->dma_mode = 0;
+ dmap->flags &= (DMAP_FRAGFIXED | DMAP_COOKED);
+ dmap->byte_counter = dmap->user_counter = 0;
+ dmap->fragment_counter = 0;
+ dmap->interrupt_count = 0;
+ dmap->expand_factor = UNIT_EXPAND; /* 1:1 */
+ dmap->play_underruns = dmap->rec_overruns = 0;
+ dmap->num_errors = 0;
+ dmap->leftover_bytes = 0;
+ dmap->leftover_buf = NULL;
+}
+
+int
+oss_alloc_dmabuf (int dev, dmap_p dmap, int direction)
+{
+ adev_t *adev = dmap->adev;
+
+ if (adev == NULL)
+ {
+ cmn_err (CE_WARN, "oss_alloc_dmabuf: adev==NULL\n");
+ return OSS_EIO;
+ }
+
+ return __oss_alloc_dmabuf (dev, dmap, adev->dmabuf_alloc_flags,
+ adev->dmabuf_maxaddr, direction);
+}
+
+static int
+default_alloc_buffer (int dev, dmap_t * dmap, int direction)
+{
+ int err;
+
+ if (dmap->dmabuf != NULL)
+ return 0;
+
+ if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0)
+ return err;
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+default_free_buffer (int dev, dmap_t * dmap, int direction)
+{
+ if (dmap->dmabuf == NULL)
+ return 0;
+
+ oss_free_dmabuf (dev, dmap);
+
+ dmap->dmabuf = NULL;
+ dmap->dmabuf_phys = 0;
+ return 0;
+}
+
+static int
+init_dmap (adev_p adev, dmap_p dmap, int direction)
+{
+ int retval;
+ int l, static_len;
+ char *p;
+ oss_native_word flags;
+
+ if (dmap->osdev == NULL)
+ {
+ cmn_err (CE_WARN, "dmap->osdev==NULL\n");
+ return OSS_EIO;
+ }
+
+ if (dmap->dmabuf == NULL)
+ {
+ if (adev->d->adrv_alloc_buffer != NULL)
+ {
+ retval =
+ adev->d->adrv_alloc_buffer (adev->engine_num, dmap, direction);
+ }
+ else
+ {
+ retval = default_alloc_buffer (adev->engine_num, dmap, direction);
+ }
+ if (retval < 0)
+ {
+ cmn_err (CE_WARN,
+ "Failed to allocate DMA buffer for audio engine %d/%s\n",
+ adev->engine_num, adev->name);
+ return retval;
+ }
+
+ if (dmap->dmabuf_phys == 0) /* Cannot do mmap */
+ adev->flags |= ADEV_NOMMAP;
+ }
+
+#ifdef linux
+ if (dmap->dmabuf_phys == 0)
+ adev->flags |= ADEV_NOMMAP;
+#endif
+ dmap->flags &= ~DMAP_FRAGFIXED;
+
+ l = sizeof (dmap_t);
+ static_len = (oss_native_word) & dmap->flags - (oss_native_word) dmap;
+
+ p = (char *) &dmap->flags;
+ l -= static_len;
+ if (p != NULL)
+ memset (p, 0, l); /* Clear all */
+
+ if (!dmap->buffer_protected && dmap->dmabuf != NULL)
+ memset (dmap->dmabuf, dmap->neutral_byte, dmap->buffsize);
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ reset_dmap (dmap);
+ dmap->bytes_in_use = dmap->buffsize;
+ dmap->frame_size = 1;
+ dmap->user_frame_size = 1;
+ dmap->flags = 0;
+ dmap->audio_callback = NULL;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return 0;
+}
+
+void
+audio_reset_adev (adev_p adev)
+{
+ oss_native_word flags, dflags;
+#ifdef DO_TIMINGS
+ oss_do_timing ("Reset input & output\n");
+#endif
+ sync_seed++;
+ if (!adev->enabled)
+ return;
+
+ dflags = 0;
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ adev->go = 1;
+ adev->enable_bits = adev->open_mode;
+ if (adev->open_mode & OPEN_WRITE)
+ {
+ dmap_p dmap = adev->dmap_out;
+ dmap->flags &= ~DMAP_PREPARED;
+ memset (dmap->dmabuf, 0, dmap->buffsize);
+ }
+
+ if (adev->d->adrv_halt_input && adev->d->adrv_halt_output)
+ {
+ if (adev->open_mode & OPEN_READ)
+ {
+ dmap_p dmap = adev->dmap_in;
+ MUTEX_ENTER (dmap->mutex, dflags);
+ dmap->flags &= ~DMAP_PREPARED;
+ if ((dmap->dma_mode == PCM_ENABLE_INPUT)
+ && (dmap->flags & DMAP_STARTED))
+ {
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_RESET_INPUT);
+#endif
+ MUTEX_EXIT (dmap->mutex, dflags);
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+ adev->d->adrv_halt_input (adev->engine_num);
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ MUTEX_ENTER (dmap->mutex, dflags);
+ reset_dmap (dmap);
+ }
+ MUTEX_EXIT (dmap->mutex, dflags);
+ }
+ if (adev->open_mode & OPEN_WRITE)
+ {
+ dmap_p dmap = adev->dmap_out;
+ MUTEX_ENTER (dmap->mutex, dflags);
+ if ((dmap->dma_mode == PCM_ENABLE_OUTPUT) &&
+ (dmap->flags & DMAP_STARTED))
+ {
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_RESET_OUTPUT);
+#endif
+ MUTEX_EXIT (dmap->mutex, dflags);
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+ adev->d->adrv_halt_output (adev->engine_num);
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ MUTEX_ENTER (dmap->mutex, dflags);
+ reset_dmap (dmap);
+ }
+ MUTEX_EXIT (dmap->mutex, dflags);
+ }
+
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+ return;
+ }
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_RESET_OUTPUT);
+ ossd_event (adev->engine_num, OSSD_EV_RESET_INPUT);
+#endif
+ adev->d->adrv_halt_io (adev->engine_num);
+
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ reset_dmap (adev->dmap_out);
+ reset_dmap (adev->dmap_in);
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+}
+
+static void
+audio_reset_input (adev_p adev)
+{
+ dmap_p dmap = adev->dmap_in;
+ oss_native_word flags, dflags;
+
+ dflags = 0;
+ if (!(adev->open_mode & OPEN_READ))
+ return;
+
+ if (dmap->dma_mode != PCM_ENABLE_INPUT)
+ return;
+
+ if (!(dmap->flags & DMAP_STARTED))
+ return;
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Reset input\n");
+#endif
+ dmap->flags &= ~DMAP_PREPARED;
+
+ if (adev->d->adrv_halt_input)
+ {
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_RESET_INPUT);
+#endif
+ adev->d->adrv_halt_input (adev->engine_num);
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ reset_dmap (dmap);
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return;
+ }
+
+ adev->d->adrv_halt_io (adev->engine_num);
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ MUTEX_ENTER (dmap->mutex, dflags);
+ reset_dmap (dmap);
+ MUTEX_EXIT (dmap->mutex, dflags);
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+}
+
+static void
+audio_reset_output (adev_p adev)
+{
+ dmap_p dmap = adev->dmap_out;
+ oss_native_word flags, dflags;
+ dflags = 0;
+
+ sync_seed++;
+
+ if (!(adev->open_mode & OPEN_WRITE))
+ return;
+
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT)
+ return;
+
+ if (!(dmap->flags & DMAP_STARTED))
+ return;
+
+ dmap->flags &= ~DMAP_PREPARED;
+#ifdef DO_TIMINGS
+ oss_do_timing ("Reset output\n");
+#endif
+
+ if (adev->d->adrv_halt_output)
+ {
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_RESET_OUTPUT);
+#endif
+ adev->d->adrv_halt_output (adev->engine_num);
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ reset_dmap (dmap);
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return;
+ }
+
+ adev->d->adrv_halt_io (adev->engine_num);
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ MUTEX_ENTER (dmap->mutex, dflags);
+ reset_dmap (dmap);
+ MUTEX_EXIT (dmap->mutex, dflags);
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+}
+
+static int launch_output (adev_p adev, dmap_p dmap);
+static int launch_input (adev_p adev, dmap_p dmap);
+
+static void
+oss_audio_post (adev_p adev)
+{
+ int i, n, p;
+ oss_uint64_t limit;
+ dmap_p dmap = adev->dmap_out;
+
+ if (!(adev->open_mode & OPEN_WRITE))
+ return;
+ if (dmap->user_counter == 0)
+ return;
+
+ if (dmap->flags & DMAP_STARTED)
+ return;
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Post output\n");
+#endif
+
+ /* Clean the unused buffer space (in slow way) */
+ limit = dmap->byte_counter + dmap->bytes_in_use;
+ if (limit > dmap->user_counter + dmap->bytes_in_use)
+ limit = dmap->user_counter + dmap->bytes_in_use;
+ n = (int) (limit - dmap->user_counter) + 1;
+ p = (int) (dmap->user_counter % dmap->bytes_in_use); /* Current ptr */
+ for (i = 0; i < n; i++)
+ {
+ dmap->dmabuf[p] = dmap->neutral_byte;
+ p = (p + 1) % dmap->bytes_in_use;
+ }
+
+ launch_output (adev, dmap);
+}
+
+static void
+oss_audio_sync (adev_p adev)
+{
+ dmap_p dmap = adev->dmap_out;
+ oss_uint64_t uc, limit;
+ oss_native_word flags;
+ unsigned int status;
+
+ int n = 0, loops = 0, tmout = OSS_HZ, i;
+ int prev_underruns;
+
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT)
+ return;
+ if (adev->dmap_out->mapping_flags & DMA_MAP_MAPPED)
+ return;
+
+ if (!(dmap->flags & DMAP_STARTED))
+ oss_audio_post (adev);
+
+ if (!(dmap->flags & DMAP_STARTED))
+ return;
+
+ /* Don't wait if output is untriggered */
+ if (adev->go == 0 || !(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ return;
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Sync output\n");
+#endif
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ dmap->flags |= DMAP_POST;
+
+ uc = dmap->user_counter;
+ prev_underruns = dmap->play_underruns;
+ dmap->play_underruns = 0;
+
+ while (dmap->play_underruns == 0 && loops++ < dmap->nfrags
+ && dmap->byte_counter < uc)
+ {
+ int p;
+ adev->dmap_out->error = 0;
+ /* Clean the unused buffer space (in a slow but precise way) */
+ limit = dmap->byte_counter + dmap->bytes_in_use;
+ if (limit > dmap->user_counter + dmap->bytes_in_use)
+ limit = dmap->user_counter + dmap->bytes_in_use;
+
+ p = (int) (dmap->user_counter % dmap->bytes_in_use); /* Current ptr */
+ n = (int) (limit - dmap->user_counter) + 1;
+ for (i = 0; i < n; i++)
+ {
+ dmap->dmabuf[p] = dmap->neutral_byte;
+ p = (p + 1) % dmap->bytes_in_use;
+ }
+
+ tmout = (dmap->fragment_size * OSS_HZ) / dmap->data_rate;
+ tmout += OSS_HZ / 10;
+
+ if (!oss_sleep (adev->out_wq, &dmap->mutex, tmout, &flags, &status))
+ {
+#ifndef __FreeBSD__
+ cmn_err (CE_WARN, "Output timed out (sync) on audio engine %d\n",
+ adev->engine_num);
+#endif
+ adev->timeout_count++;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ FMA_EREPORT(adev->osdev, DDI_FM_DEVICE_STALL, NULL, NULL, NULL);
+ FMA_IMPACT(adev->osdev, DDI_SERVICE_LOST);
+ return;
+ }
+ }
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ n = 0;
+ if (adev->d->adrv_local_qlen) /* Drain the internal queue too */
+ while (n++ <=
+ adev->d->adrv_local_qlen (adev->engine_num) / dmap->fragment_size)
+ {
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ audio_engines[adev->engine_num]->dmap_out->error = 0;
+ tmout = (dmap->fragment_size * OSS_HZ) / dmap->data_rate;
+ tmout += OSS_HZ / 10;
+ oss_sleep (adev->out_wq, &dmap->mutex, tmout, &flags, &status);
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ }
+ dmap->play_underruns = prev_underruns;
+}
+
+static int prepare_output (adev_p adev, dmap_p dmap);
+static int prepare_input (adev_p adev, dmap_p dmap);
+
+void
+oss_audio_set_error (int dev, int mode, int err, int parm)
+{
+ int i, n;
+
+ dmap_t *dmap;
+
+#ifdef DO_TIMINGS
+ {
+ if (mode == E_REC)
+ oss_timing_printf ("Audio rec error code %05d/%d, engine=%d", err, parm,
+ mode);
+ else
+ oss_timing_printf ("Audio play error code %05d/%d, engine=%d", err, parm,
+ mode);
+ }
+#endif
+
+#if 0
+ if (mode == E_REC)
+ cmn_err (CE_CONT, "Audio rec error code %05d/%d, engine=%d\n", err, parm,
+ mode);
+ else
+ cmn_err (CE_CONT, "Audio play error code %05d/%d, engine=%d\n", err, parm,
+ mode);
+#endif
+
+ switch (mode)
+ {
+ case E_PLAY:
+ dmap = audio_engines[dev]->dmap_out;
+ break;
+ case E_REC:
+ dmap = audio_engines[dev]->dmap_in;
+ break;
+
+ default:
+ dmap = audio_engines[dev]->dmap_out; /* Actually this would be an error */
+ }
+
+ n = dmap->num_errors++;
+ if (n > MAX_AUDIO_ERRORS)
+ n = MAX_AUDIO_ERRORS;
+
+ if (n > 0)
+ {
+
+/*
+ * Move previous errors (if any) upwards in the list.
+ */
+ for (i = n; i > 0; i--)
+ {
+ dmap->errors[i] = dmap->errors[i - 1];
+ dmap->error_parms[i] = dmap->error_parms[i - 1];
+ }
+ }
+
+ dmap->errors[0] = err;
+ dmap->error_parms[0] = parm;
+
+}
+
+/*ARGSUSED*/
+static void
+report_errors (char *s, const char *hdr, adev_t * adev, dmap_t * dmap,
+ int bufsize)
+{
+ int i, l, j;
+
+ if (bufsize < 30) /* No space left */
+ return;
+
+ strcpy (s, hdr);
+ s += l = strlen (s);
+ bufsize -= l;
+
+ for (i = 0; i < dmap->num_errors && i < MAX_AUDIO_ERRORS; i++)
+ {
+ int dupe = 0;
+
+ if (bufsize < 30)
+ return;
+ for (j = 0; !dupe && j < i; j++)
+ if (dmap->errors[i] == dmap->errors[j])
+ dupe = 1;
+
+ if (dupe)
+ continue;
+
+ sprintf (s, " %05d:%d", dmap->errors[i], dmap->error_parms[i]);
+ s += l = strlen (s);
+ bufsize -= l;
+ }
+}
+
+static void
+store_history (int dev)
+{
+ int p;
+ char *tmp, *s;
+
+ adev_p adev = audio_engines[dev];
+
+ if (adev->pid == 0) /* Virtual mixer */
+ return;
+
+ if (*adev->cmd && strcmp (adev->cmd, "sndconf") == 0)
+ return;
+
+ p = oss_history_p;
+ oss_history_p = (oss_history_p + 1) % OSS_HISTORY_SIZE;
+
+ tmp = oss_history[p];
+ memset (tmp, 0, OSS_HISTORY_STRSIZE);
+ s = tmp;
+
+ sprintf (s, "%s.%02d: ", adev->devnode, adev->engine_num);
+ s += strlen (s);
+
+ if (adev->pid != -1)
+ {
+ sprintf (s, "pid %d ", adev->pid);
+ s += strlen (s);
+ }
+
+ if (*adev->cmd != 0)
+ {
+ sprintf (s, "cmd '%s' ", adev->cmd);
+ s += strlen (s);
+ }
+ else
+ {
+ strcpy (s, "cmd: Unknown ");
+ s += strlen (s);
+ }
+
+ if (adev->open_mode & OPEN_READ)
+ {
+ sprintf (s, "IN ");
+ s += strlen (s);
+ }
+
+ if (adev->open_mode & OPEN_WRITE)
+ {
+ sprintf (s, "OUT ");
+ s += strlen (s);
+ }
+
+ if (adev->timeout_count > 0)
+ {
+ sprintf (s, "%d timeouts ", adev->timeout_count);
+ s += strlen (s);
+ }
+
+ if (adev->dmap_out->play_underruns > 0)
+ {
+ sprintf (s, "%d underruns ", adev->dmap_out->play_underruns);
+ s += strlen (s);
+ }
+
+ if (adev->dmap_in->rec_overruns > 0)
+ {
+ sprintf (s, "%d overruns ", adev->dmap_in->rec_overruns);
+ s += strlen (s);
+ }
+
+ if (adev->dmap_out->num_errors > 0)
+ {
+ report_errors (s, "Play events:", adev, adev->dmap_out,
+ OSS_HISTORY_STRSIZE - strlen (tmp) - 1);
+ s += strlen (s);
+ *s++ = ' ';
+ }
+
+ if (adev->dmap_in->num_errors > 0)
+ {
+ report_errors (s, "Rec events:", adev, adev->dmap_in,
+ OSS_HISTORY_STRSIZE - strlen (tmp) - 1);
+ s += strlen (s);
+ *s++ = ' ';
+ }
+}
+
+char *
+audio_show_latency (int dev)
+{
+ static char str[32];
+ adev_p adev = audio_engines[dev];
+ dmap_p dmap;
+ int dr, fs;
+
+ *str = 0;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return "Bad device";
+
+ if (adev->open_mode == 0)
+ return "Not opened";
+
+ if (adev->open_mode == OPEN_READ)
+ dmap = adev->dmap_in;
+ else
+ dmap = adev->dmap_out;
+
+ if (dmap == NULL)
+ return "Unknown";
+
+ if (!(dmap->flags & DMAP_STARTED))
+ return "Not started";
+
+ dr = dmap->data_rate;
+ fs = dmap->fragment_size;
+
+ if (dr <= 0)
+ sprintf (str, "%d", dmap->fragment_size);
+ else
+ {
+ int r = (fs * 10000) / dr;
+
+ sprintf (str, "%d/%d (%d.%d msec)", dmap->fragment_size, dr, r / 10,
+ r % 10);
+ }
+
+
+ return str;
+}
+
+void
+oss_audio_release (int dev, struct fileinfo *file)
+{
+ adev_p adev;
+ int mode;
+ oss_native_word flags;
+
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return;
+ if (file)
+ mode = file->mode & O_ACCMODE;
+ else
+ mode = OPEN_READ | OPEN_WRITE;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("=-=-=- Closing audio engine %d -=-=-=", dev);
+#endif
+
+ adev = audio_engines[dev];
+
+ if (adev->unloaded || !adev->enabled)
+ {
+ cmn_err (CE_CONT, "Audio device %s not available - close aborted\n",
+ adev->name);
+ return;
+ }
+
+ oss_audio_post (adev);
+ oss_audio_sync (adev);
+
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_CLOSE);
+#endif
+ adev->d->adrv_halt_io (dev);
+ adev->d->adrv_close (dev, mode);
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ if (adev->dmask & DMASK_OUT)
+ {
+ adev->dmap_out->audio_callback = NULL;
+ if (!adev->dmap_out->buffer_protected)
+ if (adev->dmap_out->dmabuf != NULL)
+ memset (adev->dmap_out->dmabuf, adev->dmap_out->neutral_byte,
+ adev->dmap_out->buffsize);
+ }
+
+ if (adev->dmask & DMASK_IN)
+ {
+ adev->dmap_in->audio_callback = NULL;
+ }
+
+ store_history (dev);
+
+ memset (audio_engines[dev]->cmd, 0, sizeof (audio_engines[dev]->cmd));
+ memset (audio_engines[dev]->label, 0, sizeof (audio_engines[dev]->label));
+ memset (audio_engines[dev]->song_name, 0,
+ sizeof (audio_engines[dev]->song_name));
+ audio_engines[dev]->pid = -1;
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+
+ /*
+ * Free the DMA buffer(s)
+ */
+ if (adev->dmask & DMASK_OUT)
+ if (adev->dmap_out->dmabuf != NULL)
+ {
+ if (adev->d->adrv_free_buffer != NULL)
+ {
+ adev->d->adrv_free_buffer (dev, adev->dmap_out, OPEN_WRITE);
+ }
+ else
+ default_free_buffer (dev, adev->dmap_out, OPEN_WRITE);
+ adev->dmap_out->dmabuf = NULL;
+ }
+
+ if (adev->dmask & DMASK_IN)
+ if (adev->dmap_in != adev->dmap_out && adev->dmap_in->dmabuf != NULL)
+ {
+ if (adev->d->adrv_free_buffer != NULL)
+ {
+ adev->d->adrv_free_buffer (dev, adev->dmap_in, OPEN_READ);
+ }
+ else
+ default_free_buffer (dev, adev->dmap_in, OPEN_READ);
+ adev->dmap_in->dmabuf = NULL;
+ }
+
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ adev->open_mode = 0;
+ adev->flags &= ~ADEV_OPENED;
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+}
+
+static void audio_outputintr (int dev, int intr_flags);
+static void audio_inputintr (int dev, int intr_flags);
+
+/*ARGSUSED*/
+int
+oss_audio_open_engine (int dev, int no_worries, struct fileinfo *file,
+ int recursive, int open_flags, int *newdev)
+{
+/*
+ * Open audio engine
+ */
+ adev_p adev;
+ int retval = 0, fmt;
+ int mode;
+ int saved_cooked;
+ int channels, rate;
+ oss_native_word flags;
+ extern int cooked_enable; /* Config option */
+
+ DOWN_STATUS (0xff);
+
+ if (file)
+ {
+ mode = file->mode & O_ACCMODE;
+ }
+ else
+ mode = OPEN_READ | OPEN_WRITE;
+
+ sync_seed++;
+
+ DDB (cmn_err
+ (CE_CONT, "oss_audio_open_engine(%d, mode=0x%x)\n", dev, mode));
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("-----> oss_audio_open_engine(%d, mode=0x%x)", dev, mode);
+#endif
+
+ if (dev < 0 || dev >= num_audio_engines || audio_engines == NULL)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (adev->unloaded)
+ return OSS_ENODEV;
+ if (!adev->enabled)
+ return OSS_ENXIO;
+
+ if (adev->osdev == NULL)
+ {
+ cmn_err (CE_WARN, "adev->osdev==NULL\n");
+ return OSS_EIO;
+ }
+
+ open_flags = get_open_flags (mode, open_flags, file);
+
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ if (adev->open_mode != 0)
+ {
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+ return OSS_EBUSY;
+ }
+ adev->open_mode = mode;
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+
+ adev->cooked_enable = cooked_enable; /* This must be done before drv->open() */
+#ifdef DO_TIMINGS
+ if (adev->cooked_enable)
+ oss_do_timing ("Initial cooked mode ON");
+ else
+ oss_do_timing ("Initial cooked mode OFF");
+#endif
+
+ adev->src_quality = src_quality;
+ if ((retval = adev->d->adrv_open (dev, mode, open_flags)) < 0)
+ {
+ adev->open_mode = 0;
+
+ return retval;
+ }
+
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ adev->outputintr = audio_outputintr;
+ adev->inputintr = audio_inputintr;
+ adev->forced_nonblock = 0;
+ adev->setfragment_warned = 0;
+ adev->getispace_error_count = 0;
+ adev->sync_next = NULL;
+ adev->sync_group = 0;
+ adev->sync_flags = 0;
+ adev->open_flags = open_flags;
+ adev->flags |= ADEV_OPENED;
+ adev->timeout_count = 0;
+ adev->policy = -1;
+ adev->song_name[0] = 0;
+ adev->label[0] = 0;
+
+ if (open_flags & (OF_NOCONV | OF_MMAP))
+ {
+ adev->cooked_enable = 0;
+#ifdef DO_TIMINGS
+ oss_do_timing ("Forcing cooked mode OFF");
+#endif
+ }
+
+ memset (audio_engines[dev]->cmd, 0, sizeof (audio_engines[dev]->cmd));
+
+ if (recursive)
+ {
+ strcpy (audio_engines[dev]->cmd, "OSS internal");
+ audio_engines[dev]->pid = 0;
+ }
+ else
+ {
+ char *cmd;
+
+ if ((cmd = GET_PROCESS_NAME (file)) != NULL)
+ {
+ strncpy (audio_engines[dev]->cmd, cmd, 16);
+ strncpy (audio_engines[dev]->label, cmd, 16);
+ audio_engines[dev]->cmd[15] = '\0';
+ audio_engines[dev]->label[15] = '\0';
+ }
+ audio_engines[dev]->pid = GET_PROCESS_PID (file);
+ }
+
+ adev->dmask = 0;
+/*
+ * Find out which dmap structures are required.
+ */
+
+ if (mode == OPEN_WRITE)
+ adev->dmask = DMASK_OUT;
+ else if (mode == OPEN_READ)
+ adev->dmask = DMASK_IN;
+ else
+ {
+ if (mode != (OPEN_WRITE | OPEN_READ))
+ cmn_err (CE_NOTE, "Unrecognized open mode %x\n", mode);
+ adev->dmask = DMASK_OUT;
+
+ if ((adev->flags & ADEV_DUPLEX) && adev->dmap_out != adev->dmap_in)
+ adev->dmask = DMASK_OUT | DMASK_IN;
+ }
+
+#if 0
+/*
+ * Handling of non-blocking doesn't belong here. It will be handled
+ * by read/write.
+ */
+ if (file != NULL && !(open_flags & OF_BLOCK))
+ if (ISSET_FILE_FLAG (file, O_NONBLOCK) || adev->forced_nonblock)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("*** Non-blocking open ***");
+#endif
+ adev->nonblock = 1;
+ }
+#endif
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+
+ if (adev->dmask & DMASK_OUT)
+ {
+
+ /*
+ * Use small DMA buffer if requested and if the device supports it.
+ */
+ if (SMALL_DMABUF_SIZE >= (adev->min_block * 2) &&
+ (open_flags & OF_SMALLBUF))
+ adev->dmap_out->flags |= DMAP_SMALLBUF;
+ else
+ if (MEDIUM_DMABUF_SIZE >= (adev->min_block * 2) &&
+ (open_flags & OF_MEDIUMBUF))
+ adev->dmap_out->flags |= DMAP_MEDIUMBUF;
+ else
+ adev->dmap_out->flags &= ~(DMAP_SMALLBUF | DMAP_MEDIUMBUF);
+
+ if ((retval = init_dmap (adev, adev->dmap_out, OPEN_WRITE)) < 0)
+ {
+ oss_audio_release (dev, file);
+ return retval;
+ }
+ }
+
+ if (adev->dmask & DMASK_IN)
+ {
+ /*
+ * Use small DMA buffer if requested and if the device supports it.
+ */
+ if (SMALL_DMABUF_SIZE >= (adev->min_block * 2) &&
+ (open_flags & OF_SMALLBUF))
+ adev->dmap_in->flags |= DMAP_SMALLBUF;
+ else
+ adev->dmap_in->flags &= ~DMAP_SMALLBUF;
+
+ if ((retval = init_dmap (adev, adev->dmap_in, OPEN_READ)) < 0)
+ {
+ oss_audio_release (dev, file);
+ return retval;
+ }
+ }
+
+ adev->dmap_out->num_errors = 0;
+ adev->dmap_in->num_errors = 0;
+
+ if ((mode & OPEN_WRITE) && !(adev->flags & ADEV_NOOUTPUT))
+ {
+ oss_reset_wait_queue (adev->out_wq);
+ }
+ if ((mode & OPEN_READ) && !(adev->flags & ADEV_NOINPUT))
+ {
+ oss_reset_wait_queue (adev->in_wq);
+ }
+
+ adev->xformat_mask = 0;
+
+ switch (adev->dmask)
+ {
+ case DMASK_OUT:
+ adev->xformat_mask = adev->oformat_mask;
+ break;
+
+ case DMASK_IN:
+ adev->xformat_mask = adev->iformat_mask;
+ break;
+
+ case DMASK_OUT | DMASK_IN:
+ adev->xformat_mask = adev->oformat_mask & adev->iformat_mask;
+ break;
+
+ default:
+ cmn_err (CE_WARN, "Internal error A during open\n");
+ }
+
+ fmt = DSP_DEFAULT_FMT;
+ rate = DSP_DEFAULT_SPEED;
+
+ if (adev->flags & ADEV_FIXEDRATE && adev->fixed_rate != 0)
+ rate = adev->fixed_rate;
+
+ if (adev->flags & ADEV_16BITONLY)
+ fmt = AFMT_S16_LE;
+
+ adev->go = 1;
+ adev->enable_bits = adev->open_mode;
+
+ channels = 1;
+ if (adev->flags & ADEV_STEREOONLY)
+ channels = 2;
+
+ saved_cooked = adev->cooked_enable;
+ adev->cooked_enable = 0;
+ oss_audio_set_format (dev, fmt, adev->xformat_mask);
+ oss_audio_set_channels (dev, channels);
+ oss_audio_set_rate (dev, rate);
+ adev->cooked_enable = saved_cooked;
+
+/* Clear the Cooked mode to permit mmap() */
+ if (adev->dmask & DMASK_OUT)
+ adev->dmap_out->flags &= ~DMAP_COOKED;
+ if (adev->dmask & DMASK_IN)
+ adev->dmap_in->flags &= ~DMAP_COOKED;
+
+#ifdef DO_TIMINGS
+ {
+ char *p_name;
+ pid_t proc_pid;
+ oss_timing_printf ("=-=-=- Audio device %d (%s) opened, mode %x -=-=-=",
+ adev->engine_num, adev->name, mode);
+
+ if ((proc_pid = GET_PROCESS_PID (file)) != -1)
+ oss_timing_printf ("Opened by PID: %d", proc_pid);
+
+ if ((p_name = GET_PROCESS_NAME (file)) != NULL)
+ oss_timing_printf ("Opening process name: %s", p_name);
+ }
+#endif
+
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_OPEN);
+#endif
+
+ return adev->engine_num;
+}
+
+#ifdef VDEV_SUPPORT
+int
+oss_audio_open_devfile (int dev, int dev_class, struct fileinfo *file,
+ int recursive, int open_flags, int *newdev)
+{
+/*
+ * Open audio device file (by calling oss_audio_open_engine).
+ */
+ int err, d, n;
+ int open_excl = 0;
+ adev_p adev;
+ int mode = OPEN_READ | OPEN_WRITE;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("-----> oss_audio_open_devfile(%d, mode=0x%x)", dev, mode);
+#endif
+
+ if (file)
+ {
+ mode = file->mode & O_ACCMODE;
+ open_flags = get_open_flags (mode, open_flags, file);
+ if (file->acc_flags & O_EXCL)
+ {
+ if (excl_policy == 0) open_excl = 1;
+#ifdef GET_PROCESS_UID
+ else if ((excl_policy == 1) && (GET_PROCESS_UID () == 0)) open_excl = 1;
+#endif
+ }
+ }
+
+ DDB (cmn_err
+ (CE_CONT, "oss_audio_open_devfile(%d, mode=0x%x, excl=%d)\n", dev,
+ mode, open_excl));
+
+ if (dev < 0 || dev >= num_audio_devfiles)
+ {
+ return OSS_ENODEV;
+ }
+ adev = audio_devfiles[dev];
+ dev = adev->engine_num;
+
+ n=0;
+ while (adev->d->adrv_redirect != NULL)
+ {
+ int next_dev = dev;
+
+ if (n++ > num_audio_engines)
+ {
+ cmn_err(CE_CONT, "Recursive audio device redirection\n");
+ return OSS_ELOOP;
+ }
+
+ next_dev = adev->d->adrv_redirect (dev, mode, open_flags);
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Engine redirect %d -> %d\n", dev, next_dev);
+#endif
+
+ if (next_dev == dev) /* No change */
+ break;
+
+ if (next_dev < 0 || next_dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ dev = next_dev;
+ adev = audio_engines[dev];
+ }
+
+/*
+ * Check Exclusive mode open - do not do any kind of device
+ * sharing. For the time being this mode is not supported with devices
+ * that do hardware mixing.
+ */
+ if (open_excl && !(adev->flags & ADEV_HWMIX))
+ {
+ DDB (cmn_err
+ (CE_CONT, "Exclusive open, engine=%d\n", adev->engine_num));
+ if ((dev = oss_audio_open_engine (adev->engine_num, dev_class, file,
+ recursive, open_flags, newdev)) < 0)
+ {
+ return dev;
+ }
+
+ goto done;
+ }
+
+#ifdef CONFIG_OSS_VMIX
+/*
+ * Get a vmix engine if the device has vmix support enabled.
+ */
+ if (!vmix_disabled)
+ if (adev->vmix_mixer != NULL) // Virtual mixer attached
+ {
+ int vmix_dev;
+
+ /*
+ * Create a new vmix client instance.
+ */
+
+ if ((vmix_dev=vmix_create_client(adev->vmix_mixer))>=0)
+ {
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Vmix redirect %d -> %d\n", dev, vmix_dev);
+#endif
+ if ((dev = oss_audio_open_engine (vmix_dev, dev_class, file,
+ recursive, open_flags, newdev)) < 0)
+ {
+ //cmn_err(CE_WARN, "Failed to open vmix engine %d, err=%d\n", vmix_dev, dev);
+ return dev;
+ }
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Vmix engine opened %d -> %d\n", vmix_dev, dev);
+#endif
+
+ goto done;
+ }
+ }
+#endif
+
+#if 0
+/*
+ * Follow redirection chain for the device.
+ *
+ * (TODO: This feature was for earlier versions of vmix/softoss and not in use for the time being. However
+ * it is possible that some other driver finds use for it in the future).
+ */
+
+ if (!open_excl)
+ {
+ if (mode == OPEN_WRITE)
+ {
+ redirect = adev->redirect_out;
+ }
+ else if (mode == OPEN_READ)
+ {
+ redirect = adev->redirect_in;
+ }
+ else
+ {
+ /*
+ * Read-write open. Check that redirection for both directions are
+ * identical.
+ */
+ if (adev->redirect_out == adev->redirect_in)
+ redirect = adev->redirect_out;
+ }
+ }
+ DDB (cmn_err
+ (CE_CONT, "Redirect=%d (%d, %d)\n", redirect, adev->redirect_out,
+ adev->redirect_in));
+
+ if (redirect >= 0 && redirect < num_audio_engines)
+ {
+ adev = audio_engines[redirect];
+ DDB (cmn_err
+ (CE_CONT, "Redirect devfile %d -> engine=%d\n", dev,
+ adev->engine_num));
+ dev = redirect;
+ }
+#endif
+
+ while (adev != NULL)
+ {
+ DDB (cmn_err (CE_CONT, "Trying engine=%d\n", adev->engine_num));
+ if (!adev->enabled)
+ return OSS_EAGAIN;
+
+ if (adev->unloaded)
+ return OSS_EAGAIN;
+
+ *newdev = adev->engine_num;
+ if ((err =
+ oss_audio_open_engine (adev->engine_num, dev_class, file,
+ recursive, open_flags, newdev)) < 0)
+ {
+ /*
+ * Check if the device was busy and if it has a
+ * shadow device.
+ */
+ if (err != OSS_EBUSY || adev->next_out == NULL)
+ return err;
+ adev = adev->next_out;
+ }
+ else
+ {
+ dev = adev->engine_num;
+ DDB (cmn_err (CE_CONT, "Engine %d opened\n", adev->engine_num));
+ goto done;
+ }
+ }
+
+ return OSS_EBUSY;
+
+done:
+/*
+ * Try to find which minor number matches this /dev/dsp# device.
+ */
+
+ if ((d = oss_find_minor (OSS_DEV_DSP_ENGINE, dev)) < 0)
+ {
+ oss_audio_release (dev, file);
+ return d;
+ }
+
+ *newdev = d;
+ return dev;
+}
+#endif
+
+static struct
+{
+ int sz, nfrags;
+} policies[] =
+{
+ {
+ 8, 2}, /* 0 */
+ {
+ 16, 2}, /* 1 */
+ {
+ 128, 2}, /* 2 */
+ {
+ 256, 4}, /* 3 */
+ {
+ 512, 4}, /* 4 */
+ {
+ 1024, 0}, /* 5 */
+ {
+ 2048, 0}, /* 6 */
+ {
+ 4096, 0}, /* 7 */
+ {
+ 16384, 0}, /* 8 */
+ {
+ 32768, 0}, /* 9 */
+ {
+ 256 *1024, 0}, /* 10 */
+};
+
+static void
+setup_fragments (adev_p adev, dmap_p dmap, int direction)
+{
+ int sz, maxfrags = 100000;
+ int min;
+ extern int max_intrate;
+
+ if (adev->max_intrate == 0 || adev->max_intrate > max_intrate)
+ adev->max_intrate = max_intrate;
+
+ if (dmap->flags & DMAP_FRAGFIXED)
+ return;
+ dmap->flags |= DMAP_FRAGFIXED;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("setup_fragments(%d, dir=%d)", adev->engine_num, direction);
+#endif
+
+ sz = 1024;
+
+ dmap->low_water_rq = 0;
+ dmap->low_water = -1;
+
+ if (dmap->fragsize_rq != 0) /* SNDCTL_DSP_SETFRAGMENT was called */
+ {
+ int v;
+ v = dmap->fragsize_rq & 0xffff;
+
+ sz = (1 << v);
+
+ if (dmap->expand_factor != UNIT_EXPAND)
+ {
+ int sz2;
+
+ sz2 = (sz * dmap->expand_factor) / UNIT_EXPAND;
+
+ if (sz < sz2)
+ {
+ while (sz < sz2)
+ sz *= 2;
+ }
+ else if (sz > sz2)
+ {
+ while (sz > sz2)
+ sz /= 2;
+ }
+
+ }
+
+ if (sz < 8)
+ sz = 8;
+ if (sz > dmap->buffsize / 2)
+ sz = dmap->buffsize / 2;
+
+ maxfrags = (dmap->fragsize_rq >> 16) & 0x7fff;
+
+ if (maxfrags == 0)
+ maxfrags = 0x7fff;
+ else if (maxfrags < 2)
+ maxfrags = 2;
+
+ if (maxfrags < adev->min_fragments)
+ maxfrags = adev->min_fragments;
+ else if (adev->max_fragments >= 2 && maxfrags > adev->max_fragments)
+ maxfrags = adev->max_fragments;
+
+#if 1
+ /*
+ * Workaround for realplay
+ */
+ if (adev->open_flags & OF_SMALLFRAGS)
+ while (sz > dmap->buffsize / 8)
+ {
+ maxfrags *= 2;
+ sz /= 2;
+ }
+#endif
+ }
+
+ if (adev->policy >= 0 && adev->policy <= 10)
+ {
+ sz = policies[adev->policy].sz;
+ maxfrags = policies[adev->policy].nfrags;
+ if (maxfrags == 0) /* Unlimited */
+ maxfrags = 0xffff;
+ }
+
+#if 1
+ /* Use short fragments if ossd has registered this device */
+ if (adev->ossd_registered)
+ sz = 256;
+#endif
+
+ /* Sanity check */
+ if (adev->min_block && adev->max_block)
+ if (adev->min_block > adev->max_block)
+ adev->min_block = adev->max_block = 0;
+
+ if (adev->max_block > 0 && sz > adev->max_block)
+ sz = adev->max_block;
+ if (adev->min_block > 0 && sz < adev->min_block)
+ sz = adev->min_block;
+
+ if (sz < 8)
+ sz = 8;
+
+ /* Ensure that the interrupt rate is within the limits */
+
+ min = 0;
+
+ if (adev->max_intrate > 0)
+ {
+ int data_rate;
+
+ data_rate = dmap->data_rate;
+ if (data_rate < 8000)
+ data_rate = adev->hw_parms.rate * 4;
+
+ min = data_rate / adev->max_intrate;
+
+ if (min > dmap->buffsize / 2)
+ min = dmap->buffsize / 2;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Max intrate %d -> min buffer %d", adev->max_intrate,
+ min);
+#endif
+ }
+
+#if 1
+ if (dmap->flags & DMAP_COOKED)
+ if (min < 256)
+ min = 256;
+#endif
+
+ if (adev->max_block && min > adev->max_block)
+ min = adev->max_block;
+
+ if (sz < min)
+ {
+ while (sz < min)
+ sz *= 2;
+ if (adev->max_block > 0 && sz > adev->max_block)
+ sz = adev->max_block;
+ }
+
+ dmap->fragment_size = sz;
+ dmap->nfrags = dmap->buffsize / sz;
+
+ if (dmap == adev->dmap_out)
+ {
+ if (direction == OPEN_WRITE)
+ {
+ if (dmap->nfrags > maxfrags)
+ dmap->nfrags = maxfrags;
+ }
+ }
+
+ if (adev->d->adrv_setup_fragments)
+ {
+ adev->d->adrv_setup_fragments (adev->engine_num, dmap, direction);
+ }
+
+ dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
+
+ if (dmap->nfrags < 2)
+ {
+ dmap->nfrags = 2;
+ dmap->fragment_size = dmap->bytes_in_use / 2;
+ }
+
+ while (dmap->nfrags < adev->min_fragments)
+ {
+ dmap->nfrags *= 2;
+ dmap->fragment_size /= 2;
+ }
+
+ if (adev->max_fragments >= 2)
+ while (dmap->nfrags > adev->max_fragments)
+ {
+ dmap->nfrags /= 2;
+ dmap->fragment_size *= 2;
+ }
+ while (dmap->nfrags < adev->min_fragments)
+ {
+ dmap->nfrags *= 2;
+ dmap->fragment_size /= 2;
+ }
+
+ dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
+
+ dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
+ dmap->low_water_rq = dmap->fragment_size;
+
+#if 1
+ if (dmap->nfrags < 4)
+#endif
+ if (dmap->low_water_rq < dmap->bytes_in_use / 4)
+ dmap->low_water_rq = dmap->bytes_in_use / 4;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("fragsz=%d, nfrags=%d", dmap->fragment_size, dmap->nfrags);
+#endif
+}
+
+static int
+getblksize (adev_p adev)
+{
+ int size = 4096;
+ dmap_p dmap;
+ oss_native_word flags, dflags;
+ dflags = 0;
+
+ MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
+ if (adev->dmask & DMASK_IN)
+ {
+ dmap = adev->dmap_in;
+
+ if (!(dmap->flags & DMAP_FRAGFIXED))
+ {
+ MUTEX_ENTER (dmap->mutex, dflags);
+ setup_fragments (adev, dmap, OPEN_READ);
+ size = dmap->fragment_size;
+ MUTEX_EXIT (dmap->mutex, dflags);
+ }
+ }
+
+ if (adev->dmask & DMASK_OUT)
+ {
+ dmap = adev->dmap_out;
+
+ if (!(dmap->flags & DMAP_FRAGFIXED))
+ {
+ MUTEX_ENTER (dmap->mutex, dflags);
+ setup_fragments (adev, dmap, OPEN_WRITE);
+ size = dmap->fragment_size;
+ MUTEX_EXIT (dmap->mutex, dflags);
+ }
+ }
+ MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
+
+ return size;
+}
+
+/*ARGSUSED*/
+static oss_uint64_t
+get_output_pointer (adev_p adev, dmap_p dmap, int do_adjust)
+{
+ oss_uint64_t ptr;
+
+ if (adev->d->adrv_get_output_pointer == NULL)
+ {
+ return dmap->fragment_counter * dmap->fragment_size;
+ }
+
+ UP_STATUS (STS_PTR);
+ ptr =
+ adev->d->adrv_get_output_pointer (adev->engine_num, dmap,
+ PCM_ENABLE_OUTPUT) & ~7;
+ DOWN_STATUS (STS_PTR);
+
+ return ptr;
+}
+
+static oss_uint64_t
+get_input_pointer (adev_p adev, dmap_p dmap, int do_adjust)
+{
+ oss_uint64_t ptr;
+
+ if (adev->d->adrv_get_input_pointer == NULL)
+ return dmap->fragment_counter * dmap->fragment_size;
+
+ ptr =
+ adev->d->adrv_get_input_pointer (adev->engine_num, dmap,
+ PCM_ENABLE_INPUT) & ~7;
+ if (ptr < dmap->byte_counter)
+ ptr += dmap->bytes_in_use;
+ ptr %= dmap->bytes_in_use;
+
+ if (do_adjust)
+ {
+ oss_uint64_t tmp = dmap->byte_counter;
+
+ tmp = (tmp / dmap->bytes_in_use) * dmap->bytes_in_use;
+ dmap->byte_counter = tmp + ptr;
+ }
+
+ return ptr;
+}
+
+static int
+get_optr (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ count_info *info = (count_info *) arg;
+ oss_native_word flags;
+ oss_uint64_t bytes;
+
+ memset ((char *) info, 0, sizeof (count_info));
+
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT || !(dmap->flags & DMAP_STARTED))
+ return 0;
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+
+ info->ptr = get_output_pointer (adev, dmap, 1);
+ info->blocks = dmap->interrupt_count;
+ dmap->interrupt_count = 0;
+
+ bytes = (dmap->byte_counter / dmap->bytes_in_use) * dmap->bytes_in_use;
+ bytes += info->ptr;
+ if (bytes < dmap->byte_counter)
+ bytes += dmap->bytes_in_use;
+ info->bytes = (unsigned int) bytes;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ /* Adjust for format conversions */
+ info->ptr = (info->ptr * UNIT_EXPAND) / dmap->expand_factor;
+ info->bytes = (info->bytes / dmap->expand_factor) * UNIT_EXPAND;
+ info->bytes &= 0x3fffffff;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("GETOPTR(%d,%d,%d)", info->bytes, info->ptr, info->blocks);
+#endif
+ return 0;
+}
+
+static int
+get_iptr (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ count_info *info = (count_info *) arg;
+ oss_native_word flags;
+ oss_uint64_t bytes;
+
+ memset ((char *) info, 0, sizeof (count_info));
+
+ if (dmap->dma_mode != PCM_ENABLE_INPUT || !(dmap->flags & DMAP_STARTED))
+ return 0;
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ info->ptr = get_input_pointer (adev, dmap, 1);
+ info->blocks = dmap->interrupt_count;
+ dmap->interrupt_count = 0;
+
+ bytes = (dmap->byte_counter / dmap->bytes_in_use) * dmap->bytes_in_use;
+ bytes += info->ptr;
+ if (bytes < dmap->byte_counter)
+ bytes += dmap->bytes_in_use;
+ info->bytes = (unsigned int) bytes;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ /* Adjust for format conversions */
+ info->ptr = (info->ptr * UNIT_EXPAND) / dmap->expand_factor;
+ info->bytes = (info->bytes / dmap->expand_factor) * UNIT_EXPAND;
+ info->bytes &= 0x3fffffff;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("GETIPTR(%d,%d,%d)", info->bytes, info->ptr, info->blocks);
+#endif
+ return 0;
+}
+
+#ifndef OSS_NO_LONG_LONG
+static int
+get_long_optr (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ oss_count_t *ptr = (oss_count_t *) arg;
+ oss_native_word flags;
+ oss_uint64_t pos, bytes;
+
+ memset ((char *) ptr, 0, sizeof (*ptr));
+
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT || !(dmap->flags & DMAP_STARTED))
+ return 0;
+
+ ptr->fifo_samples = 0;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ if (dmap->frame_size < 1)
+ dmap->frame_size = 1;
+ if (dmap->user_frame_size < 1)
+ dmap->user_frame_size = 1;
+ pos = get_output_pointer (adev, dmap, 1);
+ bytes = (dmap->byte_counter / dmap->bytes_in_use) * dmap->bytes_in_use;
+ bytes += pos;
+ ptr->samples = (unsigned int) (bytes / dmap->frame_size);
+
+ /* Adjust for format conversions */
+ ptr->samples = (ptr->samples * UNIT_EXPAND) / dmap->expand_factor;
+
+ if (adev->d->adrv_local_qlen)
+ {
+ ptr->fifo_samples =
+ adev->d->adrv_local_qlen (adev->engine_num) / dmap->frame_size;
+ ptr->fifo_samples =
+ (ptr->fifo_samples * UNIT_EXPAND) / dmap->expand_factor;
+ }
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return 0;
+}
+
+static int
+get_long_iptr (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ oss_count_t *ptr = (oss_count_t *) arg;
+ oss_native_word flags;
+ oss_uint64_t pos, bytes;
+
+ memset ((char *) ptr, 0, sizeof (*ptr));
+
+ if (dmap->dma_mode != PCM_ENABLE_INPUT || !(dmap->flags & DMAP_STARTED))
+ return 0;
+
+ ptr->fifo_samples = 0;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ if (dmap->frame_size < 1)
+ dmap->frame_size = 1;
+ if (dmap->user_frame_size < 1)
+ dmap->user_frame_size = 1;
+ pos = get_input_pointer (adev, dmap, 1);
+ bytes = (dmap->byte_counter / dmap->bytes_in_use) * dmap->bytes_in_use;
+ bytes += pos;
+ ptr->samples = (unsigned int) (bytes / dmap->frame_size);
+
+ /* Adjust for format conversions */
+ ptr->samples = (ptr->samples / dmap->expand_factor) * UNIT_EXPAND;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return 0;
+}
+#endif
+
+static int
+get_odelay (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ oss_int64_t val, pos;
+ oss_native_word flags;
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT ||
+ (dmap->mapping_flags & DMA_MAP_MAPPED))
+ return *arg = (0);
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ pos = get_output_pointer (adev, dmap, 1);
+ val = (dmap->byte_counter / dmap->bytes_in_use) * dmap->bytes_in_use;
+ val += pos;
+ val = dmap->user_counter - val;
+ if (val < 0)
+ val = 0;
+ if (val > dmap->bytes_in_use)
+ val = dmap->bytes_in_use;
+
+ if (adev->d->adrv_local_qlen)
+ {
+ val += adev->d->adrv_local_qlen (adev->engine_num);
+ }
+
+ val += dmap->leftover_bytes;
+ val = (val * UNIT_EXPAND) / dmap->expand_factor;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+#ifdef DO_TIMINGS
+ oss_timing_printf ("GETODELAY(%d, %d)", adev->engine_num, val);
+#endif
+ return *arg = (val);
+}
+
+static int audio_space_in_queue (adev_p adev, dmap_p dmap, int count);
+
+static int
+get_ospace (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ audio_buf_info *info = (audio_buf_info *) arg;
+ oss_native_word flags;
+
+ memset ((char *) info, 0, sizeof (audio_buf_info));
+
+ if (!(dmap->flags & DMAP_PREPARED))
+ {
+ setup_fragments (adev, dmap, OPEN_WRITE);
+ prepare_output (adev, dmap);
+ }
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ info->fragstotal = dmap->nfrags;
+
+ info->fragsize = (dmap->fragment_size * UNIT_EXPAND) / dmap->expand_factor;
+
+ /* Make sure we report full samples */
+ info->fragsize =
+ ((info->fragsize + dmap->user_frame_size -
+ 1) / dmap->user_frame_size) * dmap->user_frame_size;
+
+ if (!(adev->open_mode & OPEN_WRITE))
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ cmn_err (CE_WARN,
+ "SNDCTL_DSP_GETOSPACE cannot be called in read-only mode.\n");
+#ifdef DO_TIMINGS
+ oss_do_timing ("GETOSPACE: Bad access mode - return EACCES");
+#endif
+ return OSS_EACCES;
+ }
+
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ cmn_err (CE_WARN,
+ "SNDCTL_DSP_GETOSPACE cannot be called in mmap mode.\n");
+#ifdef DO_TIMINGS
+ oss_do_timing ("GETOSPACE: mmap access mode - return EPERM");
+#endif
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1000, "GETOSPACE called in mmap mode"), 0);
+ /* Errordesc:
+ * Do not call SNDCTL_DSP_GETOSPACE in mmap mode. Use SNDCTL_DSP_GETOPTR
+ * instead. SNDCTL_DSP_GETOSPACE is defined only for applications that
+ * use the normal write() method.
+ * Applications that use mmap can call SNDCTL_DSP_GETOSPACE before calling
+ * mmap to get the actual buffer size.
+ */
+ return OSS_EPERM;
+ }
+
+ if (!(dmap->flags & DMAP_STARTED))
+ {
+ int bytes;
+ bytes = (int) ((long long) dmap->bytes_in_use - dmap->user_counter);
+#ifdef DO_TIMINGS
+ {
+ oss_do_timing ("GETOSPACE: Not started - ignore device count");
+ oss_timing_printf ("bytes_in_use=%d", dmap->bytes_in_use);
+ oss_timing_printf ("user_counter=%lld", dmap->user_counter);
+ oss_timing_printf ("raw bytes=%d", bytes);
+ }
+#endif
+ bytes = (bytes / dmap->expand_factor) * UNIT_EXPAND; /* Round downwards */
+ bytes = (bytes / dmap->user_frame_size) * dmap->user_frame_size; /* Truncate to frame size */
+ info->bytes = bytes;
+ info->fragments = info->fragstotal = dmap->nfrags;
+ if (info->bytes > info->fragsize * info->fragstotal)
+ info->bytes = info->fragsize * info->fragstotal;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return 0;
+ }
+
+ info->bytes = audio_space_in_queue (adev, dmap, 0);
+#ifdef DO_TIMINGS
+ oss_timing_printf ("audio_space_in_queue returned %d", info->bytes);
+#endif
+
+ if (info->bytes < dmap->low_water)
+ info->bytes = 0;
+
+ if (adev->dmap_out->flags & DMAP_COOKED)
+ {
+
+ if (dmap->tmpbuf_ptr > 0)
+ info->bytes -= dmap->tmpbuf_ptr;
+ }
+
+ if ((adev->dmap_out->flags & DMAP_COOKED) && info->bytes <
+ dmap->fragment_size / 2)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("GETOSPACE: Buffer full");
+#endif
+ info->bytes = 0;
+ }
+
+ if (info->bytes < 0)
+ info->bytes = 0;
+
+ info->bytes = (info->bytes * UNIT_EXPAND) / dmap->expand_factor;
+ info->bytes = (info->bytes / dmap->user_frame_size) * dmap->user_frame_size; /* Truncate to frame size */
+ if (dmap->flags & DMAP_COOKED)
+ {
+ /*
+ * Reserve some space for format conversions. Sample rate conversions
+ * may not always be able to take the "last" sample with fractional conversion
+ * ratios.
+ */
+ if (info->bytes >= dmap->frame_size)
+ info->bytes -= dmap->frame_size; /* Substract one sample. */
+ }
+ info->fragments = info->bytes / info->fragsize;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ if (info->bytes > info->fragsize * info->fragstotal)
+ info->bytes = info->fragsize * info->fragstotal;
+
+ return 0;
+}
+
+static int
+get_ispace (adev_p adev, dmap_p dmap, ioctl_arg arg)
+{
+ audio_buf_info *info = (audio_buf_info *) arg;
+ oss_native_word flags;
+
+ memset ((char *) info, 0, sizeof (audio_buf_info));
+
+ setup_fragments (adev, dmap, OPEN_READ);
+ prepare_input (adev, dmap);
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ info->fragstotal = dmap->nfrags;
+ info->fragsize = (dmap->fragment_size * UNIT_EXPAND) / dmap->expand_factor;
+
+ /* Make sure we report full samples */
+ info->fragsize =
+ ((info->fragsize + dmap->user_frame_size -
+ 1) / dmap->user_frame_size) * dmap->user_frame_size;
+
+ if (!(adev->open_mode & OPEN_READ))
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ cmn_err (CE_WARN,
+ "SNDCTL_DSP_GETISPACE cannot be called in write-only mode.\n");
+ return OSS_EACCES;
+ }
+
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ cmn_err (CE_WARN,
+ "SNDCTL_DSP_GETISPACE cannot be called in mmap mode.\n");
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1003, "GETISPACE called in mmap mode"), 0);
+ /* Errordesc:
+ * Do not call SNDCTL_DSP_GETISPACE in mmap mode.
+ * SNDCTL_DSP_GETISPACE is defined only for applications that
+ * use the normal write() method.
+ * Applications that use mmap can call SNDCTL_DSP_GETISPACE before calling
+ * mmap to get the actual buffer size.
+ */
+ return OSS_EPERM;
+ }
+
+ if (!(dmap->flags & DMAP_STARTED))
+ {
+/*
+ * A stupid application has called GETISPACE before recording has started.
+ * The right behaviour would definitely be returning bytes=0. However
+ * reporting one ready fragment will keep poor programmers happy.
+ * After all this is a minor error because no properly written application
+ * should ever call GETISPACE before starting recording.
+ */
+ info->bytes = info->fragsize;
+ info->fragments = 1;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if (info->bytes > info->fragsize * info->fragstotal)
+ info->bytes = info->fragsize * info->fragstotal;
+ if (adev->getispace_error_count++ == 1) /* Second time this happens */
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1004,
+ "GETISPACE called before recording has been started"),
+ 0);
+ /*
+ * Errordesc:
+ * There is no recoded data available before recording has been started.
+ * This would result in situation where the application waits infinitely
+ * for recorded data that never arrives. As a workaround
+ * SNDCTL_DSP_GETISPACE will fake that one fragment is already available
+ * to read. This in turn makes the application to block incorrectly.
+ *
+ * Applications must start recording before calling SNDCTL_DSP_GETISPACE
+ * if they are trying to avoid blocking. This can be done by calling
+ * SNDCTL_DSP_SETTRIGGER.
+ *
+ * However applications going to use mmap can/should call
+ * SNDCTL_DSP_GETISPACE in the beginning to find out how large buffer to
+ * map. In such case this event is a false alarm.
+ */
+ }
+ return 0;
+ }
+
+ info->bytes = (unsigned int) (dmap->byte_counter - dmap->user_counter);
+
+ if (dmap->flags & DMAP_COOKED)
+ {
+ /* Count the already converted bytes in the tmp buffer */
+ int nn = dmap->tmpbuf_len - dmap->tmpbuf_ptr;
+
+ if (nn > 0)
+ info->bytes += nn;
+ }
+
+ if (info->bytes > dmap->bytes_in_use)
+ info->bytes = dmap->bytes_in_use;
+
+ info->bytes = (info->bytes * UNIT_EXPAND) / dmap->expand_factor;
+ info->bytes = (info->bytes / dmap->user_frame_size) * dmap->user_frame_size;
+ info->fragments = info->bytes / info->fragsize;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ if (info->bytes > info->fragsize * info->fragstotal)
+ info->bytes = info->fragsize * info->fragstotal;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("GETISPACE(%d,%d,%d,%d)", info->bytes, info->fragments,
+ info->fragsize, info->fragstotal);
+#endif
+ return 0;
+}
+
+#define xrand(seed) (seed=1664525*seed+1013904223)
+
+static void
+setfragment_error (int dev)
+{
+ if (audio_engines[dev]->setfragment_warned)
+ return;
+
+ oss_audio_set_error (dev, E_PLAY,
+ OSSERR (1011,
+ "SNDCTL_DSP_SETFRAGMENT was called too late."),
+ 0);
+ /*
+ * Errordesc: The SNDCTL_DSP_SETFRAGMENT call is only valid immediately after
+ * opening the device. It can only be called once without reopening
+ * the audio device.
+ *
+ * Calling read/write or certain ioctl calls will lock the fragment size/count
+ * to some values which makes changing it impossible.
+ */
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Setfragment called twice");
+#endif
+
+ audio_engines[dev]->setfragment_warned = 1;
+}
+
+static int
+handle_syncgroup (adev_p adev, oss_syncgroup * group)
+{
+ int id, sync_dev;
+ adev_p sync_adev;
+
+ if (adev->sync_group != 0)
+ {
+ return OSS_EBUSY;
+ }
+
+ if (group->id == 0)
+ {
+ if (adev->engine_num > SYNC_DEVICE_MASK)
+ {
+ cmn_err (CE_WARN, "Bad device number %d\n", adev->engine_num);
+ return OSS_EIO;
+ }
+
+ id = xrand (sync_seed) & ~SYNC_DEVICE_MASK; /* Clear the engine number field */
+ id |= adev->engine_num;
+
+ group->id = id;
+ sync_dev = adev->engine_num;
+ adev->sync_next = NULL;
+ }
+ else
+ {
+ id = group->id;
+ sync_dev = id & SYNC_DEVICE_MASK;
+ }
+
+ if (sync_dev < 0 || sync_dev >= num_audio_engines)
+ {
+ group->id = 0;
+ return OSS_EINVAL;
+ }
+
+ sync_adev = audio_engines[sync_dev];
+
+ if (sync_dev == adev->engine_num)
+ adev->sync_flags |= SYNC_MASTER;
+ else
+ {
+ if (!(sync_adev->sync_flags & SYNC_MASTER))
+ {
+ return OSS_EINVAL;
+ }
+
+ if (sync_adev->sync_group != id)
+ {
+ return OSS_EINVAL;
+ }
+
+ adev->sync_flags |= SYNC_SLAVE;
+ }
+
+ group->mode &= (PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT);
+ group->mode &= adev->open_mode;
+ if (group->mode == 0)
+ {
+ adev->sync_flags = 0;
+ return OSS_EINVAL;
+ }
+
+ adev->sync_mode = group->mode;
+ adev->sync_group = id;
+ if (adev->d->adrv_sync_control != NULL)
+ adev->d->adrv_sync_control (adev->engine_num, SYNC_ATTACH,
+ adev->sync_mode);
+
+ if (adev != sync_adev)
+ {
+ adev->sync_next = sync_adev->sync_next;
+ sync_adev->sync_next = adev;
+ }
+
+ adev->go = 0;
+
+ return 0;
+}
+
+static int
+handle_syncstart (int orig_dev, int group)
+{
+ int master_dev, arg;
+ adev_p adev, next;
+
+ master_dev = group & SYNC_DEVICE_MASK;
+ if (master_dev < 0 || master_dev >= num_audio_engines)
+ return OSS_EINVAL;
+
+ adev = audio_engines[master_dev];
+
+ if (!(adev->sync_flags & SYNC_MASTER) ||
+ master_dev != orig_dev)
+ {
+ /*
+ * Potential attack. SYNCSTART was called on wrong file descriptor.
+ */
+ return OSS_EPERM;
+ }
+
+ if (adev->sync_group != group)
+ return OSS_EINVAL;
+
+/*
+ * Pass 1: Inform all devices about the actual start command to come soon.
+ */
+
+ while (adev != NULL)
+ {
+
+ if (adev->sync_group == group)
+ {
+ adev->go = 0;
+
+ if (adev->d->adrv_sync_control)
+ {
+ if (adev->sync_mode & PCM_ENABLE_INPUT)
+ {
+ if (!(adev->dmap_in->flags & DMAP_PREPARED))
+ {
+ adev->dmap_in->dma_mode = PCM_ENABLE_INPUT;
+ prepare_input (adev, adev->dmap_in);
+ }
+ launch_input (adev, adev->dmap_in);
+ }
+ if (adev->sync_mode & PCM_ENABLE_OUTPUT)
+ {
+ dmap_p dmap = adev->dmap_out;
+ int err;
+
+ if (adev->dmap_out->mapping_flags & DMA_MAP_MAPPED)
+ dmap->dma_mode = PCM_ENABLE_OUTPUT;
+ if ((err = prepare_output (adev, adev->dmap_out)) < 0)
+ return err;
+ launch_output (adev, adev->dmap_out);
+ }
+ adev->d->adrv_sync_control (adev->engine_num, SYNC_PREPARE,
+ adev->sync_mode);
+ }
+ else
+ {
+ arg = 0;
+ oss_audio_ioctl (adev->engine_num, NULL, SNDCTL_DSP_SETTRIGGER,
+ (ioctl_arg) & arg);
+ }
+ }
+ else
+ {
+ cmn_err (CE_NOTE, "Broken sync chain\n");
+ }
+
+ adev = adev->sync_next;
+ }
+
+/*
+ * Pass 2: Deliver the actual start commands.
+ */
+
+ adev = audio_engines[master_dev];
+
+ while (adev != NULL)
+ {
+
+ if (adev->sync_group == group)
+ {
+ adev->go = 1;
+
+ if (adev->d->adrv_sync_control)
+ adev->d->adrv_sync_control (adev->engine_num, SYNC_TRIGGER,
+ adev->sync_mode);
+ else
+ {
+ arg = adev->sync_mode;
+ oss_audio_ioctl (adev->engine_num, NULL, SNDCTL_DSP_SETTRIGGER,
+ (ioctl_arg) & arg);
+ }
+ }
+ else
+ {
+ /* Skip this one */
+ adev = adev->sync_next;
+ continue;
+ }
+
+ next = adev->sync_next;
+ adev->sync_next = NULL;
+ adev->sync_flags = 0;
+ adev->sync_group = 0;
+
+ adev = next;
+ }
+ return 0;
+}
+
+#ifdef DO_TIMINGS
+static char *
+find_ioctl_name (unsigned int cmd, ioctl_arg val)
+{
+ static char tmp[32];
+
+ typedef struct
+ {
+ unsigned int code;
+ char *name;
+ int flags;
+#define IOF_DEC 0x00000001
+#define IOF_HEX 0x00000002
+ } ioctl_def_t;
+
+ static ioctl_def_t call_names[] = {
+ {SNDCTL_DSP_HALT, "SNDCTL_DSP_HALT"},
+ {SNDCTL_DSP_SYNC, "SNDCTL_DSP_SYNC"},
+ {SNDCTL_DSP_SPEED, "SNDCTL_DSP_SPEED", IOF_DEC},
+ {SNDCTL_DSP_STEREO, "SNDCTL_DSP_STEREO", IOF_DEC},
+ {SNDCTL_DSP_GETBLKSIZE, "SNDCTL_DSP_GETBLKSIZE"},
+ {SNDCTL_DSP_CHANNELS, "SNDCTL_DSP_CHANNELS", IOF_DEC},
+ {SNDCTL_DSP_POST, "SNDCTL_DSP_POST"},
+ {SNDCTL_DSP_SUBDIVIDE, "SNDCTL_DSP_SUBDIVIDE"},
+ {SNDCTL_DSP_SETFRAGMENT, "SNDCTL_DSP_SETFRAGMENT", IOF_HEX},
+ {SNDCTL_DSP_GETFMTS, "SNDCTL_DSP_GETFMTS"},
+ {SNDCTL_DSP_SETFMT, "SNDCTL_DSP_SETFMT", IOF_HEX},
+ {SNDCTL_DSP_GETOSPACE, "SNDCTL_DSP_GETOSPACE"},
+ {SNDCTL_DSP_GETISPACE, "SNDCTL_DSP_GETISPACE"},
+ {SNDCTL_DSP_GETCAPS, "SNDCTL_DSP_GETCAPS"},
+ {SNDCTL_DSP_GETTRIGGER, "SNDCTL_DSP_GETTRIGGER"},
+ {SNDCTL_DSP_SETTRIGGER, "SNDCTL_DSP_SETTRIGGER", IOF_HEX},
+ {SNDCTL_DSP_GETIPTR, "SNDCTL_DSP_GETIPTR"},
+ {SNDCTL_DSP_GETOPTR, "SNDCTL_DSP_GETOPTR"},
+ {SNDCTL_DSP_SETSYNCRO, "SNDCTL_DSP_SETSYNCRO", IOF_HEX},
+ {SNDCTL_DSP_SETDUPLEX, "SNDCTL_DSP_SETDUPLEX"},
+ {SNDCTL_DSP_GETODELAY, "SNDCTL_DSP_GETODELAY"},
+ {SNDCTL_DSP_GETPLAYVOL, "SNDCTL_DSP_GETPLAYVOL"},
+ {SNDCTL_DSP_SETPLAYVOL, "SNDCTL_DSP_SETPLAYVOL", IOF_HEX},
+ {SNDCTL_DSP_GETRECVOL, "SNDCTL_DSP_GETRECVOL"},
+ {SNDCTL_DSP_SETRECVOL, "SNDCTL_DSP_SETRECVOL", IOF_HEX},
+ {SNDCTL_DSP_GETERROR, "SNDCTL_DSP_GETERROR"},
+ {SNDCTL_DSP_READCTL, "SNDCTL_DSP_READCTL"},
+ {SNDCTL_DSP_WRITECTL, "SNDCTL_DSP_WRITECTL"},
+ {SNDCTL_DSP_SYNCGROUP, "SNDCTL_DSP_SYNCGROUP"},
+ {SNDCTL_DSP_SYNCSTART, "SNDCTL_DSP_SYNCSTART"},
+ {SNDCTL_DSP_COOKEDMODE, "SNDCTL_DSP_COOKEDMODE", IOF_DEC},
+ {SNDCTL_DSP_SILENCE, "SNDCTL_DSP_SILENCE"},
+ {SNDCTL_DSP_SKIP, "SNDCTL_DSP_SKIP"},
+ {SNDCTL_DSP_GETCHANNELMASK, "SNDCTL_DSP_GETCHANNELMASK"},
+ {SNDCTL_DSP_BIND_CHANNEL, "SNDCTL_DSP_BIND_CHANNEL"},
+ {SNDCTL_DSP_HALT_INPUT, "SNDCTL_DSP_HALT_INPUT"},
+ {SNDCTL_DSP_HALT_OUTPUT, "SNDCTL_DSP_HALT_OUTPUT"},
+ {SNDCTL_DSP_LOW_WATER, "SNDCTL_DSP_LOW_WATER"},
+#ifndef OSS_NO_LONG_LONG
+ {SNDCTL_DSP_CURRENT_IPTR, "SNDCTL_DSP_CURRENT_IPTR"},
+ {SNDCTL_DSP_CURRENT_OPTR, "SNDCTL_DSP_CURRENT_OPTR"},
+#endif
+ {SNDCTL_DSP_GET_RECSRC, "SNDCTL_DSP_GET_RECSRC"},
+ {SNDCTL_DSP_SET_RECSRC, "SNDCTL_DSP_SET_RECSRC"},
+ {SNDCTL_DSP_GET_RECSRC_NAMES, "SNDCTL_DSP_GET_RECSRC_NAMES"},
+ {SNDCTL_DSP_GET_PLAYTGT, "SNDCTL_DSP_GET_PLAYTGT"},
+ {SNDCTL_DSP_SET_PLAYTGT, "SNDCTL_DSP_SET_PLAYTGT"},
+ {SNDCTL_DSP_GET_PLAYTGT_NAMES, "SNDCTL_DSP_GET_PLAYTGT_NAMES"},
+ {0, NULL}
+ };
+
+ int i;
+ for (i = 0; call_names[i].code != 0; i++)
+ if (call_names[i].code == cmd)
+ {
+ int flags = call_names[i].flags;
+
+ if (flags & IOF_DEC)
+ {
+ sprintf (tmp, "%s, *%d", call_names[i].name, *val);
+ return tmp;
+ }
+
+ if (flags & IOF_HEX)
+ {
+ sprintf (tmp, "%s, *0x%08x", call_names[i].name, *val);
+ return tmp;
+ }
+
+ return call_names[i].name;
+ }
+
+ sprintf (tmp, "Unknown %08x", cmd);
+ return tmp;
+}
+#endif
+
+/*ARGSUSED*/
+int
+oss_encode_enum (oss_mixer_enuminfo * ei, const char *s, int version)
+{
+ int n = 1, l;
+ int i;
+
+ memset (ei, 0, sizeof (*ei)); /* Wipe out everything */
+
+ strncpy (ei->strings, s, sizeof (ei->strings) - 1);
+ ei->strings[sizeof (ei->strings) - 1] = 0;
+
+ ei->strindex[0] = 0;
+
+ l = strlen (ei->strings);
+ for (i = 0; i < l; i++)
+ {
+ if (ei->strings[i] == ' ')
+ {
+ ei->strindex[n++] = i + 1;
+ ei->strings[i] = 0;
+ }
+ }
+
+ ei->nvalues = n;
+
+ return 0;
+}
+
+static int
+get_legacy_recsrc_names (int dev, oss_mixer_enuminfo * ei)
+{
+ static const char *labels[] = SOUND_DEVICE_NAMES;
+
+ int i, mixer_dev, recmask, devmask, caps, n;
+ char *s;
+
+ if (audio_engines[dev]->mixer_dev < 0) /* No mixer */
+ return 0;
+
+ mixer_dev = audio_engines[dev]->mixer_dev;
+
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_CAPS, (ioctl_arg) & caps) < 0)
+ caps = 0; /* Error */
+
+ if (caps & SOUND_CAP_NORECSRC)
+ return 0;
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_DEVMASK, (ioctl_arg) & devmask) < 0)
+ return 0; /* Error */
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_RECMASK, (ioctl_arg) & recmask) < 0)
+ return 0; /* Error */
+
+ recmask &= devmask;
+ if (recmask == 0)
+ return 0;
+
+ n = 0;
+ s = ei->strings;
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if (recmask & (1 << i)) /* This control is also recording device */
+ {
+ /*LINTED*/ ei->strindex[n] = s - ei->strings;
+
+ strcpy (s, labels[i]);
+ s += strlen (s);
+ *s++ = 0;
+ n++;
+ }
+
+ ei->nvalues = n;
+
+ return (n > 0);
+}
+
+static int
+get_legacy_recsrc (int dev)
+{
+ int i, mixer_dev, recmask, recsrc, caps, n;
+
+ if (audio_engines[dev]->mixer_dev < 0) /* No mixer */
+ return 0;
+
+ mixer_dev = audio_engines[dev]->mixer_dev;
+
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_CAPS, (ioctl_arg) & caps) < 0)
+ caps = 0; /* Error */
+
+ if (caps & SOUND_CAP_NORECSRC)
+ return 0;
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_RECSRC, (ioctl_arg) & recsrc) < 0)
+ return 0; /* Error */
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_RECMASK, (ioctl_arg) & recmask) < 0)
+ return 0; /* Error */
+
+ if (recmask == 0 || recsrc == 0)
+ return 0;
+
+ n = 0;
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if (recmask & (1 << i)) /* This control is also recording device */
+ {
+ if (recsrc & (1 << i)) /* It was this one */
+ return n;
+ n++;
+ }
+
+
+ return 0;
+}
+
+static int
+set_legacy_recsrc (int dev, int val)
+{
+ int i, mixer_dev, recmask, recsrc, caps, n;
+
+ if (audio_engines[dev]->mixer_dev < 0) /* No mixer */
+ return 0;
+
+ mixer_dev = audio_engines[dev]->mixer_dev;
+
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_CAPS, (ioctl_arg) & caps) < 0)
+ caps = 0; /* Error */
+
+ if (caps & SOUND_CAP_NORECSRC)
+ return 0;
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_READ_RECMASK, (ioctl_arg) & recmask) < 0)
+ return 0; /* Error */
+
+ if (recmask == 0)
+ return 0;
+
+ n = 0;
+
+ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+ if (recmask & (1 << i)) /* This control is also recording device */
+ {
+ if (n == val)
+ {
+ recsrc = (1 << i);
+
+ if (oss_legacy_mixer_ioctl
+ (mixer_dev, -1, SOUND_MIXER_WRITE_RECSRC,
+ (ioctl_arg) & recsrc) < 0)
+ return 0; /* Error */
+
+ return 1;
+ }
+ n++;
+ }
+
+
+ return 0;
+}
+
+/*ARGSUSED*/
+int
+oss_audio_ioctl (int dev, struct fileinfo *bogus,
+ unsigned int cmd, ioctl_arg arg)
+{
+ int val, err;
+ int mixdev;
+ int ret;
+ adev_p adev;
+ dmap_p dmapin, dmapout;
+ oss_native_word flags;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("oss_audio_ioctl(%d, %s)", dev, find_ioctl_name (cmd, arg));
+#endif
+
+ if (cmd == OSS_GETVERSION)
+ return *arg = OSS_VERSION;
+
+ UP_STATUS (STS_IOCTL);
+ DOWN_STATUS (STS_IOCTL);
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (adev->unloaded)
+ return OSS_ENODEV;
+ if (!adev->enabled)
+ return OSS_ENXIO;
+
+ if (adev->d->adrv_ioctl_override != NULL)
+ {
+ /*
+ * Use the ioctl override function if available. However process the request
+ * in the usual way if the override function returned OSS_EAGAIN. It may
+ * be possible that the override function has modified the parameters
+ * before returning.
+ */
+ if ((err = adev->d->adrv_ioctl_override(dev, cmd, arg)) != OSS_EAGAIN)
+ return err;
+ }
+
+ dmapout = adev->dmap_out;
+ dmapin = adev->dmap_in;
+
+/*
+ * Handle mixer ioctl calls on audio fd.
+ */
+
+ if (cmd == SOUND_MIXER_WRITE_PCM) cmd = SNDCTL_DSP_SETPLAYVOL;
+ if (cmd == SOUND_MIXER_WRITE_RECLEV) cmd = SNDCTL_DSP_SETRECVOL;
+ if (cmd == SOUND_MIXER_READ_PCM) cmd = SNDCTL_DSP_GETPLAYVOL;
+ if (cmd == SOUND_MIXER_READ_RECLEV) cmd = SNDCTL_DSP_GETRECVOL;
+
+ if ((mixdev = adev->mixer_dev) != -1)
+ {
+ if (((cmd >> 8) & 0xff) == 'M' && num_mixers > 0) /* Mixer ioctl */
+ if ((ret = oss_legacy_mixer_ioctl (mixdev, dev, cmd, arg)) != OSS_EINVAL)
+ return ret;
+ }
+
+ if (((cmd >> 8) & 0xff) == 'X') /* Mixer extension API */
+ return oss_mixer_ext (adev->engine_num, OSS_DEV_DSP, cmd, arg);
+
+ switch (cmd)
+ {
+ case SNDCTL_DSP_SYNC:
+ oss_audio_sync (adev);
+ return 0;
+ break;
+
+ case SNDCTL_DSP_POST:
+ oss_audio_post (adev);
+ return 0;
+ break;
+
+ case SNDCTL_DSP_HALT:
+ audio_reset_adev (adev);
+ return 0;
+ break;
+
+ case SNDCTL_DSP_HALT_INPUT:
+ audio_reset_input (adev);
+ return 0;
+ break;
+
+ case SNDCTL_DSP_LOW_WATER:
+ val = *arg;
+ if (adev->open_mode & OPEN_READ)
+ adev->dmap_in->low_water = val;
+ if (adev->open_mode & OPEN_WRITE)
+ adev->dmap_out->low_water = val;
+ return 0;
+ break;
+
+ case SNDCTL_DSP_HALT_OUTPUT:
+ audio_reset_output (adev);
+ return 0;
+ break;
+
+ case SNDCTL_DSP_GETFMTS:
+ switch (adev->open_mode & (OPEN_READ | OPEN_WRITE))
+ {
+ case OPEN_WRITE:
+ return *arg = (adev->oformat_mask);
+ break;
+
+ case OPEN_READ:
+ return *arg = (adev->oformat_mask);
+ break;
+
+ default:
+ return *arg = (adev->xformat_mask);
+ break;
+ }
+ break;
+
+ case SNDCTL_DSP_SETFMT:
+ val = *arg;
+ switch (adev->open_mode & (OPEN_READ | OPEN_WRITE))
+ {
+ case OPEN_WRITE:
+ return *arg = (oss_audio_set_format (dev, val,
+ audio_engines
+ [dev]->oformat_mask));
+ break;
+
+ case OPEN_READ:
+ return *arg = (oss_audio_set_format (dev, val,
+ audio_engines
+ [dev]->iformat_mask));
+ break;
+
+ default:
+ return *arg = (oss_audio_set_format (dev, val,
+ audio_engines
+ [dev]->oformat_mask &
+ audio_engines
+ [dev]->iformat_mask));
+ break;
+ }
+
+ case SNDCTL_DSP_GETOSPACE:
+ if (!(adev->dmask & DMASK_OUT))
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1001,
+ "GETOSPACE called in read-only mode"),
+ 0);
+ /* Errordesc: SNDCTL_DSP_GETOSPACE is not defined in read-only access mode */
+ return OSS_ENOTSUP;
+ }
+ ret = get_ospace (adev, dmapout, arg);
+#ifdef DO_TIMINGS
+ {
+ audio_buf_info *info = (audio_buf_info *) arg;
+ oss_timing_printf ("GETOSPACE(b=%d,f=%d,fsz=%d,ft=%d)=%d", info->bytes,
+ info->fragments, info->fragsize, info->fragstotal, ret);
+ oss_timing_printf ("Low water %d, ap flags=%x, tmpbuf=%d/%d",
+ dmapout->low_water, adev->open_flags, dmapout->tmpbuf_ptr,
+ dmapout->tmpbuf_len);
+ }
+#endif
+ return ret;
+ break;
+
+ case SNDCTL_DSP_GETISPACE:
+ if (!(adev->dmask & DMASK_IN))
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1002,
+ "GETISPACE called in write-only mode"),
+ 0);
+ /*
+ * Errordesc: SNDCTL_DSP_GETISPACE has no defined meaning when the audio
+ * device is opened in write-only mode.
+ */
+ return OSS_ENOTSUP;
+ }
+
+ return get_ispace (adev, dmapin, arg);
+ break;
+
+ case SNDCTL_DSP_GETODELAY:
+ if (!(adev->dmask & DMASK_OUT))
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1005,
+ "GETODELAY called in read-only mode"),
+ 0);
+ return OSS_ENOTSUP;
+ }
+ return get_odelay (adev, dmapout, arg);
+ break;
+
+ case SNDCTL_DSP_SETDUPLEX:
+ /*
+ * Note! SNDCTL_DSP_SETDUPLEX has not been implemented by any driver for years.
+ * The call is still implemented in audio core but it may get removed in the
+ * future.
+ */
+ if (adev->open_mode != OPEN_READWRITE)
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1006,
+ "SETDUPLEX called in non-read/write mode"),
+ 0);
+ return OSS_ENOTSUP;
+ }
+ if (adev->flags & ADEV_DUPLEX)
+ {
+ if (adev->d->adrv_ioctl == NULL)
+ return 0;
+ val = adev->d->adrv_ioctl (dev, cmd, arg);
+ if (val == OSS_EINVAL)
+ return 0;
+ else
+ return val;
+ }
+ else
+ {
+ return OSS_ENOTSUP;
+ }
+ break;
+
+ case SNDCTL_DSP_COOKEDMODE:
+ val = *arg;
+
+ if (adev->flags & ADEV_NONINTERLEAVED)
+ val=1;
+
+ adev->cooked_enable = !!val;
+ if (adev->d->adrv_ioctl != NULL)
+ adev->d->adrv_ioctl (dev, cmd, arg);
+#ifdef DO_TIMINGS
+ if (adev->cooked_enable)
+ oss_do_timing ("Setting cooked mode ON");
+ else
+ oss_do_timing ("Setting cooked mode OFF");
+#endif
+ return 0;
+ break;
+
+ case SNDCTL_DSP_GETCAPS:
+ {
+ int info;
+ info = audio_engines[dev]->caps;
+ info |= 2; /* Revision level of this ioctl() */
+
+#if 0
+ if (!(adev->flags & ADEV_VIRTUAL) && !adev->d->adrv_local_qlen)
+#endif
+ info |= PCM_CAP_REALTIME;
+
+
+ if (!(adev->flags & ADEV_NOINPUT))
+ info |= PCM_CAP_INPUT;
+
+ if (!(adev->flags & ADEV_NOOUTPUT))
+ info |= PCM_CAP_OUTPUT;
+
+ if ((adev->flags & ADEV_VIRTUAL))
+ info |= PCM_CAP_VIRTUAL;
+
+ if (!(adev->flags & ADEV_NOINPUT) && !(adev->flags & ADEV_NOOUTPUT))
+ if (adev->flags & ADEV_DUPLEX && adev->open_mode == OPEN_READWRITE)
+ info |= PCM_CAP_DUPLEX;
+
+ if (dev > 0)
+ if (adev->flags & ADEV_SPECIAL)
+ info |= PCM_CAP_SPECIAL;
+
+ if (dev < num_audio_engines - 1)
+ {
+ if (audio_engines[dev + 1]->flags & ADEV_SHADOW)
+ info |= PCM_CAP_MULTI;
+ }
+
+ if (adev->d->adrv_local_qlen) /* Device has hidden buffers */
+ info |= PCM_CAP_BATCH;
+
+ if (adev->d->adrv_trigger) /* Supports SETTRIGGER */
+ info |= PCM_CAP_TRIGGER;
+
+#ifdef ALLOW_BUFFER_MAPPING
+ info |= PCM_CAP_MMAP;
+#endif
+ if (!(adev->flags & ADEV_NOINPUT))
+ info |= PCM_CAP_INPUT;
+
+ if (!(adev->flags & ADEV_NOOUTPUT))
+ info |= PCM_CAP_OUTPUT;
+
+ if (adev->d->adrv_bind != NULL)
+ info |= PCM_CAP_BIND;
+ /*
+ * TODO: ADEV_DEFAULT is not the right way to find out
+ * PCM_CAP_DEFAULT devices. A new ADEV_ flag should be defined
+ * for this purpose.
+ */
+ if (adev->flags & ADEV_DEFAULT)
+ info |= PCM_CAP_DEFAULT;
+
+ return *arg = (info);
+ }
+ break;
+
+ case SNDCTL_DSP_NONBLOCK:
+ adev->forced_nonblock = 1;
+ return 0;
+ break;
+
+ case SNDCTL_DSP_GETCHANNELMASK:
+ case SNDCTL_DSP_BIND_CHANNEL:
+ if (adev->d->adrv_bind == NULL)
+ return OSS_EINVAL;
+ return adev->d->adrv_bind (adev->engine_num, cmd, arg);
+ break;
+
+ case SNDCTL_DSP_SETTRIGGER:
+ {
+ int val;
+
+ if (!adev->d->adrv_trigger)
+ {
+ cmn_err (CE_NOTE, "Device %d doesn't have trigger capability\n",
+ adev->engine_num);
+ return OSS_EIO;
+ }
+
+ val = *arg;
+ val &= PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
+ val &= adev->open_mode;
+
+ if ((val & adev->open_mode) != (adev->enable_bits & adev->open_mode))
+ {
+ if ((val & PCM_ENABLE_OUTPUT)
+ && !(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ {
+ dmap_p dmap = adev->dmap_out;
+ oss_native_word flags;
+ if (adev->dmap_out->mapping_flags & DMA_MAP_MAPPED)
+ dmap->dma_mode = PCM_ENABLE_OUTPUT;
+ if ((err = prepare_output (adev, adev->dmap_out)) < 0)
+ return err;
+ launch_output (adev, adev->dmap_out);
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ adev->enable_bits &= PCM_ENABLE_OUTPUT;
+ adev->enable_bits |= val & PCM_ENABLE_OUTPUT;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ }
+
+ if ((val & PCM_ENABLE_INPUT)
+ && !(adev->enable_bits & PCM_ENABLE_INPUT))
+ {
+ dmap_p dmap = adev->dmap_in;
+ oss_native_word flags;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ dmap->dma_mode = PCM_ENABLE_INPUT;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if ((err = prepare_input (adev, adev->dmap_in)) < 0)
+ return err;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ launch_input (adev, adev->dmap_in);
+ adev->enable_bits &= PCM_ENABLE_INPUT;
+ adev->enable_bits |= val & PCM_ENABLE_INPUT;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ }
+
+ adev->enable_bits = val;
+ if (adev->enable_bits == 0 || adev->go) /* Device is enabled */
+ {
+ adev->d->adrv_trigger (adev->engine_num, adev->enable_bits);
+ }
+ }
+ *arg = val;
+ return 0;
+ }
+ break;
+
+ case SNDCTL_DSP_GETTRIGGER:
+ if (!adev->d->adrv_trigger)
+ {
+ cmn_err (CE_NOTE, "Device %d doesn't have trigger capability\n",
+ adev->engine_num);
+ return OSS_EIO;
+ }
+ return *arg = (adev->enable_bits & adev->open_mode);
+ break;
+
+ case SNDCTL_DSP_SETSYNCRO:
+ adev->go = 0;
+ break;
+
+ case SNDCTL_DSP_SYNCGROUP:
+ return handle_syncgroup (adev, (oss_syncgroup *) arg);
+ break;
+
+ case SNDCTL_DSP_SYNCSTART:
+ val = *arg;
+
+ MUTEX_ENTER_IRQDISABLE (audio_global_mutex, flags);
+ ret = handle_syncstart (adev->engine_num, val);
+ MUTEX_EXIT_IRQRESTORE (audio_global_mutex, flags);
+
+ return ret;
+ break;
+
+ case SNDCTL_DSP_GETERROR:
+ {
+ audio_errinfo *info = (audio_errinfo *) arg;
+
+ memset ((void *) info, 0, sizeof (*info));
+
+ //if (audio_engines[dev]->open_mode & OPEN_READ)
+ {
+ dmap_t *dmap = audio_engines[dev]->dmap_in;
+
+ info->rec_overruns = dmap->rec_overruns;
+ dmap->rec_overruns = 0;
+ info->rec_ptradjust = 0;
+ info->rec_errorcount = dmap->num_errors;
+ info->rec_lasterror = dmap->errors[0];
+ info->rec_errorparm = dmap->error_parms[0];
+ }
+
+ //if (audio_engines[dev]->open_mode & OPEN_WRITE)
+ {
+ dmap_t *dmap = audio_engines[dev]->dmap_out;
+
+ info->play_underruns = dmap->play_underruns;
+ dmap->play_underruns = 0;
+ info->play_ptradjust = 0;
+ info->play_errorcount = dmap->num_errors;
+ info->play_lasterror = dmap->errors[0];
+ info->play_errorparm = dmap->error_parms[0];
+ }
+
+ return 0;
+ }
+ break;
+
+ case SNDCTL_DSP_GETPLAYVOL:
+ case SNDCTL_DSP_SETPLAYVOL:
+ {
+ int err, mixdev;
+
+ mixdev = adev->mixer_dev;
+ if (cmd == SNDCTL_DSP_SETPLAYVOL && mixdev >= 0
+ && mixdev < num_mixers)
+ mixer_devs[mixdev]->modify_counter++;
+
+ if (adev->d->adrv_ioctl == NULL
+ || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL)
+ {
+ /* Emulate these calls using mixer API */
+
+ if (mixdev < 0 || mixdev >= num_mixers)
+ return OSS_EINVAL;
+
+ if (cmd == SNDCTL_DSP_GETPLAYVOL)
+ cmd = SOUND_MIXER_READ_PCM;
+ else
+ cmd = SOUND_MIXER_WRITE_PCM;
+ return mixer_devs[mixdev]->d->ioctl (mixdev, dev, cmd, arg);
+ }
+ return err;
+ }
+ break;
+
+ case SNDCTL_DSP_GETRECVOL:
+ case SNDCTL_DSP_SETRECVOL:
+ {
+ int err, mixdev;
+
+ mixdev = adev->mixer_dev;
+ if (cmd == SNDCTL_DSP_SETRECVOL && mixdev >= 0 && mixdev < num_mixers)
+ mixer_devs[mixdev]->modify_counter++;
+
+ if (adev->d->adrv_ioctl == NULL
+ || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL)
+ {
+ /* Emulate these calls using mixer API */
+
+ if (mixdev < 0 || mixdev >= num_mixers)
+ return OSS_EINVAL;
+
+ /* Try with RECGAIN */
+ if (cmd == SNDCTL_DSP_GETRECVOL)
+ cmd = SOUND_MIXER_READ_RECGAIN;
+ else
+ cmd = SOUND_MIXER_WRITE_RECGAIN;
+ err = mixer_devs[mixdev]->d->ioctl (mixdev, dev, cmd, arg);
+
+ /* Try with RECLEV */
+ if (cmd == SNDCTL_DSP_GETRECVOL)
+ cmd = SOUND_MIXER_READ_RECLEV;
+ else
+ cmd = SOUND_MIXER_WRITE_RECLEV;
+ err = mixer_devs[mixdev]->d->ioctl (mixdev, dev, cmd, arg);
+
+ if (err >= 0) /* Was OK */
+ return err;
+
+ /* Try with IGAIN */
+ if (cmd == SNDCTL_DSP_GETRECVOL)
+ cmd = SOUND_MIXER_READ_IGAIN;
+ else
+ cmd = SOUND_MIXER_WRITE_IGAIN;
+ return mixer_devs[mixdev]->d->ioctl (mixdev, dev, cmd, arg);
+ }
+ return err;
+ }
+ break;
+
+ case SNDCTL_DSP_GETOPTR:
+ if (!(adev->dmask & DMASK_OUT))
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1007,
+ "GETOPTR called in read-only mode"),
+ 0);
+ return OSS_ENOTSUP;
+ }
+ return get_optr (adev, dmapout, arg);
+ break;
+
+ case SNDCTL_DSP_GETIPTR:
+ if (!(adev->dmask & DMASK_IN))
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1008,
+ "GETIPTR called in write-only mode"),
+ 0);
+ return OSS_ENOTSUP;
+ }
+ return get_iptr (adev, dmapin, arg);
+ break;
+
+#ifndef OSS_NO_LONG_LONG
+ case SNDCTL_DSP_CURRENT_OPTR:
+ if (!(adev->dmask & DMASK_OUT))
+ return OSS_ENOTSUP;
+ return get_long_optr (adev, dmapout, arg);
+ break;
+
+ case SNDCTL_DSP_CURRENT_IPTR:
+ if (!(adev->dmask & DMASK_IN))
+ return OSS_ENOTSUP;
+ return get_long_iptr (adev, dmapin, arg);
+ break;
+#endif
+
+ case SNDCTL_DSP_POLICY:
+ val = *arg;
+ if (val < 0 || val > 10)
+ return OSS_EIO;
+ adev->policy = val;
+ return 0;
+ break;
+
+ case SNDCTL_DSP_SETFRAGMENT:
+ case SNDCTL_DSP_SUBDIVIDE:
+ if (cmd == SNDCTL_DSP_SUBDIVIDE)
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1010,
+ "SNDCTL_DSP_SUBDIVIDE is obsolete"),
+ 0);
+ /*
+ * Errordesc:
+ * SNDCTL_DSP_SUBDIVIDE is obsolete. It's still emulated by OSS but
+ * the result is not precise. You need to use SNDCTL_DSP_SETFRAGMENT
+ * instead.
+ */
+ *arg = 0x00040008; /* Default to 4 fragments of 256 bytes */
+ }
+
+ val = *arg;
+ if (adev->dmask & DMASK_OUT)
+ {
+ if (dmapout->flags & DMAP_FRAGFIXED)
+ setfragment_error (dev);
+ dmapout->fragsize_rq = val;
+ }
+ if (adev->dmask & DMASK_IN)
+ {
+ if (dmapin->flags & DMAP_FRAGFIXED)
+ setfragment_error (dev);
+ dmapin->fragsize_rq = val;
+ }
+ return 0;
+
+#ifdef __FreeBSD__
+ case FREEBSD_GETBLKSIZE:
+#endif
+ case SNDCTL_DSP_GETBLKSIZE:
+ return *arg = getblksize (adev);
+
+ case SNDCTL_DSP_SPEED:
+ val = *arg;
+ if (val<0)
+ return OSS_EINVAL;
+ return *arg = (oss_audio_set_rate (dev, val));
+
+ case SNDCTL_DSP_STEREO:
+ {
+ int n, v;
+
+ n = *arg;
+ if (n > 1)
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1009,
+ "SNDCTL_DSP_STEREO called with bad agrument value"),
+ n);
+ /*
+ * Errordesc: SNDCTL_DSP_STEREO is an obsolete ioctl call that
+ * supports only mono (0) or stereo (1). For larger number of channels
+ * you need to use SNDCTL_DSP_CHANNELS instead.
+ */
+ return OSS_EINVAL;
+ }
+
+ if (n < 0)
+ return OSS_EINVAL;
+
+ v = oss_audio_set_channels (dev, n + 1);
+ return *arg = (v - 1);
+ }
+
+ case SNDCTL_DSP_CHANNELS:
+ {
+ int v;
+ val = *arg;
+#ifdef DO_TIMINGS
+ {
+ char tmp[128];
+
+ sprintf (tmp, "Set channels %d", (int) val);
+ oss_do_timing2 (DFLAG_PROFILE, tmp);
+ }
+#endif
+ if (val<0)
+ {
+ return OSS_EINVAL;
+ }
+ v = oss_audio_set_channels (dev, val);
+ return *arg = v;
+ }
+
+ case SNDCTL_DSP_PROFILE: /* Obsolete */
+ return 0;
+ break;
+
+ case SNDCTL_DSP_SILENCE:
+ memset (dmapout->dmabuf, dmapout->neutral_byte, dmapout->buffsize);
+ return 0;
+ break;
+
+ case SNDCTL_DSP_SKIP:
+ return 0;
+ break;
+
+ case SNDCTL_DSP_GET_RECSRC_NAMES:
+ if (adev->d->adrv_ioctl == NULL || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL) /* Not handled */
+ {
+ oss_mixer_enuminfo *ei = (oss_mixer_enuminfo *) arg;
+
+ memset (ei, 0, sizeof (*ei)); /* Wipe out everything */
+
+ if (get_legacy_recsrc_names (dev, ei))
+ return 0;
+ ei->nvalues = 1;
+ strcpy (ei->strings, "default");
+ return 0;
+ }
+ return err;
+ break;
+
+ case SNDCTL_DSP_GET_RECSRC:
+ if (adev->d->adrv_ioctl == NULL || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL) /* Not handled */
+ {
+ return *arg = (get_legacy_recsrc (dev));
+ }
+ return err;
+ break;
+
+ case SNDCTL_DSP_SET_RECSRC:
+ if (adev->d->adrv_ioctl == NULL || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL) /* Not handled */
+ {
+ val = *arg;
+ set_legacy_recsrc (dev, val);
+ return *arg = (get_legacy_recsrc (dev));
+ }
+ return err;
+ break;
+
+ case SNDCTL_DSP_GET_PLAYTGT_NAMES:
+ if (adev->d->adrv_ioctl == NULL || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL) /* Not handled */
+ {
+ oss_mixer_enuminfo *ei = (oss_mixer_enuminfo *) arg;
+ memset (ei, 0, sizeof (*ei));
+ ei->nvalues = 1;
+ strcpy (ei->strings, "default");
+ return 0;
+ }
+ return err;
+ break;
+
+ case SNDCTL_DSP_GET_PLAYTGT:
+ if (adev->d->adrv_ioctl == NULL || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL) /* Not handled */
+ {
+ return *arg = (0);
+ }
+ return err;
+ break;
+
+ case SNDCTL_DSP_SET_PLAYTGT:
+ if (adev->d->adrv_ioctl == NULL || (err = adev->d->adrv_ioctl (dev, cmd, arg)) == OSS_EINVAL) /* Not handled */
+ {
+ return *arg = (0);
+ }
+ return err;
+ break;
+
+ case SNDCTL_SETSONG:
+ if (adev->d->adrv_ioctl != NULL
+ && adev->d->adrv_ioctl (dev, cmd, arg) >= 0)
+ return 0;
+ strncpy (adev->song_name, (char *) arg, sizeof (adev->song_name));
+ adev->song_name[sizeof (adev->song_name) - 1] = 0;
+ return 0;
+ break;
+
+ case SNDCTL_SETLABEL:
+ if (adev->d->adrv_ioctl != NULL
+ && adev->d->adrv_ioctl (dev, cmd, arg) >= 0)
+ return 0;
+ strncpy (adev->label, (char *) arg, sizeof (adev->label));
+ adev->label[sizeof (adev->label) - 1] = 0;
+ if (*adev->cmd == 0) /* Command name not known */
+ strcpy (adev->cmd, adev->label);
+ return 0;
+ break;
+ default:
+ if (adev->d->adrv_ioctl == NULL)
+ return OSS_EINVAL;
+ return adev->d->adrv_ioctl (dev, cmd, arg);
+ }
+ return OSS_EINVAL;
+}
+
+
+static int
+prepare_output (adev_p adev, dmap_p dmap)
+{
+ int ret, data_rate = 0;
+ audio_format_info_p fmt_info;
+
+ if (dmap->flags & DMAP_PREPARED)
+ return 0;
+
+ data_rate = 8;
+
+ dmap->flags &= ~DMAP_COOKED;
+ adev->user_parms.convert = 0;
+ adev->hw_parms.convert = 0;
+ dmap->convert_func = NULL;
+ dmap->convert_mode = 0;
+ dmap->expand_factor = UNIT_EXPAND;
+
+ if (adev->user_parms.rate != adev->hw_parms.rate)
+ dmap->flags |= DMAP_COOKED;
+ if (adev->user_parms.fmt != adev->hw_parms.fmt)
+ dmap->flags |= DMAP_COOKED;
+ if (adev->user_parms.channels != adev->hw_parms.channels)
+ dmap->flags |= DMAP_COOKED;
+ if ((adev->flags & ADEV_NONINTERLEAVED) && adev->hw_parms.channels > 1)
+ dmap->flags |= DMAP_COOKED;
+
+#if 1
+ if (always_cooked && !(dmap->mapping_flags & DMA_MAP_MAPPED))
+ dmap->flags |= DMAP_COOKED;
+#endif
+
+ if ((dmap->mapping_flags & DMA_MAP_MAPPED) && (dmap->flags & DMAP_COOKED))
+ {
+ cmn_err (CE_WARN, "Internal error in mmap() support\n");
+ dmap->flags &= ~DMAP_COOKED;
+ }
+
+ if (dmap->flags & DMAP_COOKED)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Cooked mode - Setting up conversions");
+#endif
+ if ((ret =
+ setup_format_conversions (adev, dmap, &adev->user_parms,
+ &adev->hw_parms,
+ &adev->user_parms,
+ &adev->hw_parms,
+ adev->oformat_mask)) < 0)
+ {
+ return ret;
+ }
+ }
+ else
+ {
+ DDB (cmn_err (CE_CONT, "No format conversions needed\n"));
+#ifdef DO_TIMINGS
+ oss_do_timing ("No format conversions needed");
+#endif
+ }
+
+/*
+ * Compute device data rate and frame size
+ */
+ if ((fmt_info = oss_find_format (adev->hw_parms.fmt)) != NULL)
+ data_rate = fmt_info->bits;
+
+ data_rate /= 8;
+ if (data_rate < 1)
+ data_rate = 1;
+ data_rate *= adev->hw_parms.channels;
+ dmap->frame_size = data_rate;
+ data_rate *= adev->hw_parms.rate;
+ if (data_rate < 1)
+ data_rate = 8000;
+ dmap->data_rate = data_rate;
+
+/*
+ * Compute application/user frame_size
+ */
+ data_rate = 8;
+ if ((fmt_info = oss_find_format (adev->user_parms.fmt)) != NULL)
+ data_rate = fmt_info->bits;
+
+ data_rate /= 8;
+ if (data_rate < 1)
+ data_rate = 1;
+ data_rate *= adev->user_parms.channels;
+ dmap->user_frame_size = data_rate;
+
+ setup_fragments (adev, dmap, OPEN_WRITE);
+ if (dmap->expand_factor == 0)
+ {
+ dmap->expand_factor = UNIT_EXPAND;
+ cmn_err (CE_WARN, "Bad expand factor for device %d\n",
+ adev->engine_num);
+ }
+ if (dmap->low_water == -1)
+ {
+ dmap->low_water =
+ (dmap->low_water_rq * UNIT_EXPAND) / dmap->expand_factor;
+ }
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Prepare output dev=%d, fragsize=%d, nfrags=%d, bytes_in_use=%d/%d",
+ adev->engine_num, dmap->fragment_size, dmap->nfrags,
+ dmap->bytes_in_use, dmap->buffsize);
+#endif
+
+ if ((ret =
+ adev->d->adrv_prepare_for_output (adev->engine_num,
+ dmap->fragment_size,
+ dmap->nfrags)) < 0)
+ {
+ return ret;
+ }
+
+ dmap->flags |= DMAP_PREPARED;
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_PREPARE_OUTPUT);
+#endif
+
+ return 0;
+}
+
+static int
+prepare_input (adev_p adev, dmap_p dmap)
+{
+ int ret, data_rate = 0;
+ audio_format_info_p fmt_info;
+
+ if (dmap->flags & DMAP_PREPARED)
+ return 0;
+
+ data_rate = 0;
+
+ dmap->flags &= ~DMAP_COOKED;
+ adev->user_parms.convert = 0;
+ adev->hw_parms.convert = 0;
+ dmap->convert_func = NULL;
+ dmap->convert_mode = 0;
+ dmap->expand_factor = UNIT_EXPAND;
+
+ if (adev->user_parms.rate != adev->hw_parms.rate)
+ dmap->flags |= DMAP_COOKED;
+ if (adev->user_parms.fmt != adev->hw_parms.fmt)
+ dmap->flags |= DMAP_COOKED;
+ if (adev->user_parms.channels != adev->hw_parms.channels)
+ dmap->flags |= DMAP_COOKED;
+ if ((adev->flags & ADEV_NONINTERLEAVED) && adev->hw_parms.channels > 1)
+ dmap->flags |= DMAP_COOKED;
+
+#if 1
+ if (always_cooked && !(dmap->mapping_flags & DMA_MAP_MAPPED))
+ dmap->flags |= DMAP_COOKED;
+#endif
+
+ if ((dmap->mapping_flags & DMA_MAP_MAPPED) && (dmap->flags & DMAP_COOKED))
+ {
+ cmn_err (CE_WARN, "Internal error in mmap() support\n");
+ dmap->flags &= ~DMAP_COOKED;
+ }
+
+ if (dmap->flags & DMAP_COOKED)
+ {
+ if ((ret =
+ setup_format_conversions (adev, dmap, &adev->hw_parms,
+ &adev->user_parms,
+ &adev->user_parms,
+ &adev->hw_parms,
+ adev->iformat_mask)) < 0)
+ {
+ DDB (cmn_err
+ (CE_CONT, "setup_format_conversions failed, err=%d\n", ret));
+ return ret;
+ }
+ }
+ else
+ dmap->expand_factor = UNIT_EXPAND;
+
+/*
+ * Compute device data rate and frame size
+ */
+ if ((fmt_info = oss_find_format (adev->hw_parms.fmt)) != NULL)
+ data_rate = fmt_info->bits;
+
+ data_rate /= 8;
+ if (data_rate < 1)
+ data_rate = 1;
+ data_rate *= adev->hw_parms.channels;
+ dmap->frame_size = data_rate;
+ data_rate *= adev->hw_parms.rate;
+ if (data_rate < 1)
+ data_rate = 8000;
+ dmap->data_rate = data_rate;
+
+/*
+ * Compute user/application frame size
+ */
+ data_rate = 8;
+ if ((fmt_info = oss_find_format (adev->user_parms.fmt)) != NULL)
+ data_rate = fmt_info->bits;
+
+ data_rate /= 8;
+ if (data_rate < 1)
+ data_rate = 1;
+ data_rate *= adev->user_parms.channels;
+ dmap->user_frame_size = data_rate;
+
+ setup_fragments (adev, dmap, OPEN_READ);
+
+#if 1
+ /* Compute 1/expand_factor */
+
+ if (dmap->expand_factor == 0)
+ {
+ cmn_err (CE_NOTE, "Internal error (expand_factor==0)\n");
+ dmap->expand_factor = UNIT_EXPAND;
+ }
+
+ {
+ int expand = dmap->expand_factor;
+ int expand2 = expand * 100 / UNIT_EXPAND;
+ DDB (cmn_err (CE_CONT, "Expand factor was = %d (%d.%02d)\n", expand,
+ expand2 / 100, expand2 % 100));
+ }
+
+ dmap->expand_factor = (UNIT_EXPAND * UNIT_EXPAND) / dmap->expand_factor;
+
+ {
+ int expand = dmap->expand_factor;
+ int expand2 = expand * 100 / UNIT_EXPAND;
+ DDB (cmn_err
+ (CE_CONT, "Expand factor inverted to = %d (%d.%02d)\n", expand,
+ expand2 / 100, expand2 % 100));
+ }
+#endif
+
+ if (dmap->low_water == -1)
+ {
+ dmap->low_water =
+ (dmap->low_water_rq * UNIT_EXPAND) / dmap->expand_factor;
+ }
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Prepare input dev=%d, fragsize=%d, nfrags=%d, bytes_in_use=%d",
+ adev->engine_num, dmap->fragment_size, dmap->nfrags,
+ dmap->bytes_in_use);
+#endif
+
+ if ((ret =
+ adev->d->adrv_prepare_for_input (adev->engine_num, dmap->fragment_size,
+ dmap->nfrags)) < 0)
+ {
+ DDB (cmn_err
+ (CE_CONT, "/dev/dsp%d: prepare_for_input failed, err=%d\n",
+ adev->engine_num, ret));
+ return ret;
+ }
+
+ dmap->flags |= DMAP_PREPARED;
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_PREPARE_INPUT);
+#endif
+
+ return 0;
+}
+
+static int
+launch_input (adev_p adev, dmap_p dmap)
+{
+#ifdef DO_TIMINGS
+ oss_do_timing ("Launch input called");
+#endif
+ if (dmap->flags & DMAP_STARTED)
+ return 0;
+
+ if (!(dmap->flags & DMAP_PREPARED))
+ {
+ cmn_err (CE_WARN, "launch_input while not prepared.\n");
+ return OSS_EIO;
+ }
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Launch_input calling d->start_input");
+#endif
+
+ if (adev->d->adrv_start_input != NULL)
+ {
+ if (adev->flags & ADEV_AUTOMODE)
+ adev->d->adrv_start_input (adev->engine_num, dmap->dmabuf_phys,
+ dmap->bytes_in_use, dmap->fragment_size,
+ 0);
+ else
+ adev->d->adrv_start_input (adev->engine_num, dmap->dmabuf_phys,
+ dmap->fragment_size, dmap->fragment_size,
+ 0);
+ }
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Launch_input calling trigger");
+#endif
+ dmap->flags |= DMAP_STARTED;
+ if (adev->d->adrv_trigger
+ && ((adev->enable_bits * adev->go) & PCM_ENABLE_INPUT))
+ {
+ adev->d->adrv_trigger (adev->engine_num, adev->enable_bits * adev->go);
+ }
+
+ return 0;
+}
+
+static int
+find_raw_input_space (adev_p adev, dmap_p dmap, int *dmapos)
+{
+ int count;
+ int tmout, n = 0;
+ oss_uint64_t offs, doffs;
+ int lim = 1;
+ unsigned int status;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ if (adev->nonblock)
+ get_input_pointer (adev, dmap, 1);
+ count = (int) (dmap->byte_counter - dmap->user_counter);
+ *dmapos=0;
+
+ if (dmap->flags & DMAP_COOKED)
+ {
+ lim = dmap->fragment_size;
+ }
+
+ while (count < lim)
+ {
+ if (adev->nonblock)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+#ifdef DO_TIMINGS
+ oss_do_timing ("*** EAGAIN ***");
+#endif
+ return OSS_EAGAIN;
+ }
+
+ if (n++ > 100)
+ {
+ cmn_err (CE_WARN, "Audio input %d doesn't get filled.\n",
+ adev->engine_num);
+ cmn_err (CE_CONT, "Counters %d / %d\n", count, lim);
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return OSS_EIO;
+ }
+
+ tmout = (dmap->fragment_size * OSS_HZ) / dmap->data_rate;
+ tmout += OSS_HZ / 2;
+
+ if (adev->go == 0)
+ tmout = 0;
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Sleep (in)");
+ oss_timing_enter (DF_SLEEPREAD);
+#endif
+ audio_engines[adev->engine_num]->dmap_in->error = 0;
+ if (!oss_sleep (adev->in_wq, &dmap->mutex, tmout, &flags, &status))
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Sleep (in) timed out");
+#endif
+ adev->timeout_count++;
+ if (adev->d->adrv_check_input)
+ {
+ int err = adev->d->adrv_check_input (adev->engine_num);
+ if (err == 0)
+ continue; /* Retry */
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return err;
+ }
+ cmn_err (CE_NOTE,
+ "Input timed out on audio engine %d (count=%lld)\n",
+ adev->engine_num, dmap->byte_counter);
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ FMA_EREPORT(adev->osdev, DDI_FM_DEVICE_STALL, NULL, NULL, NULL);
+ FMA_IMPACT(adev->osdev, DDI_SERVICE_LOST);
+ return OSS_EIO;
+ } /* Timed out */
+
+#ifdef DO_TIMINGS
+ oss_timing_leave (DF_SLEEPREAD);
+ oss_do_timing ("Sleep (in) done");
+#endif
+
+ if (status & WK_SIGNAL)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return OSS_EINTR;
+ }
+ count = (int) (dmap->byte_counter - dmap->user_counter);
+#ifdef DO_TIMINGS
+ oss_timing_printf ("User counter %d, byte_counter %d", dmap->user_counter,
+ dmap->byte_counter);
+#endif
+ }
+
+/* Now we should have some data */
+
+ if (adev->nonblock)
+ get_input_pointer (adev, dmap, 1);
+
+ offs = dmap->user_counter % dmap->bytes_in_use;
+ doffs = dmap->byte_counter % dmap->bytes_in_use;
+
+ count = (int) (dmap->bytes_in_use - offs);
+ if (offs <= doffs)
+ count = (int) (doffs - offs);
+ if (count == 0)
+ count = dmap->bytes_in_use;
+
+ *dmapos = offs;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return count;
+}
+
+/*ARGSUSED*/
+static int
+move_raw_rdpointer (adev_p adev, dmap_p dmap, int len)
+{
+ int ret = 0;
+ oss_native_word flags;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Move rdpointer, offs=%d, incr %d", dmap->user_counter,
+ len);
+#endif
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ dmap->user_counter += len;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return ret;
+}
+
+static void
+copy_read_noninterleaved(adev_t *adev, dmap_t *dmap, int dma_offs, unsigned char *localbuf, int local_offs, int l)
+{
+/*
+ * Copy audio data from non-interleaved device buffer to interleaved
+ * local buffer.
+ */
+// TODO: This function assumes 32 bit audio DATA
+
+ int ch, i, nc = adev->hw_parms.channels;
+ int *outbuf, *inbuf;
+
+ l /= sizeof(*outbuf)*nc;
+ dma_offs /= sizeof(*outbuf);
+ local_offs /= sizeof(*outbuf);
+
+ for (ch=0;ch<nc;ch++)
+ {
+ outbuf = (int*)(localbuf+local_offs);
+ outbuf += ch;
+
+ inbuf = (int *)(dmap->dmabuf + dmap->buffsize*ch / nc);
+ inbuf += dma_offs / nc;
+
+ for (i=0;i<l;i++)
+ {
+ *outbuf = *inbuf++;
+ outbuf += nc;
+ }
+ }
+
+}
+
+static int
+find_input_space (adev_p adev, dmap_p dmap, unsigned char **dbuf)
+{
+ unsigned char *p, *p1 = dmap->tmpbuf1, *p2 = dmap->tmpbuf2;
+ int err, l, l2, max, dmapos;
+
+ if (!(dmap->flags & DMAP_COOKED))
+ {
+ err=find_raw_input_space (adev, dmap, &dmapos);
+ *dbuf=dmap->dmabuf+dmapos;
+ return err;
+ }
+
+ if (dmap->tmpbuf_len > dmap->tmpbuf_ptr)
+ {
+ *dbuf = dmap->tmpbuf1 + dmap->tmpbuf_ptr;
+ return dmap->tmpbuf_len - dmap->tmpbuf_ptr;
+ }
+
+ dmap->tmpbuf_len = dmap->tmpbuf_ptr = 0;
+
+ if ((l = find_raw_input_space (adev, dmap, &dmapos)) < 0)
+ {
+ return l;
+ }
+ p=dmap->dmabuf;
+
+ if (dmap->expand_factor > UNIT_EXPAND)
+ max = (TMP_CONVERT_MAX * UNIT_EXPAND) / dmap->expand_factor;
+ else
+ max = TMP_CONVERT_MAX;
+
+ if (max > TMP_CONVERT_MAX)
+ max = TMP_CONVERT_MAX;
+
+ if (l > max)
+ l = max;
+ l = (l / dmap->frame_size) * dmap->frame_size; /* Truncate to nearest frame size */
+ l2 = l;
+ VMEM_CHECK (p1, l);
+ VMEM_CHECK (p+dmapos, l);
+
+ if ((adev->flags & ADEV_NONINTERLEAVED) && adev->hw_parms.channels > 1)
+ copy_read_noninterleaved(adev, dmap, dmapos, p1, 0, l);
+ else
+ memcpy (p1, p+dmapos, l);
+
+ move_raw_rdpointer (adev, dmap, l);
+
+ if ((err =
+ dmap->convert_func (adev, dmap, &p1, &l2,
+ &p2, &adev->hw_parms,
+ &adev->user_parms)) < 0)
+ return err;
+
+ dmap->tmpbuf1 = p1;
+ dmap->tmpbuf2 = p2;
+ dmap->tmpbuf_len = l2;
+
+ *dbuf = dmap->tmpbuf1;
+ return dmap->tmpbuf_len;
+}
+
+static int
+move_rdpointer (adev_p adev, dmap_p dmap, int len)
+{
+ if (!(dmap->flags & DMAP_COOKED) || dmap->tmpbuf_ptr >= dmap->tmpbuf_len)
+ {
+ dmap->tmpbuf_ptr = 0;
+ dmap->tmpbuf_len = 0;
+ return move_raw_rdpointer (adev, dmap, len);
+ }
+
+ if (dmap->tmpbuf_ptr < dmap->tmpbuf_len)
+ {
+ dmap->tmpbuf_ptr += len;
+
+ if (dmap->tmpbuf_ptr >= dmap->tmpbuf_len)
+ dmap->tmpbuf_len = dmap->tmpbuf_ptr = 0;
+
+ return 0;
+ }
+
+ cmn_err (CE_NOTE, "Why here?\n");
+ return OSS_EIO;
+}
+
+int
+oss_audio_read (int dev, struct fileinfo *file, uio_t * buf, int count)
+{
+ adev_p adev;
+ dmap_p dmap;
+ int c, l, p, ret, n;
+ unsigned char *dmabuf;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("--- audio_read(%d, %d) ---", dev, count);
+#endif
+
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (!adev->enabled)
+ return OSS_ENXIO;
+ if (adev->flags & ADEV_NOINPUT)
+ return OSS_EACCES;
+ dmap = adev->dmap_in;
+
+ if (!(adev->open_mode & OPEN_READ))
+ return OSS_ENOTSUP;
+ if (dmap->dma_mode == PCM_ENABLE_OUTPUT)
+ {
+ audio_reset_output (adev);
+ reset_dmap (dmap);
+ }
+
+/*
+ * Initial setup
+ */
+ oss_reset_wait_queue (adev->in_wq);
+
+ if (file != NULL)
+ {
+ if ((ISSET_FILE_FLAG (file, O_NONBLOCK)
+ && !(adev->open_flags & OF_BLOCK)) || adev->forced_nonblock)
+ adev->nonblock = 1;
+ else
+ adev->nonblock = 0;
+#ifdef DO_TIMINGS
+ if (adev->nonblock)
+ oss_do_timing ("*** NON BLOCKING READ ***");
+#endif
+ }
+
+ if (dmap->dma_mode != PCM_ENABLE_INPUT)
+ {
+ if ((ret = prepare_input (adev, dmap)) < 0)
+ {
+ DDB (cmn_err (CE_CONT, "Prepare input failed, err=%d\n", ret));
+ return ret;
+ }
+ dmap->dma_mode = PCM_ENABLE_INPUT;
+ launch_input (adev, dmap);
+ }
+
+ if (!(dmap->flags & DMAP_PREPARED))
+ { /* Not prepared. Why??? */
+ cmn_err (CE_WARN, "Intenal error (not prepared)\n");
+ return OSS_EIO;
+ }
+
+ c = count;
+ p = 0;
+ n = 0;
+
+ while (c > 0 && n++ < 1000)
+ {
+ if ((l = find_input_space (adev, dmap, &dmabuf)) < 0)
+ {
+ if (l == OSS_EINTR)
+ {
+ if (c == count) /* Nothing read yet */
+ return OSS_EINTR;
+ return count - c;
+ }
+ if (l == OSS_EAGAIN)
+ {
+ if (c == count) /* Nothing read yet */
+ return OSS_EAGAIN;
+ return count - c;
+ }
+ return l;
+ }
+
+ if (l > c)
+ l = c;
+
+ if (uiomove (dmabuf, l, UIO_READ, buf) != 0)
+ {
+ cmn_err (CE_WARN, "audio: uiomove(UIO_READ) failed\n");
+ return OSS_EFAULT;
+ }
+ if ((ret = move_rdpointer (adev, dmap, l)) < 0)
+ {
+ return ret;
+ }
+
+ c -= l;
+ p += l;
+ }
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("-------- Audio read done (%d)", count - c);
+#endif
+
+ return count - c;
+}
+
+/*ARGSUSED*/
+static int
+audio_space_in_queue (adev_p adev, dmap_p dmap, int count)
+{
+ int cnt;
+
+ cnt = (int) (dmap->byte_counter + dmap->bytes_in_use - dmap->user_counter);
+
+ if (cnt < 0)
+ {
+ cmn_err (CE_CONT, "Buffer %d overfilled (%d)\n", adev->engine_num, cnt);
+ }
+
+ if (!(dmap->mapping_flags & DMA_MAP_MAPPED))
+ if (cnt > dmap->bytes_in_use) /* Output underrun */
+ {
+#ifdef DO_TIMINGS
+ oss_timing_printf ("adev %d: Play underrun B", adev->engine_num);
+ oss_timing_printf (" User=%lld", dmap->user_counter);
+ oss_timing_printf (" Dev=%lld", dmap->byte_counter);
+ oss_timing_printf (" Tmp=%d", dmap->tmpbuf_ptr);
+#endif
+ cnt = dmap->bytes_in_use;
+ dmap->user_counter = dmap->byte_counter;
+ dmap->play_underruns++;
+ if (!dmap->underrun_flag)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Clearing the buffer");
+#endif
+ memset (dmap->dmabuf, dmap->neutral_byte, dmap->bytes_in_use);
+ }
+ dmap->underrun_flag = 1;
+ }
+
+ if ((dmap->user_counter + cnt) > (dmap->byte_counter + dmap->bytes_in_use))
+ cmn_err (CE_CONT, "Overflow %lld+%d, %lld\n", dmap->user_counter, cnt,
+ dmap->byte_counter);
+
+ return cnt;
+}
+
+static int
+find_output_space (adev_p adev, dmap_p dmap, int *size, int count)
+{
+ int offs;
+ int len, l2, n = 0, tmout;
+ oss_native_word flags;
+ int tout;
+ unsigned int status;
+
+ if (dmap == NULL)
+ {
+ cmn_err (CE_WARN, "Internal error - dmap==NULL\n");
+ return OSS_EIO;
+ }
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ len = audio_space_in_queue (adev, dmap, count);
+
+ tout = 0;
+ while (len < dmap->user_frame_size)
+ { /* Wait for some space */
+ if (tout++ > 10000)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ cmn_err (CE_WARN, "Internal timeout error B\n");
+ return OSS_EIO;
+ }
+
+ if (adev->nonblock || !(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Space=%d\n", len);
+ oss_do_timing ("*** EAGAIN ***");
+#endif
+ if (!(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ {
+ launch_output (adev, dmap);
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1021,
+ "Play buffer full when playback is triggered off."),
+ 0);
+ /*
+ * Errordesc:
+ * An application had written too many samples of data between
+ * turning off the PCM_ENABLE_OUTPUT trigger bit and turning it back
+ * again to trigger playback.
+ *
+ * Applications using SNDCTL_DSP_SETTRIGGER should avoid filling the
+ * available playback buffer before triggering output.
+ *
+ * One possible error causing this is that the application has
+ * triggered only recording on a duplex device.
+ */
+ }
+ return OSS_EAGAIN;
+ }
+
+ if (n++ > dmap->nfrags * 2)
+ {
+ cmn_err (CE_WARN, "Audio output %d doesn't drain (%lld/%lld %d).\n",
+ adev->engine_num, dmap->user_counter, dmap->byte_counter,
+ len);
+ cmn_err (CE_CONT, "len=%d/%d, total=%d\n", len, dmap->fragment_size,
+ dmap->bytes_in_use);
+#ifdef DO_TIMINGS
+ oss_do_timing ("Audio output doesn't drain");
+#endif
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ audio_reset_output (adev);
+ return OSS_EIO;
+ }
+
+ tmout = (dmap->fragment_size * OSS_HZ) / dmap->data_rate;
+ tmout += OSS_HZ;
+
+ if (adev->go == 0)
+ tmout = 0;
+
+ audio_engines[adev->engine_num]->dmap_out->error = 0;
+ if (adev->go == 0)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return OSS_EAGAIN;
+ }
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Sleep(%d)", adev->engine_num);
+ oss_timing_enter (DF_SLEEPWRITE);
+#endif
+ UP_STATUS (STS_SLEEP);
+ if (!oss_sleep (adev->out_wq, &dmap->mutex, tmout, &flags, &status))
+ {
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Sleep(%d) (out) timed out", adev->engine_num);
+#endif
+ adev->timeout_count++;
+
+ if (adev->d->adrv_check_output)
+ {
+ int err = adev->d->adrv_check_output (adev->engine_num);
+ if (err == 0)
+ continue; /* Retry */
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return err;
+ }
+ else
+ {
+ cmn_err (CE_NOTE,
+ "Output timed out on audio engine %d/'%s' (count=%lld)\n",
+ adev->engine_num, adev->name, dmap->byte_counter);
+ }
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ FMA_EREPORT(adev->osdev, DDI_FM_DEVICE_STALL, NULL, NULL, NULL);
+ FMA_IMPACT(adev->osdev, DDI_SERVICE_LOST);
+ return OSS_EIO;
+ } /* Timed out */
+ DOWN_STATUS (STS_SLEEP);
+
+#ifdef DO_TIMINGS
+ oss_timing_leave (DF_SLEEPWRITE);
+ oss_timing_printf ("Sleep(%d) (out) done", adev->engine_num);
+#endif
+
+ if (status & WK_SIGNAL)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Signal caught");
+#endif
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return OSS_EINTR;
+ }
+
+ len = audio_space_in_queue (adev, dmap, count);
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Free output space now %d bytes", len);
+#endif
+ } /* Wait for space */
+
+/*
+ * Now we hopefully have some free space in the buffer.
+ */
+
+ offs = (int) (dmap->user_counter % dmap->bytes_in_use);
+
+ l2 = dmap->bytes_in_use - offs;
+ if (len > l2)
+ len = l2;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if (len < 0)
+ len = 0;
+
+ *size = len;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Got output buffer, offs %d/%d, len %d", offs,
+ dmap->bytes_in_use, len);
+#endif
+ if (offs < 0 || (offs + len) > dmap->bytes_in_use)
+ {
+ cmn_err (CE_WARN, "Bad audio output buffer %d/%d\n", offs, len);
+ return OSS_EIO;
+ }
+
+ return offs;
+}
+
+static int
+launch_output (adev_p adev, dmap_p dmap)
+{
+ oss_native_word flags;
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Launch output called");
+#endif
+ if (dmap->flags & DMAP_STARTED)
+ {
+ return 0;
+ }
+
+ if (dmap->user_counter == 0 && dmap->audio_callback == NULL
+ && dmap->mapping_flags == 0)
+ {
+ return 0;
+ }
+
+ if (!(dmap->flags & DMAP_PREPARED))
+ {
+ cmn_err (CE_WARN, "launch_output while not prepared. Engine=%d\n", adev->engine_num);
+ return OSS_EIO;
+ }
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Launch_output calling output_block");
+#endif
+
+ if (adev->d->adrv_output_block != NULL)
+ {
+ if (adev->flags & ADEV_AUTOMODE)
+ adev->d->adrv_output_block (adev->engine_num, dmap->dmabuf_phys,
+ dmap->bytes_in_use, dmap->fragment_size,
+ 0);
+ else
+ adev->d->adrv_output_block (adev->engine_num, dmap->dmabuf_phys,
+ dmap->fragment_size, dmap->fragment_size,
+ 0);
+ }
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Launch_output calling trigger");
+#endif
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ dmap->flags |= DMAP_STARTED;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if (adev->d->adrv_trigger
+ && ((adev->enable_bits * adev->go) & PCM_ENABLE_OUTPUT))
+ {
+ adev->d->adrv_trigger (adev->engine_num, adev->enable_bits * adev->go);
+ }
+
+ return 0;
+}
+
+static int
+move_wrpointer (adev_p adev, dmap_p dmap, int len)
+{
+ int ret = 0;
+ oss_native_word flags;
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Move wrpointer, len %d", len);
+#endif
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ dmap->underrun_flag = 0;
+
+ dmap->user_counter += len;
+#ifdef DO_TIMINGS
+ oss_timing_printf (" User=%lld", dmap->user_counter);
+ oss_timing_printf (" Byte=%lld", dmap->byte_counter);
+ oss_timing_printf (" Fill=%lld", dmap->user_counter - dmap->byte_counter);
+#endif
+
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_UPDATE_OUTPUT);
+#endif
+
+ if (ret < 0 || dmap->flags & DMAP_STARTED)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return ret;
+ }
+
+ if (!(adev->enable_bits & PCM_ENABLE_OUTPUT))
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Output not triggered - skipping launch_output");
+#endif
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return ret;
+ }
+
+ if ((dmap->user_counter < dmap->fragment_size * 2)
+ && (dmap->user_counter < dmap->bytes_in_use / 2))
+ {
+#ifdef DO_TIMINGS
+ oss_timing_printf ("dmap->user_counter=%lld, dmap->fragment_size*2=%ld",
+ dmap->user_counter, dmap->fragment_size * 2);
+ oss_do_timing
+ ("Not enough data in the buffer yet - skipping launch_output");
+#endif
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ return ret;
+ }
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ ret = launch_output (adev, dmap);
+ return ret;
+}
+
+/*ARGSUSED*/
+static void
+store_tmp_data (adev_p adev, dmap_p dmap, unsigned char *buf, int count)
+{
+ dmap->leftover_buf = buf;
+ dmap->leftover_bytes = count;
+}
+
+static void
+copy_write_noninterleaved(adev_t *adev, dmap_t *dmap, int dma_offs, unsigned char *localbuf, int local_offs, int l)
+{
+/*
+ * Copy interleaved N channel data to non-interleaved device buffer.
+ */
+// TODO: This function assumes 32 bit audio DATA
+
+ int ch, i, nc = adev->hw_parms.channels;
+ int *inbuf, *outbuf;
+
+ l /= sizeof(*inbuf)*nc;
+ dma_offs /= sizeof(*inbuf);
+ local_offs /= sizeof(*inbuf);
+
+ for (ch=0;ch<nc;ch++)
+ {
+ inbuf = (int*)(localbuf+local_offs);
+ inbuf += ch;
+
+ outbuf = (int *)(dmap->dmabuf + dmap->buffsize*ch / nc);
+ outbuf += dma_offs / nc;
+
+ for (i=0;i<l;i++)
+ {
+ *outbuf++ = *inbuf;
+ inbuf += nc;
+ }
+ }
+
+}
+
+static int
+write_copy (adev_p adev, dmap_p dmap, unsigned char *buf, int count)
+{
+ int err, offs, spc, l, p = 0;
+
+ while (count)
+ {
+ l = count;
+
+ if ((offs = find_output_space (adev, dmap, &spc, l)) < 0)
+ {
+ if (offs == OSS_EAGAIN)
+ {
+ store_tmp_data (adev, dmap, buf + p, count);
+ launch_output (adev, dmap);
+ return OSS_EAGAIN;
+ }
+ return offs;
+ }
+
+ if (l > spc)
+ l = spc;
+
+ VMEM_CHECK (&dmap->dmabuf[offs], l);
+ VMEM_CHECK (buf + p, l);
+
+ if ((adev->flags & ADEV_NONINTERLEAVED) && adev->hw_parms.channels > 1)
+ {
+ copy_write_noninterleaved(adev, dmap, offs, buf + p, 0, l);
+ }
+ else
+ memcpy (&dmap->dmabuf[offs], buf + p, l);
+
+ if ((err = move_wrpointer (adev, dmap, l)) < 0)
+ return err;
+
+ count -= l;
+ p += l;
+ }
+ return 0;
+}
+
+int
+oss_audio_write (int dev, struct fileinfo *file, uio_t * buf, int count)
+{
+ adev_p adev;
+ oss_native_word flags;
+ dmap_p dmap;
+ int ret;
+ int l, c, spc, offs, p, err;
+ int tmout;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("--- audio_write(%d, %d) ---", dev, count);
+#endif
+
+ sync_seed++;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+
+ adev = audio_engines[dev];
+ if (!adev->enabled)
+ return OSS_ENXIO;
+ if (adev->flags & ADEV_NOOUTPUT)
+ return OSS_EACCES;
+ dmap = adev->dmap_out;
+
+ if (!(adev->open_mode & OPEN_WRITE))
+ return OSS_ENOTSUP;
+
+ UP_STATUS (STS_WRITE);
+
+ if (dmap->dma_mode == PCM_ENABLE_INPUT)
+ {
+ audio_reset_input (adev);
+ reset_dmap (dmap);
+ }
+
+/*
+ * Initial setup
+ */
+ oss_reset_wait_queue (adev->out_wq);
+
+ if (file != NULL)
+ {
+ if ((ISSET_FILE_FLAG (file, O_NONBLOCK)
+ && !(adev->open_flags & OF_BLOCK)) || adev->forced_nonblock)
+ adev->nonblock = 1;
+ else
+ adev->nonblock = 0;
+#ifdef DO_TIMINGS
+ if (adev->nonblock)
+ oss_do_timing ("*** NON BLOCKING WRITE ***");
+#endif
+ }
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if ((ret = prepare_output (adev, dmap)) < 0)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return ret;
+ }
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ dmap->dma_mode = PCM_ENABLE_OUTPUT;
+ }
+
+ if (!(dmap->flags & DMAP_PREPARED))
+ { /* Not prepared. Why??? */
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ DOWN_STATUS (STS_WRITE);
+ cmn_err (CE_WARN, "Internal error (not prepared)\n");
+ return OSS_EIO;
+ }
+#if 1
+ if (dmap->leftover_bytes > 0)
+ {
+ unsigned char *b;
+ int l;
+
+ b = dmap->leftover_buf;
+ l = dmap->leftover_bytes;
+ dmap->leftover_bytes = 0;
+ dmap->leftover_buf = NULL;
+
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if ((err = write_copy (adev, dmap, b, l)) < 0)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return err;
+ }
+
+ dmap->leftover_bytes = 0;
+ dmap->leftover_buf = NULL;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ }
+#endif
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ if (count <= 0)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return 0;
+ }
+
+ c = count;
+ p = 0;
+
+ tmout = 0;
+
+ while (c > 0)
+ {
+#ifdef DO_TIMINGS
+ oss_timing_printf ("%d/%d bytes to go", c, count);
+#endif
+
+ if (tmout++ > 1000)
+ {
+ cmn_err (CE_WARN, "Internal timeout error A (%d/%d)\n", c, count);
+ return OSS_EIO;
+ }
+
+ l = c;
+ if (l > 8)
+ l &= ~7; /* Align it */
+
+ if ((offs = find_output_space (adev, dmap, &spc, l)) < 0)
+ {
+ if (offs == OSS_EINTR)
+ {
+ DOWN_STATUS (STS_WRITE);
+ if (c == count) /* Nothing written yet */
+ return OSS_EINTR;
+ return count - c;
+ }
+ if (offs == OSS_EAGAIN)
+ {
+ DOWN_STATUS (STS_WRITE);
+ if (c == count) /* Nothing written yet */
+ {
+ return OSS_EAGAIN;
+ }
+ return count - c;
+ }
+ DOWN_STATUS (STS_WRITE);
+ return offs;
+ }
+
+ if (dmap->convert_func == NULL)
+ {
+ if (dmap->device_write != NULL)
+ {
+ unsigned char *tmpbuf;
+ int l2;
+
+ if (dmap->tmpbuf1 == NULL)
+ {
+ dmap->tmpbuf1 = AUDIO_MALLOC (dmap->osdev, TMP_CONVERT_BUF_SIZE+512);
+ }
+
+/*
+ * Leave some room for data expansion so use just half of the available
+ * space.
+ */
+ tmpbuf = dmap->tmpbuf1;
+ if (l > spc / 2)
+ l = spc / 2;
+ if (l > TMP_CONVERT_BUF_SIZE / 2)
+ l = TMP_CONVERT_BUF_SIZE / 2;
+
+ l2 = l;
+ if (uiomove (tmpbuf, l, UIO_WRITE, buf) != 0)
+ {
+ cmn_err (CE_WARN,
+ "audio: uiomove(UIO_WRITE) failed (noconv)\n");
+ DOWN_STATUS (STS_WRITE);
+ return OSS_EFAULT;
+ }
+ if ((err =
+ dmap->device_write (adev, dmap, tmpbuf,
+ &dmap->dmabuf[offs], spc, &l,
+ &l2)) < 0)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return err;
+ }
+ if ((err = move_wrpointer (adev, dmap, l2)) < 0)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return err;
+ }
+ }
+ else
+ {
+ if (l > spc)
+ l = spc;
+ if (uiomove (&dmap->dmabuf[offs], l, UIO_WRITE, buf) != 0)
+ {
+ cmn_err (CE_WARN,
+ "audio: uiomove(UIO_WRITE) (noconv2) failed\n");
+ return OSS_EFAULT;
+ }
+ if ((err = move_wrpointer (adev, dmap, l)) < 0)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return err;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Perform format conversions.
+ */
+ unsigned char *p1 = dmap->tmpbuf1, *p2 = dmap->tmpbuf2;
+ int l2, max, out_max;
+
+ if (spc > TMP_CONVERT_MAX)
+ spc = TMP_CONVERT_MAX / 2;
+
+ if (dmap->expand_factor > UNIT_EXPAND)
+ {
+ max = spc;
+
+ out_max = (spc * dmap->expand_factor) / UNIT_EXPAND; /* Output size */
+ if (out_max > TMP_CONVERT_BUF_SIZE) /* Potential overflow */
+ {
+ max = (TMP_CONVERT_BUF_SIZE * UNIT_EXPAND) / dmap->expand_factor;
+ }
+ }
+ else
+ max = spc;
+ if (max < dmap->frame_size)
+ max = dmap->frame_size;
+
+ if (max > TMP_CONVERT_MAX)
+ max = TMP_CONVERT_MAX / 2;
+
+ if (l > max)
+ l = max;
+ /* Avoid leaving too short "tails" */
+ if (c - l < 64)
+ l = c;
+
+ /* Round to integer number of samples */
+ l =
+ (((l + dmap->frame_size -
+ 1) / dmap->frame_size)) * dmap->frame_size;
+ if (l > c)
+ l = c;
+
+ l2 = l;
+
+ VMEM_CHECK (p1, l);
+ if (uiomove (p1, l, UIO_WRITE, buf) != 0)
+ cmn_err (CE_WARN, "audio: uiomove(UIO_WRITE) (conv) failed\n");
+ UP_STATUS (STS_CONVERT);
+ if ((err =
+ dmap->convert_func (adev, dmap, &p1, &l2,
+ &p2, &adev->user_parms,
+ &adev->hw_parms)) < 0)
+ {
+ cmn_err (CE_WARN, "Format conversion failed (%d)\n", err);
+ DOWN_STATUS (STS_WRITE | STS_CONVERT);
+ return err;
+ }
+ DOWN_STATUS (STS_CONVERT);
+
+ if ((err = write_copy (adev, dmap, p1, l2)) < 0)
+ {
+ if (err != OSS_EAGAIN)
+ {
+ DOWN_STATUS (STS_WRITE);
+ return err;
+ }
+
+ /* Handle non blocking I/O */
+ if (c == count) /* Nothing written yet */
+ {
+ DOWN_STATUS (STS_WRITE);
+ return OSS_EAGAIN;
+ }
+
+ DOWN_STATUS (STS_WRITE);
+ return count - c;
+
+ }
+ }
+
+ c -= l;
+ p += l;
+
+ if (l > 0)
+ tmout = 0;
+ }
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("--- Audio write done");
+#endif
+ DOWN_STATUS (STS_WRITE);
+ return count - c;
+}
+
+#ifdef MANAGE_DEV_DSP
+#ifdef VDEV_SUPPORT
+void
+oss_combine_write_lists (void)
+{
+ int i;
+
+ for (i = 0; i < dspoutlist2.ndevs; i++)
+ dspoutlist.devices[dspoutlist.ndevs++] = dspoutlist2.devices[i];
+
+ dspoutlist2.ndevs = 0;
+}
+
+/*ARGSUSED*/
+int
+oss_open_vdsp (int dev, int dev_type, struct fileinfo *file, int recursive,
+ int open_flags, int *newdev)
+{
+ int ret, i, d;
+ int mode = file->mode & O_ACCMODE;
+
+ DDB (cmn_err (CE_CONT, "oss_open_vdsp(%d, mode=%d)\n", dev, mode));
+
+ switch (dev)
+ {
+ case 1:
+ mode = OPEN_READ;
+ break;
+ case 2:
+ mode = OPEN_WRITE;
+ break;
+
+ /* default: Use the mode defined by O_ACCMODE */
+ }
+
+ dev = -1;
+ open_flags = get_open_flags (mode, open_flags, file);
+
+ switch (dev_type)
+ {
+ case OSS_DEV_VDSP:
+ dev_type = OSS_DEV_DSP;
+ break;
+#if 0
+ case OSS_DEV_VAUDIO:
+ dev_type = OSS_DEV_DEVAUDIO;
+ break;
+#endif
+ default:
+ cmn_err (CE_NOTE, "Unknown dev class %d\n", dev_type);
+ break;
+ }
+
+ if (audio_devfiles == NULL)
+ {
+ cmn_err (CE_NOTE, "No audio device files available\n");
+ return OSS_ENXIO;
+ }
+#ifdef MANAGE_DEV_DSP
+#ifdef VDEV_SUPPORT
+ oss_combine_write_lists ();
+#endif
+#endif
+
+#ifdef APPLIST_SUPPORT
+ {
+ char *appname;
+ appname = GET_PROCESS_NAME (file);
+ if (open_flags & OF_DEVAUDIO)
+ appname = "Devaudio_Support";
+
+ if ((dev = app_lookup (mode, appname, &open_flags)) >= 0)
+ if (audio_devfiles[dev]->enabled && !audio_devfiles[dev]->unloaded)
+ if ((ret =
+ oss_audio_open_devfile (dev, dev_type, file, 0, open_flags,
+ newdev)) >= 0)
+ {
+ dev = ret;
+ DDB (cmn_err
+ (CE_CONT, "Using dsp%d configured for this application\n",
+ dev));
+ goto done;
+ }
+ }
+#endif
+ DDB (cmn_err (CE_CONT, "\n"));
+ DDB (cmn_err (CE_CONT, "Out devs: "));
+ for (i = 0; i < dspoutlist.ndevs; i++)
+ DDB (cmn_err (CE_CONT, "%d ", dspoutlist.devices[i]));
+ for (i = 0; i < dspoutlist2.ndevs; i++)
+ DDB (cmn_err (CE_CONT, "(%d) ", dspoutlist2.devices[i]));
+ DDB (cmn_err (CE_CONT, "\n"));
+ DDB (cmn_err (CE_CONT, "In devs: "));
+ for (i = 0; i < dspinlist.ndevs; i++)
+ DDB (cmn_err (CE_CONT, "%d ", dspinlist.devices[i]));
+ DDB (cmn_err (CE_CONT, "\n"));
+ DDB (cmn_err (CE_CONT, "In/out devs: "));
+ for (i = 0; i < dspinoutlist.ndevs; i++)
+ DDB (cmn_err (CE_CONT, "%d ", dspinoutlist.devices[i]));
+ DDB (cmn_err (CE_CONT, "\n"));
+
+ switch (mode & (OPEN_READ | OPEN_WRITE))
+ {
+ case OPEN_WRITE:
+ DDB (cmn_err (CE_CONT, "Selecting output device: "));
+
+ for (i = 0; i < dspoutlist.ndevs; i++)
+ {
+ dev = dspoutlist.devices[i];
+ if (!audio_devfiles[dev]->enabled || audio_devfiles[dev]->unloaded)
+ {
+ dev = -1;
+ continue;
+ }
+
+ DDB (cmn_err (CE_CONT, "%d ", dev));
+ if ((ret =
+ oss_audio_open_devfile (dev, dev_type, file, 0, open_flags,
+ newdev)) >= 0)
+ {
+ dev = ret;
+ DDB (cmn_err (CE_CONT, "->%d ", dev));
+ break;
+ }
+ dev = -1;
+ }
+ break;
+
+ case OPEN_READ:
+ DDB (cmn_err (CE_CONT, "Selecting input device: "));
+ for (i = 0; i < dspinlist.ndevs; i++)
+ {
+ dev = dspinlist.devices[i];
+ if (!audio_devfiles[dev]->enabled || audio_devfiles[dev]->unloaded)
+ {
+ dev = -1;
+ continue;
+ }
+ DDB (cmn_err (CE_CONT, "%d ", dev));
+ if ((ret =
+ oss_audio_open_devfile (dev, dev_type, file, 0, open_flags,
+ newdev)) >= 0)
+ {
+ dev = ret;
+ DDB (cmn_err (CE_CONT, "->%d ", dev));
+ break;
+ }
+
+ dev = -1;
+ }
+ break;
+
+ case OPEN_WRITE | OPEN_READ:
+ DDB (cmn_err (CE_CONT, "Selecting input/output device: "));
+ for (i = 0; i < dspinoutlist.ndevs; i++)
+ {
+ dev = dspinoutlist.devices[i];
+ if (!audio_devfiles[dev]->enabled || audio_devfiles[dev]->unloaded)
+ {
+ dev = -1;
+ continue;
+ }
+ DDB (cmn_err (CE_CONT, "%d ", dev));
+ if ((ret =
+ oss_audio_open_devfile (dev, dev_type, file, 0, open_flags,
+ newdev)) >= 0)
+ {
+ dev = ret;
+ DDB (cmn_err (CE_CONT, "->%d ", dev));
+ break;
+ }
+ dev = -1;
+ }
+ break;
+ }
+
+ DDB (cmn_err (CE_CONT, " - got vdsp -> %d\n", dev));
+ if (dev == -1)
+ return OSS_EBUSY;
+
+done:
+/*
+ * Try to find which minor number matches this /dev/dsp# device. Note that the actual
+ * device type doesn't matter after this point so we can use OSS_DEV_DSP.
+ */
+ if ((d = oss_find_minor (OSS_DEV_DSP_ENGINE, dev)) < 0)
+ {
+ oss_audio_release (dev, file);
+ return d;
+ }
+
+ *newdev = d;
+ return dev;
+}
+#endif
+#endif
+
+static int
+audio_init_device (int dev)
+{
+ adev_p adev;
+
+ sync_seed = GET_JIFFIES ();
+ adev = audio_engines[dev];
+
+ MUTEX_INIT (adev->master_osdev, adev->mutex, MH_FRAMEW);
+
+ if (audio_engines[dev]->dmap_out == NULL
+ || audio_engines[dev]->dmap_in == NULL)
+ {
+ dmap_p dmap;
+
+ dmap = AUDIO_MALLOC (adev->osdev, sizeof (dmap_t));
+ if (dmap == NULL)
+ {
+ cmn_err (CE_WARN, "Failed to allocate dmap, dev=%d\n", dev);
+ return OSS_ENOMEM;
+ }
+
+ memset ((char *) dmap, 0, sizeof (dmap_t));
+
+ if (!(adev->flags & ADEV_NOOUTPUT) && adev->out_wq == NULL)
+ {
+ if ((adev->out_wq =
+ oss_create_wait_queue (adev->osdev, "audio_out")) == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot create audio output wait queue\n");
+ return OSS_ENOMEM;
+ }
+ }
+
+ if (!(adev->flags & ADEV_NOINPUT) && adev->in_wq == NULL)
+ {
+ if ((adev->in_wq =
+ oss_create_wait_queue (adev->osdev, "audio_in")) == NULL)
+ {
+ cmn_err (CE_WARN, "Cannot create audio input wait queue\n");
+ return OSS_ENOMEM;
+ }
+ }
+
+ memset ((char *) dmap, 0, sizeof (dmap_t));
+ dmap->osdev = adev->osdev;
+ dmap->adev = adev;
+ dmap->master_osdev = adev->master_osdev;
+ MUTEX_INIT (dmap->master_osdev, dmap->mutex, MH_FRAMEW + 1);
+ adev->dmap_out = adev->dmap_in = dmap;
+
+ if (adev->flags & ADEV_DUPLEX)
+ {
+
+ dmap = AUDIO_MALLOC (adev->osdev, sizeof (dmap_t));
+ if (dmap == NULL)
+ {
+ cmn_err (CE_WARN, "Failed to allocate dmap, dev=%d\n", dev);
+ return OSS_ENOMEM;
+ }
+
+ memset ((char *) dmap, 0, sizeof (dmap_t));
+ dmap->osdev = adev->osdev;
+ dmap->adev = adev;
+ dmap->master_osdev = adev->master_osdev;
+ MUTEX_INIT (dmap->master_osdev, dmap->mutex, MH_FRAMEW + 1);
+ adev->dmap_in = dmap;
+ }
+ }
+
+ return 0;
+}
+
+void
+audio_uninit_device (int dev)
+{
+ adev_p adev;
+ /* oss_native_word flags; */
+
+ adev = audio_engines[dev];
+
+ if (adev->unloaded)
+ return;
+
+ if (adev->dmap_out != NULL && adev->dmap_out->dmabuf != NULL)
+ {
+ if (adev->d->adrv_free_buffer != NULL)
+ {
+ adev->d->adrv_free_buffer (dev, adev->dmap_out, OPEN_WRITE);
+ }
+ else
+ default_free_buffer (dev, adev->dmap_out, OPEN_WRITE);
+ adev->dmap_out->dmabuf = NULL;
+ }
+
+ if (adev->dmap_in != NULL
+ && (adev->dmap_in != adev->dmap_out && adev->dmap_in->dmabuf != NULL))
+ {
+ if (adev->d->adrv_free_buffer != NULL)
+ {
+ adev->d->adrv_free_buffer (dev, adev->dmap_in, OPEN_READ);
+ }
+ else
+ default_free_buffer (dev, adev->dmap_in, OPEN_READ);
+ adev->dmap_in->dmabuf = NULL;
+ }
+
+ if (adev->in_wq != NULL)
+ {
+ oss_remove_wait_queue (adev->in_wq);
+ adev->in_wq = NULL;
+ }
+ if (adev->out_wq != NULL)
+ {
+ oss_remove_wait_queue (adev->out_wq);
+ adev->out_wq = NULL;
+ }
+
+#ifdef CONFIG_OSS_VMIX
+ if (adev->vmix_mixer != NULL)
+ {
+ adev->vmix_mixer = NULL;
+ }
+#endif
+
+ MUTEX_CLEANUP (adev->mutex);
+
+ if (adev->dmap_out != NULL)
+ MUTEX_CLEANUP (adev->dmap_out->mutex);
+
+ if (adev->flags & ADEV_DUPLEX && adev->dmap_in != NULL
+ && adev->dmap_out != adev->dmap_in)
+ {
+ MUTEX_CLEANUP (adev->dmap_in->mutex);
+ }
+ adev->unloaded = 1;
+}
+
+void
+oss_audio_init (oss_device_t *osdev)
+{
+ MUTEX_INIT (osdev, audio_global_mutex, MH_DRV);
+}
+
+void
+oss_audio_uninit (void)
+{
+/*
+ * Release all memory/resources allocated by the audio core.
+ */
+ oss_memblk_unalloc(&audio_global_memblk);
+
+ MUTEX_CLEANUP (audio_global_mutex);
+}
+
+void
+oss_audio_inc_byte_counter (dmap_t * dmap, int increment)
+{
+ oss_uint64_t p1, p2;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ p1 = dmap->byte_counter / dmap->fragment_size;
+ dmap->byte_counter += increment;
+ p2 = dmap->byte_counter / dmap->fragment_size;
+
+ dmap->interrupt_count += (int) (p2 - p1);
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+}
+
+static void
+do_inputintr (int dev, int intr_flags)
+{
+ adev_p adev;
+ dmap_p dmap;
+ int cptr;
+ oss_native_word flags;
+
+ adev = audio_engines[dev];
+ dmap = adev->dmap_in;
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+
+ if (!(intr_flags & AINTR_NO_POINTER_UPDATES))
+ {
+ dmap->byte_counter += dmap->fragment_size;
+ dmap->interrupt_count++;
+ }
+
+ while (dmap->byte_counter > dmap->user_counter &&
+ (int) (dmap->byte_counter - dmap->user_counter) > dmap->bytes_in_use)
+ {
+ dmap->user_counter += dmap->fragment_size;
+ dmap->rec_overruns++;
+ }
+ dmap->fragment_counter = (dmap->fragment_counter + 1) % dmap->nfrags;
+
+ if (dmap->dmabuf_dma_handle != NULL) /* Some drivers don't use DMA */
+ OSS_DMA_SYNC(dmap->dmabuf_dma_handle, 0, dmap->bytes_in_use, OSS_DMA_SYNC_INBOUND);
+
+#ifdef DO_TIMINGS
+ oss_do_timing ("Wake up (in)");
+#endif
+ oss_wakeup (adev->in_wq, &dmap->mutex, &flags, POLLIN | POLLRDNORM);
+
+ if (adev->flags & ADEV_AUTOMODE)
+ {
+ goto finish;
+ }
+
+ cptr = dmap_get_qtail (dmap) * dmap->fragment_size;
+ if (adev->d->adrv_start_input != NULL)
+ adev->d->adrv_start_input (adev->engine_num, dmap->dmabuf_phys + cptr,
+ dmap->fragment_size, dmap->fragment_size, 1);
+finish:
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ if (dmap->audio_callback != NULL)
+ dmap->audio_callback (adev->engine_num, dmap->callback_parm);
+}
+
+static void
+audio_inputintr (int dev, int intr_flags)
+{
+ adev_p adev;
+ dmap_p dmap;
+ int n;
+ oss_uint64_t pos;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return;
+
+ adev = audio_engines[dev];
+ dmap = adev->dmap_in;
+
+ if (dmap->dma_mode != PCM_ENABLE_INPUT)
+ {
+ return;
+ }
+
+ if (!(dmap->flags & DMAP_STARTED))
+ {
+ return;
+ }
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Inputintr(%d)", dev);
+#endif
+
+ if (adev->d->adrv_get_input_pointer == NULL
+ || (intr_flags & AINTR_NO_POINTER_UPDATES))
+ {
+ do_inputintr (dev, intr_flags);
+ return;
+ }
+
+ pos =
+ adev->d->adrv_get_input_pointer (adev->engine_num, dmap,
+ PCM_ENABLE_INPUT) / dmap->fragment_size;
+ n = 0;
+
+ while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags)
+ {
+ do_inputintr (dev, intr_flags);
+ }
+}
+
+static void
+finish_output_interrupt (adev_p adev, dmap_p dmap)
+{
+ if (dmap->dmabuf_dma_handle != NULL) /* Some drivers don't use DMA */
+ OSS_DMA_SYNC(dmap->dmabuf_dma_handle, 0, dmap->bytes_in_use, OSS_DMA_SYNC_OUTBOUND);
+
+#ifdef CONFIG_OSSD
+ ossd_event (adev->engine_num, OSSD_EV_UPDATE_OUTPUT);
+#endif
+ if (dmap->audio_callback != NULL)
+ {
+ dmap->audio_callback (adev->engine_num, dmap->callback_parm);
+ }
+}
+
+static void
+do_outputintr (int dev, int intr_flags)
+{
+ adev_p adev;
+ dmap_p dmap;
+ int cptr;
+ oss_native_word flags;
+
+ adev = audio_engines[dev];
+ dmap = adev->dmap_out;
+
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+
+ if (dmap->dmabuf == NULL)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ cmn_err (CE_WARN, "Output interrupt when no buffer is allocated\n");
+ return;
+ }
+
+ if (!(intr_flags & AINTR_NO_POINTER_UPDATES))
+ {
+ dmap->byte_counter += dmap->fragment_size;
+ dmap->interrupt_count++;
+ }
+
+ dmap->fragment_counter = (dmap->fragment_counter + 1) % dmap->nfrags;
+
+ if (dmap->user_counter <= dmap->byte_counter) /* Underrun */
+ {
+ if (!(dmap->mapping_flags & DMA_MAP_MAPPED))
+ {
+#ifdef DO_TIMINGS
+ oss_timing_printf ("adev %d: Play underrun A", dev);
+ oss_timing_printf (" User=%lld", dmap->user_counter);
+ oss_timing_printf (" Dev=%lld", dmap->byte_counter);
+ oss_timing_printf (" Tmp=%d", dmap->tmpbuf_ptr);
+#endif
+ dmap->play_underruns++;
+
+ if (!dmap->underrun_flag)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Clearing the buffer");
+#endif
+ memset (dmap->dmabuf, dmap->neutral_byte, dmap->buffsize);
+ }
+ dmap->underrun_flag = 1;
+#if 1
+ dmap->user_counter = dmap->byte_counter + dmap->fragment_size;
+#endif
+ }
+ }
+
+ if (audio_space_in_queue (adev, dmap, 0) >= 0 || adev->nonblock)
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Wake up (out)");
+#endif
+ oss_wakeup (adev->out_wq, &dmap->mutex, &flags, POLLOUT | POLLWRNORM);
+ }
+
+ if (adev->flags & ADEV_AUTOMODE)
+ {
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ finish_output_interrupt (adev, dmap);
+ return;
+ }
+
+ cptr = dmap_get_qhead (dmap) * dmap->fragment_size;
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+ if (adev->d->adrv_output_block != NULL)
+ {
+ adev->d->adrv_output_block (adev->engine_num, dmap->dmabuf_phys + cptr,
+ dmap->fragment_size, dmap->fragment_size,
+ 1);
+ }
+ finish_output_interrupt (adev, dmap);
+}
+
+static void
+audio_outputintr (int dev, int intr_flags)
+{
+ adev_p adev;
+ dmap_p dmap;
+ int n;
+ oss_uint64_t pos;
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return;
+
+ adev = audio_engines[dev];
+ dmap = adev->dmap_out;
+
+ if (dmap->dma_mode != PCM_ENABLE_OUTPUT)
+ {
+ return;
+ }
+
+ if (!(dmap->flags & DMAP_STARTED))
+ {
+ return;
+ }
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Outputintr(%d)", dev);
+#endif
+ UP_STATUS (STS_INTR);
+
+ if (adev->d->adrv_get_output_pointer == NULL
+ || (intr_flags & AINTR_NO_POINTER_UPDATES))
+ {
+ UP_STATUS (STS_DOINTR);
+ do_outputintr (dev, intr_flags);
+ DOWN_STATUS (STS_DOINTR | STS_INTR);
+ return;
+ }
+
+ pos = get_output_pointer (adev, dmap, 0) / dmap->fragment_size;
+ n = 0;
+
+ while (dmap_get_qhead (dmap) != pos && n++ < dmap->nfrags)
+ {
+ UP_STATUS (STS_DOINTR);
+ do_outputintr (dev, intr_flags);
+ DOWN_STATUS (STS_DOINTR);
+ }
+ DOWN_STATUS (STS_INTR);
+}
+
+void
+oss_audio_reset (int dev)
+{
+ adev_p adev;
+ if (dev < 0 || dev >= num_audio_engines)
+ return;
+ adev = audio_engines[dev];
+
+ audio_reset_adev (adev);
+
+ if (adev->dmask & DMASK_OUT)
+ reset_dmap (adev->dmap_out);
+ if (adev->dmask & DMASK_IN)
+ reset_dmap (adev->dmap_in);
+}
+
+void
+oss_audio_start_syncgroup (unsigned int syncgroup)
+{
+/*
+ * This routine is to be called by the /dev/midi driver to start a sync group
+ * at the right time.
+ */
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (audio_global_mutex, flags);
+ handle_syncstart (syncgroup & SYNC_DEVICE_MASK, syncgroup);
+ MUTEX_EXIT_IRQRESTORE (audio_global_mutex, flags);
+}
+
+#ifdef ALLOW_SELECT
+/*ARGSUSED*/
+static int
+chpoll_output (int dev, struct fileinfo *file, oss_poll_event_t * ev)
+{
+ short events = ev->events;
+ adev_p adev;
+ dmap_p dmap;
+ oss_native_word flags;
+ audio_buf_info bi;
+ int limit;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("--- audio_chpoll_output(%d, %08x) ---", dev, events);
+#endif
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+ adev = audio_engines[dev];
+
+ dmap = adev->dmap_out;
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ {
+#if 1
+ /*
+ * It might actually be better to permit pollling in mmapped
+ * mode rather than returning an error.
+ */
+ if (dmap->interrupt_count > 0)
+ ev->revents |= (POLLOUT | POLLWRNORM) & events;
+ return 0;
+#else
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1014,
+ "select/poll called for an OSS device in mmap mode."),
+ 0);
+ /*
+ * Errordesc:
+ * The select() and poll() system calls are not defined for OSS devices
+ * when the device is in mmap mode.
+ */
+ return OSS_EPERM;
+#endif
+ }
+
+ if (dmap->dma_mode == PCM_ENABLE_INPUT)
+ {
+ oss_audio_set_error (adev->engine_num, E_PLAY,
+ OSSERR (1015,
+ "select/poll called for wrong direction."),
+ 0);
+ /*
+ * Errordesc: The application has opened the device in read-only
+ * mode but it tried to use select/poll to check if the device is
+ * ready for write. This is pointless because that will never
+ * happen.
+ */
+ return 0;
+ }
+
+ get_ospace (audio_engines[dev], dmap, (ioctl_arg) & bi);
+ limit = bi.fragsize * bi.fragstotal / 4;
+ if (dmap->low_water > limit)
+ dmap->low_water = limit;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ if (bi.bytes < dmap->low_water)
+ { /* No space yet */
+#ifdef DO_TIMINGS
+ oss_do_timing ("Output not ready yet");
+#endif
+ oss_register_poll (adev->out_wq, &dmap->mutex, &flags, ev);
+ }
+ else
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Reporting output ready");
+#endif
+ ev->revents |= (POLLOUT | POLLWRNORM) & events;
+ }
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+chpoll_input (int dev, struct fileinfo *file, oss_poll_event_t * ev)
+{
+ short events = ev->events;
+ adev_p adev;
+ dmap_p dmap;
+ oss_native_word flags;
+ audio_buf_info bi;
+ int limit;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("--- audio_chpoll_input(%d, %08x) ---", dev, events);
+#endif
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+ adev = audio_engines[dev];
+
+ dmap = adev->dmap_in;
+ if (dmap->mapping_flags & DMA_MAP_MAPPED)
+ {
+#if 1
+ /*
+ * It might actually be better to permit pollling in mmapped
+ * mode rather than returning an error.
+ */
+ if (dmap->interrupt_count > 0)
+ ev->revents |= (POLLOUT | POLLWRNORM) & events;
+ return 0;
+#else
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1013,
+ "select/poll called for an OSS device in mmap mode."),
+ 0);
+ /*
+ * Errordesc:
+ * The select() and poll() system calls are not defined for OSS devices
+ * when the device is in mmap mode.
+ */
+ return OSS_EIO;
+#endif
+ }
+
+ if (dmap->dma_mode != PCM_ENABLE_INPUT)
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1016,
+ "select/poll called for wrong direction."),
+ 0);
+ /*
+ * Errordesc: The application has opened the device in write-only
+ * mode but it tried to use select/poll to check if the device has any
+ * data available to read. This is pointless because that will never
+ * happen.
+ */
+ return 0;
+ }
+
+ if (!(dmap->flags & DMAP_STARTED))
+ {
+ oss_audio_set_error (adev->engine_num, E_REC,
+ OSSERR (1017,
+ "select/poll called before recording is started."),
+ 0);
+ /*
+ * Errordesc:
+ * The application should start recording (using SNDCTL_DSP_SETTRIGGER)
+ * before calling select/poll to wait for recorded data. If recording is
+ * not active then recorded data will never arrive and the application
+ * will just sit and wait without doing anything.
+ */
+ }
+
+ get_ispace (audio_engines[dev], dmap, (ioctl_arg) & bi);
+ limit = bi.fragsize * bi.fragstotal / 4;
+ if (dmap->low_water > limit)
+ dmap->low_water = limit;
+ MUTEX_ENTER_IRQDISABLE (dmap->mutex, flags);
+ if (bi.bytes < dmap->low_water)
+ { /* No space yet */
+#ifdef DO_TIMINGS
+ oss_timing_printf ("Input not ready yet (%d/%d)\n", bi.bytes,
+ dmap->low_water);
+#endif
+ oss_register_poll (adev->in_wq, &dmap->mutex, &flags, ev);
+ }
+ else
+ {
+#ifdef DO_TIMINGS
+ oss_do_timing ("Reporting input ready");
+#endif
+ ev->revents |= (POLLIN | POLLRDNORM) & events;
+ }
+ MUTEX_EXIT_IRQRESTORE (dmap->mutex, flags);
+
+ return 0;
+}
+
+/*ARGSUSED*/
+int
+oss_audio_chpoll (int dev, struct fileinfo *file, oss_poll_event_t * ev)
+{
+ short events = ev->events;
+ adev_p adev;
+
+#ifdef DO_TIMINGS
+ oss_timing_printf ("--- audio_chpoll(%d, %08x) ---", dev, events);
+#endif
+
+ if (dev < 0 || dev >= num_audio_engines)
+ return OSS_ENXIO;
+ adev = audio_engines[dev];
+
+ if ((events & (POLLOUT | POLLWRNORM)) && (adev->open_mode & OPEN_WRITE))
+ {
+ int err;
+
+ if ((err=chpoll_output (dev, file, ev))<0)
+ return err;
+ }
+
+ if ((events & (POLLIN | POLLRDNORM)) && (adev->open_mode & OPEN_READ))
+ {
+ int err;
+
+ if ((err=chpoll_input (dev, file, ev))<0)
+ return err;
+ }
+
+ return 0;
+}
+#endif
+
+/*ARGSUSED*/
+static void
+dummy_inputintr (int dev, int intr_flags)
+{
+ /* Dummy input interrupt handler */
+}
+
+/*ARGSUSED*/
+static void
+dummy_outputintr (int dev, int x)
+{
+ /* Dummy output interrupt handler */
+}
+
+static oss_cdev_drv_t audio_cdev_drv = {
+#ifdef VDEV_SUPPORT
+ oss_audio_open_devfile,
+#else
+ oss_audio_open_engine,
+#endif
+ oss_audio_release,
+ oss_audio_read,
+ oss_audio_write,
+ oss_audio_ioctl,
+#ifdef ALLOW_SELECT
+ oss_audio_chpoll
+#else
+ NULL
+#endif
+};
+
+#ifdef MANAGE_DEV_DSP
+#ifdef VDEV_SUPPORT
+static oss_cdev_drv_t vdsp_cdev_drv = {
+ oss_open_vdsp,
+ oss_audio_release,
+ oss_audio_read,
+ oss_audio_write,
+ oss_audio_ioctl,
+#ifdef ALLOW_SELECT
+ oss_audio_chpoll
+#else
+ NULL
+#endif
+};
+#endif
+#endif
+
+static oss_cdev_drv_t audio_engine_cdev_drv = {
+ oss_audio_open_engine,
+ oss_audio_release,
+ oss_audio_read,
+ oss_audio_write,
+ oss_audio_ioctl,
+#ifdef ALLOW_SELECT
+ oss_audio_chpoll
+#else
+ NULL
+#endif
+};
+
+void
+oss_audio_register_client (oss_audio_startup_func func, void *devc,
+ oss_device_t * osdev)
+{
+/*
+ * Register a callback for a driver that wants to be initialized after a new audio device
+ * is installed.
+ */
+
+ if (num_audio_startups >= MAX_STARTUPS)
+ return;
+
+ audio_startups[num_audio_startups].func = func;
+ audio_startups[num_audio_startups].devc = devc;
+ audio_startups[num_audio_startups].osdev = osdev;
+ num_audio_startups++;
+}
+
+void
+oss_add_audio_devlist (int list_id, int devfile)
+{
+#if 0
+/*
+ * Device list support is not in use at this moment
+ */
+ oss_devlist_t *list;
+ int i;
+
+ switch (list_id)
+ {
+ case OPEN_READ:
+ list = &dspinlist;
+ break;
+
+ case OPEN_WRITE:
+ list = &dspoutlist;
+ break;
+
+ case OPEN_READ | OPEN_WRITE:
+ list = &dspinoutlist;
+ break;
+
+ default:
+ cmn_err (CE_CONT, "Bad device list ID %d\n", list_id);
+ return;
+ }
+
+ for (i = 0; i < list->ndevs; i++)
+ if (list->devices[i] == devfile) /* Already there */
+ return;
+
+ /*
+ * Add the device to the list
+ */
+ list->devices[list->ndevs++] = devfile;
+#endif
+}
+
+#if 0
+static void
+add_to_devlists (adev_p adev)
+{
+/*
+ * Add the device to the default input, output and/or input/output lists
+ * for /dev/dsp.
+ */
+
+ if (!(adev->flags & ADEV_SPECIAL))
+ {
+ if (!(adev->flags & ADEV_NOOUTPUT))
+ { /* Output capable */
+
+ if (adev->flags & ADEV_VIRTUAL)
+ {
+ int n = dspoutlist.ndevs++;
+
+ if (adev->flags & ADEV_DEFAULT)
+ {
+ int i;
+
+ for (i = n; i > 0; i--)
+ dspoutlist.devices[i] = dspoutlist.devices[i - 1];
+ dspoutlist.devices[0] = adev->audio_devfile;
+ }
+ else
+ dspoutlist.devices[n] = adev->audio_devfile;
+ }
+ else
+ {
+ /* Put non-virtual devices to a secondary list */
+ int n = dspoutlist2.ndevs++;
+ dspoutlist2.devices[n] = adev->audio_devfile;
+ }
+
+ }
+
+ if (!(adev->flags & (ADEV_NOINPUT)))
+ { /* Input capable */
+
+ int n = dspinlist.ndevs++;
+
+ if (adev->flags & ADEV_DEFAULT)
+ {
+ int i;
+
+ for (i = n; i > 0; i--)
+ dspinlist.devices[i] = dspinlist.devices[i - 1];
+ dspinlist.devices[0] = adev->audio_devfile;
+ }
+ else
+ dspinlist.devices[n] = adev->audio_devfile;
+ }
+
+ if (!(adev->flags & (ADEV_NOINPUT | ADEV_NOOUTPUT))
+ || (adev->flags & ADEV_DUPLEX))
+ { /* Input/output capable */
+
+ int n = dspinoutlist.ndevs++;
+
+ if (adev->flags & ADEV_DEFAULT)
+ {
+ int i;
+
+ for (i = n; i > 0; i--)
+ dspinoutlist.devices[i] = dspinoutlist.devices[i - 1];
+ dspinoutlist.devices[0] = adev->audio_devfile;
+ }
+ else
+ dspinoutlist.devices[n] = adev->audio_devfile;
+ }
+ }
+}
+#endif
+
+static int
+resize_array(oss_device_t *osdev, adev_t ***arr, int *size, int increment)
+{
+ adev_t **old=*arr, **new = *arr;
+ int old_size = *size;
+ int new_size = *size;
+
+ if (new_size >= HARD_MAX_AUDIO_DEVFILES) /* Too many device files */
+ return 0;
+
+ new_size += increment;
+ if (new_size > HARD_MAX_AUDIO_DEVFILES)
+ new_size = HARD_MAX_AUDIO_DEVFILES;
+
+ if ((new=AUDIO_MALLOC(osdev, new_size * sizeof (adev_t *)))==NULL)
+ return 0;
+
+ memset(new, 0, new_size * sizeof(adev_t *));
+ if (old != NULL)
+ memcpy(new, old, old_size * sizeof(adev_t *));
+
+ *size = new_size;
+ *arr = new;
+
+ if (old != NULL)
+ AUDIO_FREE(osdev, old);
+
+ return 1;
+}
+
+int
+oss_install_audiodev_with_devname (int vers,
+ oss_device_t * osdev,
+ oss_device_t * master_osdev,
+ char *name,
+ const audiodrv_t * driver,
+ int driver_size,
+ int flags,
+ unsigned int format_mask, void *devc, int parent,
+ const char * devfile_name)
+{
+ audiodrv_t *d;
+ adev_t *op;
+ int i, num = -1, hidden_device = 0;
+ int update_devlists = 0;
+ int reinsterted_device = 0;
+ int chdev_flags = 0;
+ int devfile_num = 0;
+
+ if (devc == NULL)
+ {
+ cmn_err(CE_WARN, "devc==NULL for %s. Cannot install audio device\n", name);
+ return OSS_EINVAL;
+ }
+
+ if (name == NULL)
+ cmn_err (CE_CONT, "Name is really NULL\n");
+ if (master_osdev == NULL)
+ master_osdev = osdev;
+
+#ifdef VDEV_SUPPORT
+ if (flags & (ADEV_SHADOW | ADEV_HIDDEN))
+ hidden_device = 1;
+#else
+ flags &= ~ADEV_HIDDEN;
+ hidden_device = 0;
+#endif
+
+ if (audio_engines == NULL)
+ {
+ audio_engines = AUDIO_MALLOC (NULL, sizeof (adev_t *) * MAX_AUDIO_ENGINES);
+ memset (audio_engines, 0, sizeof (adev_t *) * MAX_AUDIO_ENGINES);
+ }
+
+ if (audio_devfiles == NULL)
+ {
+ audio_devfiles = AUDIO_MALLOC (NULL, sizeof (adev_t *) * MAX_AUDIO_DEVFILES);
+ memset (audio_devfiles, 0, sizeof (adev_t *) * MAX_AUDIO_DEVFILES);
+ }
+
+ if (vers != OSS_AUDIO_DRIVER_VERSION)
+ {
+ cmn_err (CE_WARN, "Incompatible audio driver for %s\n", name);
+ return OSS_EIO;
+ }
+
+ if (driver_size > sizeof (audiodrv_t))
+ driver_size = sizeof (audiodrv_t);
+/*
+ * Try to figure out if this device has earlier been installed. Sometimes it may happen that
+ * the low level driver gets unloaded while the osscore module still
+ * remains loaded. Try to re-use the same audio device number if possible.
+ */
+
+ num = -1;
+
+ for (i = 0; i < num_audio_engines; i++)
+ {
+
+ if ((audio_engines[i]->flags & (ADEV_SHADOW|ADEV_HIDDEN)) !=
+ (flags & (ADEV_SHADOW|ADEV_HIDDEN))) /* Different visibility */
+ continue;
+
+ if (audio_engines[i]->unloaded
+ && audio_engines[i]->os_id == oss_get_osid (osdev))
+ {
+ /* This audio device was previously connected to the same physical device */
+
+ num = i;
+ reinsterted_device = 1;
+ DDB (cmn_err
+ (CE_NOTE, "Audio device %d got re-installed again.\n", i));
+ break;
+ }
+ }
+
+ if (num == -1)
+ {
+
+ if (num_audio_engines >= MAX_AUDIO_ENGINES)
+ {
+ if (!resize_array(osdev, &audio_engines, &oss_max_audio_engines, AUDIO_ENGINE_INCREMENT))
+ {
+ cmn_err (CE_CONT, "Cannot grow audio_engines[]\n");
+ return OSS_EIO;
+ }
+ }
+
+ d = AUDIO_MALLOC (osdev, sizeof (audiodrv_t));
+ op = AUDIO_MALLOC (osdev, sizeof (adev_t));
+ memset ((char *) op, 0, sizeof (adev_t));
+ if (driver_size < sizeof (audiodrv_t))
+ memset ((char *) d, 0, sizeof (audiodrv_t));
+ memcpy ((char *) d, (char *) driver, driver_size);
+ num = num_audio_engines;
+ audio_engines[num] = op;
+ num_audio_engines++;
+
+ update_devlists = 1;
+
+ sprintf (op->handle, "%s-au%02d", osdev->handle,
+ osdev->num_audio_engines + 1);
+ op->port_number = osdev->num_audio_engines;
+ }
+ else
+ {
+ op = audio_engines[num];
+ d = audio_engines[num]->d;
+ if (driver_size < sizeof (audiodrv_t))
+ memset ((char *) d, 0, sizeof (audiodrv_t));
+ memcpy ((char *) d, (char *) driver, driver_size);
+ update_devlists = 0;
+ }
+
+ if (d == NULL || op == NULL)
+ {
+ cmn_err (CE_WARN, "Can't allocate driver for %s (adev=%p, d=%p)\n",
+ name, op, d);
+ return OSS_ENOSPC;
+ }
+
+ if (d->adrv_get_input_pointer == NULL)
+ d->adrv_get_input_pointer = d->adrv_get_buffer_pointer;
+ if (d->adrv_get_output_pointer == NULL)
+ d->adrv_get_output_pointer = d->adrv_get_buffer_pointer;
+
+ op->d = d;
+ op->os_id = oss_get_osid (osdev);
+ op->enabled = 0;
+ op->outputintr = dummy_outputintr;
+ op->inputintr = dummy_inputintr;
+ op->vmix_mixer = NULL;
+
+ strncpy (op->name, name, sizeof (op->name));
+ op->name[sizeof (op->name) - 1] = 0;
+ op->caps = 0;
+
+ op->flags = flags;
+ op->latency = -1; /* Unknown */
+
+ op->oformat_mask = format_mask;
+ op->iformat_mask = format_mask;
+ op->mixer_dev = -1;
+ op->min_channels = 1;
+ op->max_channels = 2;
+ op->min_rate = 8000;
+ op->max_rate = 44100;
+ op->devc = devc;
+ op->parent_dev = parent + 1;
+ op->dmap_in = NULL;
+ op->dmap_out = NULL;
+ memset (op->cmd, 0, sizeof (op->cmd));
+ op->pid = -1;
+ op->osdev = osdev;
+ op->master_osdev = master_osdev;
+ op->card_number = osdev->cardnum;
+ op->engine_num = op->rate_source = num;
+ op->real_dev = num;
+ op->redirect_in = -1;
+ op->redirect_out = -1;
+ op->dmabuf_alloc_flags = 0;
+ op->dmabuf_maxaddr = MEMLIMIT_32BITS;
+
+ audio_engines[num] = op;
+ op->next_out = NULL;
+ op->next_in = NULL;
+
+ op->song_name[0] = 0;
+ op->label[0] = 0;
+ op->nrates=0;
+
+ if ((flags & ADEV_SHADOW) && num > 0)
+ {
+ adev_p prev = audio_engines[num - 1]; /* Previous device */
+
+ prev->next_out = op;
+ prev->next_in = op;
+ }
+
+ if (audio_init_device (num) < 0)
+ return OSS_ENOMEM;
+
+/*
+ * Create the device node.
+ */
+#ifndef VDEV_SUPPORT
+ flat_device_model = 1;
+ hidden_device = 0;
+#endif
+
+ if (reinsterted_device)
+ {
+ /*
+ * A hotpluggable device has been reinserted in the system.
+ * Update the internal tables and re-create the device file entry.
+ * However don't create new audio engine and device file numbers.
+ */
+ devfile_num = op->audio_devfile;
+ chdev_flags |= CHDEV_REPLUG;
+ }
+ else
+ {
+ if (!hidden_device)
+ {
+ if (num_audio_devfiles >= MAX_AUDIO_DEVFILES)
+ {
+ if (!resize_array(osdev, &audio_devfiles, &oss_max_audio_devfiles, AUDIO_DEVFILE_INCREMENT))
+ {
+ cmn_err (CE_CONT, "Cannot grow audio_devfiles[]\n");
+ return num;
+ }
+ }
+
+ audio_devfiles[num_audio_devfiles] = op;
+ devfile_num = num_audio_devfiles++;
+ }
+ }
+
+ if (flat_device_model || !hidden_device)
+ {
+#ifdef NEW_DEVICE_NAMING
+ oss_devnode_t name;
+ char tmpl[32];
+
+ if (*devfile_name != 0)
+ {
+ /*
+ * A name was suggested by the low level driver
+ */
+ strcpy (tmpl, devfile_name);
+ }
+ else if (flags & ADEV_NOOUTPUT)
+ sprintf (tmpl, "pcmin%d", osdev->num_audiorec++);
+ else
+ sprintf (tmpl, "pcm%d", osdev->num_audioduplex++);
+
+# ifdef USE_DEVICE_SUBDIRS
+ sprintf (name, "oss/%s/%s", osdev->nick, tmpl);
+# else
+ sprintf (name, "%s_%s", osdev->nick, tmpl);
+# endif
+#else
+ sprintf (name, "dsp%d", num);
+#endif
+
+ op->real_dev = devfile_num;
+ op->audio_devfile = devfile_num;
+ audio_devfiles[devfile_num] = op;
+ sprintf (op->devnode, "/dev/%s", name);
+ oss_install_chrdev (master_osdev, name, OSS_DEV_DSP, devfile_num,
+ &audio_cdev_drv, chdev_flags);
+ osdev->num_audio_engines++;
+
+ op->enabled = 1;
+ op->unloaded = 0;
+
+ }
+ else
+ {
+ op->real_dev = -1;
+ if (devfile_num > 0 && (flags & ADEV_SHADOW))
+ {
+ /* Use the device node of the parent device file */
+ if (audio_devfiles[devfile_num - 1] != NULL)
+ strcpy (op->devnode, audio_devfiles[devfile_num - 1]->devnode);
+ else
+ strcpy (op->devnode, "Unknown");
+ audio_devfiles[devfile_num] = op;
+ }
+ else
+ {
+ strcpy (op->devnode, "HiddenAudioDevice");
+ }
+ op->enabled = 1;
+ op->unloaded = 0;
+ }
+
+//#ifdef VDEV_SUPPORT
+ oss_install_chrdev (osdev, NULL, OSS_DEV_DSP_ENGINE, num,
+ &audio_engine_cdev_drv, chdev_flags);
+//#endif
+
+#if 0
+ if (!reinsterted_device && update_devlists && !hidden_device)
+ add_to_devlists (op);
+#endif
+
+ {
+ int i;
+ for (i = 0; i < num_audio_engines; i++)
+ if (audio_engines[i] == NULL)
+ cmn_err (CE_WARN, "Audio engines %d==NULL\n", i);
+ for (i = 0; i < num_audio_devfiles; i++)
+ if (audio_devfiles[i] == NULL)
+ cmn_err (CE_WARN, "Audio devfiles %d==NULL\n", i);
+ }
+
+ return num;
+}
+
+int
+oss_install_audiodev (int vers,
+ oss_device_t * osdev,
+ oss_device_t * master_osdev,
+ char *name,
+ const audiodrv_t * driver,
+ int driver_size,
+ unsigned long long flags,
+ unsigned int format_mask, void *devc, int parent)
+{
+ return oss_install_audiodev_with_devname (vers,
+ osdev,
+ master_osdev,
+ name,
+ driver,
+ driver_size,
+ flags,
+ format_mask, devc, parent,
+ ""); /* Use default device file naming */
+}
+
+void
+oss_audio_delayed_attach (void)
+{
+ int i;
+
+ /*
+ * Serve possible other drivers waiting for new friends.
+ */
+ for (i = 0; i < num_audio_startups; i++)
+ {
+ if (audio_startups[i].osdev != NULL
+ && audio_startups[i].osdev->available)
+ {
+ if (audio_startups[i].func (audio_startups[i].devc))
+ audio_startups[i].osdev = NULL; /* Inactivate it */
+ }
+ }
+}
+
+/*ARGSUSED*/
+void
+install_vdsp (oss_device_t * osdev)
+{
+#ifdef MANAGE_DEV_DSP
+/*
+ * Install the driver for /dev/dsp (the multiplexer device)
+ *
+ * NOTE! Not used at this moment because /dev/dsp assignment is managed in user
+ * space (by ossdevlinks).
+ */
+#ifdef VDEV_SUPPORT
+ oss_install_chrdev (osdev, "dsp", OSS_DEV_VDSP, 0, &vdsp_cdev_drv,
+ CHDEV_VIRTUAL);
+ oss_install_chrdev (osdev, "dsp_in", OSS_DEV_VDSP, 1, &vdsp_cdev_drv,
+ CHDEV_VIRTUAL);
+ oss_install_chrdev (osdev, "dsp_out", OSS_DEV_VDSP, 2, &vdsp_cdev_drv,
+ CHDEV_VIRTUAL);
+#endif
+#endif
+}