aboutsummaryrefslogtreecommitdiff
path: root/sound/core/oss/pcm_oss.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/core/oss/pcm_oss.c')
-rw-r--r--sound/core/oss/pcm_oss.c2530
1 files changed, 2530 insertions, 0 deletions
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
new file mode 100644
index 00000000000..1a805020f57
--- /dev/null
+++ b/sound/core/oss/pcm_oss.c
@@ -0,0 +1,2530 @@
+/*
+ * Digital Audio (PCM) abstract layer / OSS compatible
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#if 0
+#define PLUGIN_DEBUG
+#endif
+#if 0
+#define OSS_DEBUG
+#endif
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include <sound/minors.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "pcm_plugin.h"
+#include <sound/info.h>
+#include <linux/soundcard.h>
+#include <sound/initval.h>
+
+#define OSS_ALSAEMULVER _SIOR ('M', 249, int)
+
+static int dsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 0};
+static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1};
+static int nonblock_open = 1;
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Abramo Bagnara <abramo@alsa-project.org>");
+MODULE_DESCRIPTION("PCM OSS emulation for ALSA.");
+MODULE_LICENSE("GPL");
+module_param_array(dsp_map, int, NULL, 0444);
+MODULE_PARM_DESC(dsp_map, "PCM device number assigned to 1st OSS device.");
+module_param_array(adsp_map, int, NULL, 0444);
+MODULE_PARM_DESC(adsp_map, "PCM device number assigned to 2nd OSS device.");
+module_param(nonblock_open, bool, 0644);
+MODULE_PARM_DESC(nonblock_open, "Don't block opening busy PCM devices.");
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM);
+MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM1);
+
+extern int snd_mixer_oss_ioctl_card(snd_card_t *card, unsigned int cmd, unsigned long arg);
+static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file);
+static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file);
+static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file);
+
+static inline mm_segment_t snd_enter_user(void)
+{
+ mm_segment_t fs = get_fs();
+ set_fs(get_ds());
+ return fs;
+}
+
+static inline void snd_leave_user(mm_segment_t fs)
+{
+ set_fs(fs);
+}
+
+static int snd_pcm_oss_plugin_clear(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_plugin_t *plugin, *next;
+
+ plugin = runtime->oss.plugin_first;
+ while (plugin) {
+ next = plugin->next;
+ snd_pcm_plugin_free(plugin);
+ plugin = next;
+ }
+ runtime->oss.plugin_first = runtime->oss.plugin_last = NULL;
+ return 0;
+}
+
+static int snd_pcm_plugin_insert(snd_pcm_plugin_t *plugin)
+{
+ snd_pcm_runtime_t *runtime = plugin->plug->runtime;
+ plugin->next = runtime->oss.plugin_first;
+ plugin->prev = NULL;
+ if (runtime->oss.plugin_first) {
+ runtime->oss.plugin_first->prev = plugin;
+ runtime->oss.plugin_first = plugin;
+ } else {
+ runtime->oss.plugin_last =
+ runtime->oss.plugin_first = plugin;
+ }
+ return 0;
+}
+
+int snd_pcm_plugin_append(snd_pcm_plugin_t *plugin)
+{
+ snd_pcm_runtime_t *runtime = plugin->plug->runtime;
+ plugin->next = NULL;
+ plugin->prev = runtime->oss.plugin_last;
+ if (runtime->oss.plugin_last) {
+ runtime->oss.plugin_last->next = plugin;
+ runtime->oss.plugin_last = plugin;
+ } else {
+ runtime->oss.plugin_last =
+ runtime->oss.plugin_first = plugin;
+ }
+ return 0;
+}
+
+static long snd_pcm_oss_bytes(snd_pcm_substream_t *substream, long frames)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ frames = frames_to_bytes(runtime, frames);
+ if (buffer_size == runtime->oss.buffer_bytes)
+ return frames;
+ return (runtime->oss.buffer_bytes * frames) / buffer_size;
+}
+
+static long snd_pcm_alsa_frames(snd_pcm_substream_t *substream, long bytes)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_uframes_t buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ if (buffer_size == runtime->oss.buffer_bytes)
+ return bytes_to_frames(runtime, bytes);
+ return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes);
+}
+
+static int snd_pcm_oss_format_from(int format)
+{
+ switch (format) {
+ case AFMT_MU_LAW: return SNDRV_PCM_FORMAT_MU_LAW;
+ case AFMT_A_LAW: return SNDRV_PCM_FORMAT_A_LAW;
+ case AFMT_IMA_ADPCM: return SNDRV_PCM_FORMAT_IMA_ADPCM;
+ case AFMT_U8: return SNDRV_PCM_FORMAT_U8;
+ case AFMT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE;
+ case AFMT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE;
+ case AFMT_S8: return SNDRV_PCM_FORMAT_S8;
+ case AFMT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE;
+ case AFMT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE;
+ case AFMT_MPEG: return SNDRV_PCM_FORMAT_MPEG;
+ default: return SNDRV_PCM_FORMAT_U8;
+ }
+}
+
+static int snd_pcm_oss_format_to(int format)
+{
+ switch (format) {
+ case SNDRV_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW;
+ case SNDRV_PCM_FORMAT_A_LAW: return AFMT_A_LAW;
+ case SNDRV_PCM_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM;
+ case SNDRV_PCM_FORMAT_U8: return AFMT_U8;
+ case SNDRV_PCM_FORMAT_S16_LE: return AFMT_S16_LE;
+ case SNDRV_PCM_FORMAT_S16_BE: return AFMT_S16_BE;
+ case SNDRV_PCM_FORMAT_S8: return AFMT_S8;
+ case SNDRV_PCM_FORMAT_U16_LE: return AFMT_U16_LE;
+ case SNDRV_PCM_FORMAT_U16_BE: return AFMT_U16_BE;
+ case SNDRV_PCM_FORMAT_MPEG: return AFMT_MPEG;
+ default: return -EINVAL;
+ }
+}
+
+static int snd_pcm_oss_period_size(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *oss_params,
+ snd_pcm_hw_params_t *slave_params)
+{
+ size_t s;
+ size_t oss_buffer_size, oss_period_size, oss_periods;
+ size_t min_period_size, max_period_size;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ size_t oss_frame_size;
+
+ oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) *
+ params_channels(oss_params) / 8;
+
+ oss_buffer_size = snd_pcm_plug_client_size(substream,
+ snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL)) * oss_frame_size;
+ oss_buffer_size = 1 << ld2(oss_buffer_size);
+ if (atomic_read(&runtime->mmap_count)) {
+ if (oss_buffer_size > runtime->oss.mmap_bytes)
+ oss_buffer_size = runtime->oss.mmap_bytes;
+ }
+
+ if (substream->oss.setup &&
+ substream->oss.setup->period_size > 16)
+ oss_period_size = substream->oss.setup->period_size;
+ else if (runtime->oss.fragshift) {
+ oss_period_size = 1 << runtime->oss.fragshift;
+ if (oss_period_size > oss_buffer_size / 2)
+ oss_period_size = oss_buffer_size / 2;
+ } else {
+ int sd;
+ size_t bytes_per_sec = params_rate(oss_params) * snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8;
+
+ oss_period_size = oss_buffer_size;
+ do {
+ oss_period_size /= 2;
+ } while (oss_period_size > bytes_per_sec);
+ if (runtime->oss.subdivision == 0) {
+ sd = 4;
+ if (oss_period_size / sd > 4096)
+ sd *= 2;
+ if (oss_period_size / sd < 4096)
+ sd = 1;
+ } else
+ sd = runtime->oss.subdivision;
+ oss_period_size /= sd;
+ if (oss_period_size < 16)
+ oss_period_size = 16;
+ }
+
+ min_period_size = snd_pcm_plug_client_size(substream,
+ snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL));
+ min_period_size *= oss_frame_size;
+ min_period_size = 1 << (ld2(min_period_size - 1) + 1);
+ if (oss_period_size < min_period_size)
+ oss_period_size = min_period_size;
+
+ max_period_size = snd_pcm_plug_client_size(substream,
+ snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL));
+ max_period_size *= oss_frame_size;
+ max_period_size = 1 << ld2(max_period_size);
+ if (oss_period_size > max_period_size)
+ oss_period_size = max_period_size;
+
+ oss_periods = oss_buffer_size / oss_period_size;
+
+ if (substream->oss.setup) {
+ if (substream->oss.setup->periods > 1)
+ oss_periods = substream->oss.setup->periods;
+ }
+
+ s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL);
+ if (runtime->oss.maxfrags && s > runtime->oss.maxfrags)
+ s = runtime->oss.maxfrags;
+ if (oss_periods > s)
+ oss_periods = s;
+
+ s = snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL);
+ if (s < 2)
+ s = 2;
+ if (oss_periods < s)
+ oss_periods = s;
+
+ while (oss_period_size * oss_periods > oss_buffer_size)
+ oss_period_size /= 2;
+
+ snd_assert(oss_period_size >= 16, return -EINVAL);
+ runtime->oss.period_bytes = oss_period_size;
+ runtime->oss.period_frames = 1;
+ runtime->oss.periods = oss_periods;
+ return 0;
+}
+
+static int choose_rate(snd_pcm_substream_t *substream,
+ snd_pcm_hw_params_t *params, unsigned int best_rate)
+{
+ snd_interval_t *it;
+ snd_pcm_hw_params_t *save;
+ unsigned int rate, prev;
+
+ save = kmalloc(sizeof(*save), GFP_KERNEL);
+ if (save == NULL)
+ return -ENOMEM;
+ *save = *params;
+ it = hw_param_interval(save, SNDRV_PCM_HW_PARAM_RATE);
+
+ /* try multiples of the best rate */
+ rate = best_rate;
+ for (;;) {
+ if (it->max < rate || (it->max == rate && it->openmax))
+ break;
+ if (it->min < rate || (it->min == rate && !it->openmin)) {
+ int ret;
+ ret = snd_pcm_hw_param_set(substream, params,
+ SNDRV_PCM_HW_PARAM_RATE,
+ rate, 0);
+ if (ret == (int)rate) {
+ kfree(save);
+ return rate;
+ }
+ *params = *save;
+ }
+ prev = rate;
+ rate += best_rate;
+ if (rate <= prev)
+ break;
+ }
+
+ /* not found, use the nearest rate */
+ kfree(save);
+ return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL);
+}
+
+static int snd_pcm_oss_change_params(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_hw_params_t *params, *sparams;
+ snd_pcm_sw_params_t *sw_params;
+ ssize_t oss_buffer_size, oss_period_size;
+ size_t oss_frame_size;
+ int err;
+ int direct;
+ int format, sformat, n;
+ snd_mask_t sformat_mask;
+ snd_mask_t mask;
+
+ sw_params = kmalloc(sizeof(*sw_params), GFP_KERNEL);
+ params = kmalloc(sizeof(*params), GFP_KERNEL);
+ sparams = kmalloc(sizeof(*sparams), GFP_KERNEL);
+ if (!sw_params || !params || !sparams) {
+ snd_printd("No memory\n");
+ err = -ENOMEM;
+ goto failure;
+ }
+
+ if (atomic_read(&runtime->mmap_count)) {
+ direct = 1;
+ } else {
+ snd_pcm_oss_setup_t *setup = substream->oss.setup;
+ direct = (setup != NULL && setup->direct);
+ }
+
+ _snd_pcm_hw_params_any(sparams);
+ _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS);
+ _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0);
+ snd_mask_none(&mask);
+ if (atomic_read(&runtime->mmap_count))
+ snd_mask_set(&mask, SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+ else {
+ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+ if (!direct)
+ snd_mask_set(&mask, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED);
+ }
+ err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask);
+ if (err < 0) {
+ snd_printd("No usable accesses\n");
+ err = -EINVAL;
+ goto failure;
+ }
+ choose_rate(substream, sparams, runtime->oss.rate);
+ snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_CHANNELS, runtime->oss.channels, NULL);
+
+ format = snd_pcm_oss_format_from(runtime->oss.format);
+
+ sformat_mask = *hw_param_mask(sparams, SNDRV_PCM_HW_PARAM_FORMAT);
+ if (direct)
+ sformat = format;
+ else
+ sformat = snd_pcm_plug_slave_format(format, &sformat_mask);
+
+ if (sformat < 0 || !snd_mask_test(&sformat_mask, sformat)) {
+ for (sformat = 0; sformat <= SNDRV_PCM_FORMAT_LAST; sformat++) {
+ if (snd_mask_test(&sformat_mask, sformat) &&
+ snd_pcm_oss_format_to(sformat) >= 0)
+ break;
+ }
+ if (sformat > SNDRV_PCM_FORMAT_LAST) {
+ snd_printd("Cannot find a format!!!\n");
+ err = -EINVAL;
+ goto failure;
+ }
+ }
+ err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, sformat, 0);
+ snd_assert(err >= 0, goto failure);
+
+ if (direct) {
+ memcpy(params, sparams, sizeof(*params));
+ } else {
+ _snd_pcm_hw_params_any(params);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
+ SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
+ snd_pcm_oss_format_from(runtime->oss.format), 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+ runtime->oss.channels, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
+ runtime->oss.rate, 0);
+ pdprintf("client: access = %i, format = %i, channels = %i, rate = %i\n",
+ params_access(params), params_format(params),
+ params_channels(params), params_rate(params));
+ }
+ pdprintf("slave: access = %i, format = %i, channels = %i, rate = %i\n",
+ params_access(sparams), params_format(sparams),
+ params_channels(sparams), params_rate(sparams));
+
+ oss_frame_size = snd_pcm_format_physical_width(params_format(params)) *
+ params_channels(params) / 8;
+
+ snd_pcm_oss_plugin_clear(substream);
+ if (!direct) {
+ /* add necessary plugins */
+ snd_pcm_oss_plugin_clear(substream);
+ if ((err = snd_pcm_plug_format_plugins(substream,
+ params,
+ sparams)) < 0) {
+ snd_printd("snd_pcm_plug_format_plugins failed: %i\n", err);
+ snd_pcm_oss_plugin_clear(substream);
+ goto failure;
+ }
+ if (runtime->oss.plugin_first) {
+ snd_pcm_plugin_t *plugin;
+ if ((err = snd_pcm_plugin_build_io(substream, sparams, &plugin)) < 0) {
+ snd_printd("snd_pcm_plugin_build_io failed: %i\n", err);
+ snd_pcm_oss_plugin_clear(substream);
+ goto failure;
+ }
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ err = snd_pcm_plugin_append(plugin);
+ } else {
+ err = snd_pcm_plugin_insert(plugin);
+ }
+ if (err < 0) {
+ snd_pcm_oss_plugin_clear(substream);
+ goto failure;
+ }
+ }
+ }
+
+ err = snd_pcm_oss_period_size(substream, params, sparams);
+ if (err < 0)
+ goto failure;
+
+ n = snd_pcm_plug_slave_size(substream, runtime->oss.period_bytes / oss_frame_size);
+ err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, n, NULL);
+ snd_assert(err >= 0, goto failure);
+
+ err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS,
+ runtime->oss.periods, NULL);
+ snd_assert(err >= 0, goto failure);
+
+ snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+
+ if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, sparams)) < 0) {
+ snd_printd("HW_PARAMS failed: %i\n", err);
+ goto failure;
+ }
+
+ memset(sw_params, 0, sizeof(*sw_params));
+ if (runtime->oss.trigger) {
+ sw_params->start_threshold = 1;
+ } else {
+ sw_params->start_threshold = runtime->boundary;
+ }
+ if (atomic_read(&runtime->mmap_count) || substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ sw_params->stop_threshold = runtime->boundary;
+ else
+ sw_params->stop_threshold = runtime->buffer_size;
+ sw_params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+ sw_params->period_step = 1;
+ sw_params->sleep_min = 0;
+ sw_params->avail_min = 1;
+ sw_params->xfer_align = 1;
+ if (atomic_read(&runtime->mmap_count) ||
+ (substream->oss.setup && substream->oss.setup->nosilence)) {
+ sw_params->silence_threshold = 0;
+ sw_params->silence_size = 0;
+ } else {
+ snd_pcm_uframes_t frames;
+ frames = runtime->period_size + 16;
+ if (frames > runtime->buffer_size)
+ frames = runtime->buffer_size;
+ sw_params->silence_threshold = frames;
+ sw_params->silence_size = frames;
+ }
+
+ if ((err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_SW_PARAMS, sw_params)) < 0) {
+ snd_printd("SW_PARAMS failed: %i\n", err);
+ goto failure;
+ }
+
+ runtime->oss.periods = params_periods(sparams);
+ oss_period_size = snd_pcm_plug_client_size(substream, params_period_size(sparams));
+ snd_assert(oss_period_size >= 0, err = -EINVAL; goto failure);
+ if (runtime->oss.plugin_first) {
+ err = snd_pcm_plug_alloc(substream, oss_period_size);
+ if (err < 0)
+ goto failure;
+ }
+ oss_period_size *= oss_frame_size;
+
+ oss_buffer_size = oss_period_size * runtime->oss.periods;
+ snd_assert(oss_buffer_size >= 0, err = -EINVAL; goto failure);
+
+ runtime->oss.period_bytes = oss_period_size;
+ runtime->oss.buffer_bytes = oss_buffer_size;
+
+ pdprintf("oss: period bytes = %i, buffer bytes = %i\n",
+ runtime->oss.period_bytes,
+ runtime->oss.buffer_bytes);
+ pdprintf("slave: period_size = %i, buffer_size = %i\n",
+ params_period_size(sparams),
+ params_buffer_size(sparams));
+
+ runtime->oss.format = snd_pcm_oss_format_to(params_format(params));
+ runtime->oss.channels = params_channels(params);
+ runtime->oss.rate = params_rate(params);
+
+ runtime->oss.params = 0;
+ runtime->oss.prepare = 1;
+ vfree(runtime->oss.buffer);
+ runtime->oss.buffer = vmalloc(runtime->oss.period_bytes);
+ runtime->oss.buffer_used = 0;
+ if (runtime->dma_area)
+ snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes));
+
+ runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size);
+
+ err = 0;
+failure:
+ kfree(sw_params);
+ kfree(params);
+ kfree(sparams);
+ return err;
+}
+
+static int snd_pcm_oss_get_active_substream(snd_pcm_oss_file_t *pcm_oss_file, snd_pcm_substream_t **r_substream)
+{
+ int idx, err;
+ snd_pcm_substream_t *asubstream = NULL, *substream;
+
+ for (idx = 0; idx < 2; idx++) {
+ substream = pcm_oss_file->streams[idx];
+ if (substream == NULL)
+ continue;
+ if (asubstream == NULL)
+ asubstream = substream;
+ if (substream->runtime->oss.params) {
+ err = snd_pcm_oss_change_params(substream);
+ if (err < 0)
+ return err;
+ }
+ }
+ snd_assert(asubstream != NULL, return -EIO);
+ if (r_substream)
+ *r_substream = asubstream;
+ return 0;
+}
+
+static int snd_pcm_oss_prepare(snd_pcm_substream_t *substream)
+{
+ int err;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
+ if (err < 0) {
+ snd_printd("snd_pcm_oss_prepare: SNDRV_PCM_IOCTL_PREPARE failed\n");
+ return err;
+ }
+ runtime->oss.prepare = 0;
+ runtime->oss.prev_hw_ptr_interrupt = 0;
+ runtime->oss.period_ptr = 0;
+ runtime->oss.buffer_used = 0;
+
+ return 0;
+}
+
+static int snd_pcm_oss_make_ready(snd_pcm_substream_t *substream)
+{
+ snd_pcm_runtime_t *runtime;
+ int err;
+
+ if (substream == NULL)
+ return 0;
+ runtime = substream->runtime;
+ if (runtime->oss.params) {
+ err = snd_pcm_oss_change_params(substream);
+ if (err < 0)
+ return err;
+ }
+ if (runtime->oss.prepare) {
+ err = snd_pcm_oss_prepare(substream);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int snd_pcm_oss_capture_position_fixup(snd_pcm_substream_t *substream, snd_pcm_sframes_t *delay)
+{
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_uframes_t frames;
+ int err = 0;
+
+ while (1) {
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, delay);
+ if (err < 0)
+ break;
+ runtime = substream->runtime;
+ if (*delay <= (snd_pcm_sframes_t)runtime->buffer_size)
+ break;
+ /* in case of overrun, skip whole periods like OSS/Linux driver does */
+ /* until avail(delay) <= buffer_size */
+ frames = (*delay - runtime->buffer_size) + runtime->period_size - 1;
+ frames /= runtime->period_size;
+ frames *= runtime->period_size;
+ err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_FORWARD, &frames);
+ if (err < 0)
+ break;
+ }
+ return err;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_write3(snd_pcm_substream_t *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: write: recovering from XRUN\n");
+ else
+ printk("pcm_oss: write: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_write(substream, (void __user *)ptr, frames);
+ }
+ if (ret != -EPIPE && ret != -ESTRPIPE)
+ break;
+ /* test, if we can't store new data, because the stream */
+ /* has not been started */
+ if (runtime->status->state == SNDRV_PCM_STATE_PREPARED)
+ return -EAGAIN;
+ }
+ return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_read3(snd_pcm_substream_t *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t delay;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: read: recovering from XRUN\n");
+ else
+ printk("pcm_oss: read: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+ if (ret < 0)
+ break;
+ } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) {
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ ret = snd_pcm_oss_capture_position_fixup(substream, &delay);
+ if (ret < 0)
+ break;
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_read(substream, (void __user *)ptr, frames);
+ }
+ if (ret == -EPIPE) {
+ if (runtime->status->state == SNDRV_PCM_STATE_DRAINING) {
+ ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ if (ret < 0)
+ break;
+ }
+ continue;
+ }
+ if (ret != -ESTRPIPE)
+ break;
+ }
+ return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_writev3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: writev: recovering from XRUN\n");
+ else
+ printk("pcm_oss: writev: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_writev(substream, (void __user **)bufs, frames);
+ }
+ if (ret != -EPIPE && ret != -ESTRPIPE)
+ break;
+
+ /* test, if we can't store new data, because the stream */
+ /* has not been started */
+ if (runtime->status->state == SNDRV_PCM_STATE_PREPARED)
+ return -EAGAIN;
+ }
+ return ret;
+}
+
+snd_pcm_sframes_t snd_pcm_oss_readv3(snd_pcm_substream_t *substream, void **bufs, snd_pcm_uframes_t frames, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ int ret;
+ while (1) {
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+#ifdef OSS_DEBUG
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN)
+ printk("pcm_oss: readv: recovering from XRUN\n");
+ else
+ printk("pcm_oss: readv: recovering from SUSPEND\n");
+#endif
+ ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
+ if (ret < 0)
+ break;
+ } else if (runtime->status->state == SNDRV_PCM_STATE_SETUP) {
+ ret = snd_pcm_oss_prepare(substream);
+ if (ret < 0)
+ break;
+ }
+ if (in_kernel) {
+ mm_segment_t fs;
+ fs = snd_enter_user();
+ ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames);
+ snd_leave_user(fs);
+ } else {
+ ret = snd_pcm_lib_readv(substream, (void __user **)bufs, frames);
+ }
+ if (ret != -EPIPE && ret != -ESTRPIPE)
+ break;
+ }
+ return ret;
+}
+
+static ssize_t snd_pcm_oss_write2(snd_pcm_substream_t *substream, const char *buf, size_t bytes, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t frames, frames1;
+ if (runtime->oss.plugin_first) {
+ snd_pcm_plugin_channel_t *channels;
+ size_t oss_frame_bytes = (runtime->oss.plugin_first->src_width * runtime->oss.plugin_first->src_format.channels) / 8;
+ if (!in_kernel) {
+ if (copy_from_user(runtime->oss.buffer, (const char __user *)buf, bytes))
+ return -EFAULT;
+ buf = runtime->oss.buffer;
+ }
+ frames = bytes / oss_frame_bytes;
+ frames1 = snd_pcm_plug_client_channels_buf(substream, (char *)buf, frames, &channels);
+ if (frames1 < 0)
+ return frames1;
+ frames1 = snd_pcm_plug_write_transfer(substream, channels, frames1);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames1 * oss_frame_bytes;
+ } else {
+ frames = bytes_to_frames(runtime, bytes);
+ frames1 = snd_pcm_oss_write3(substream, buf, frames, in_kernel);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames_to_bytes(runtime, frames1);
+ }
+ return bytes;
+}
+
+static ssize_t snd_pcm_oss_write1(snd_pcm_substream_t *substream, const char __user *buf, size_t bytes)
+{
+ size_t xfer = 0;
+ ssize_t tmp;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ if (atomic_read(&runtime->mmap_count))
+ return -ENXIO;
+
+ if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+ return tmp;
+ while (bytes > 0) {
+ if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
+ tmp = bytes;
+ if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes)
+ tmp = runtime->oss.period_bytes - runtime->oss.buffer_used;
+ if (tmp > 0) {
+ if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp))
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+ }
+ runtime->oss.buffer_used += tmp;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ if ((substream->oss.setup != NULL && substream->oss.setup->partialfrag) ||
+ runtime->oss.buffer_used == runtime->oss.period_bytes) {
+ tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr,
+ runtime->oss.buffer_used - runtime->oss.period_ptr, 1);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ runtime->oss.period_ptr += tmp;
+ runtime->oss.period_ptr %= runtime->oss.period_bytes;
+ if (runtime->oss.period_ptr == 0 ||
+ runtime->oss.period_ptr == runtime->oss.buffer_used)
+ runtime->oss.buffer_used = 0;
+ else if ((substream->ffile->f_flags & O_NONBLOCK) != 0)
+ return xfer > 0 ? xfer : -EAGAIN;
+ }
+ } else {
+ tmp = snd_pcm_oss_write2(substream, (const char *)buf, runtime->oss.period_bytes, 0);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ if ((substream->ffile->f_flags & O_NONBLOCK) != 0 &&
+ tmp != runtime->oss.period_bytes)
+ break;
+ }
+ }
+ return xfer;
+}
+
+static ssize_t snd_pcm_oss_read2(snd_pcm_substream_t *substream, char *buf, size_t bytes, int in_kernel)
+{
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ snd_pcm_sframes_t frames, frames1;
+ char __user *final_dst = (char __user *)buf;
+ if (runtime->oss.plugin_first) {
+ snd_pcm_plugin_channel_t *channels;
+ size_t oss_frame_bytes = (runtime->oss.plugin_last->dst_width * runtime->oss.plugin_last->dst_format.channels) / 8;
+ if (!in_kernel)
+ buf = runtime->oss.buffer;
+ frames = bytes / oss_frame_bytes;
+ frames1 = snd_pcm_plug_client_channels_buf(substream, buf, frames, &channels);
+ if (frames1 < 0)
+ return frames1;
+ frames1 = snd_pcm_plug_read_transfer(substream, channels, frames1);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames1 * oss_frame_bytes;
+ if (!in_kernel && copy_to_user(final_dst, buf, bytes))
+ return -EFAULT;
+ } else {
+ frames = bytes_to_frames(runtime, bytes);
+ frames1 = snd_pcm_oss_read3(substream, buf, frames, in_kernel);
+ if (frames1 <= 0)
+ return frames1;
+ bytes = frames_to_bytes(runtime, frames1);
+ }
+ return bytes;
+}
+
+static ssize_t snd_pcm_oss_read1(snd_pcm_substream_t *substream, char __user *buf, size_t bytes)
+{
+ size_t xfer = 0;
+ ssize_t tmp;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ if (atomic_read(&runtime->mmap_count))
+ return -ENXIO;
+
+ if ((tmp = snd_pcm_oss_make_ready(substream)) < 0)
+ return tmp;
+ while (bytes > 0) {
+ if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) {
+ if (runtime->oss.buffer_used == 0) {
+ tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ runtime->oss.period_ptr = tmp;
+ runtime->oss.buffer_used = tmp;
+ }
+ tmp = bytes;
+ if ((size_t) tmp > runtime->oss.buffer_used)
+ tmp = runtime->oss.buffer_used;
+ if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp))
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : -EFAULT;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ runtime->oss.buffer_used -= tmp;
+ } else {
+ tmp = snd_pcm_oss_read2(substream, (char *)buf, runtime->oss.period_bytes, 0);
+ if (tmp <= 0)
+ return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp;
+ runtime->oss.bytes += tmp;
+ buf += tmp;
+ bytes -= tmp;
+ xfer += tmp;
+ }
+ }
+ return xfer;
+}
+
+static int snd_pcm_oss_reset(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream != NULL) {
+ snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ substream->runtime->oss.prepare = 1;
+ }
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE];
+ if (substream != NULL) {
+ snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ substream->runtime->oss.prepare = 1;
+ }
+ return 0;
+}
+
+static int snd_pcm_oss_post(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ snd_pcm_substream_t *substream;
+ int err;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream != NULL) {
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL);
+ }
+ /* note: all errors from the start action are ignored */
+ /* OSS apps do not know, how to handle them */
+ return 0;
+}
+
+static int snd_pcm_oss_sync1(snd_pcm_substream_t *substream, size_t size)
+{
+ snd_pcm_runtime_t *runtime;
+ ssize_t result = 0;
+ long res;
+ wait_queue_t wait;
+
+ runtime = substream->runtime;
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&runtime->sleep, &wait);
+#ifdef OSS_DEBUG
+ printk("sync1: size = %li\n", size);
+#endif
+ while (1) {
+ result = snd_pcm_oss_write2(substream, runtime->oss.buffer, size, 1);
+ if (result > 0) {
+ runtime->oss.buffer_used = 0;
+ result = 0;
+ break;
+ }
+ if (result != 0 && result != -EAGAIN)
+ break;
+ result = 0;
+ set_current_state(TASK_INTERRUPTIBLE);
+ snd_pcm_stream_lock_irq(substream);
+ res = runtime->status->state;
+ snd_pcm_stream_unlock_irq(substream);
+ if (res != SNDRV_PCM_STATE_RUNNING) {
+ set_current_state(TASK_RUNNING);
+ break;
+ }
+ res = schedule_timeout(10 * HZ);
+ if (signal_pending(current)) {
+ result = -ERESTARTSYS;
+ break;
+ }
+ if (res == 0) {
+ snd_printk(KERN_ERR "OSS sync error - DMA timeout\n");
+ result = -EIO;
+ break;
+ }
+ }
+ remove_wait_queue(&runtime->sleep, &wait);
+ return result;
+}
+
+static int snd_pcm_oss_sync(snd_pcm_oss_file_t *pcm_oss_file)
+{
+ int err = 0;
+ unsigned int saved_f_flags;
+ snd_pcm_substream_t *substream;
+ snd_pcm_runtime_t *runtime;
+ snd_pcm_format_t format;
+ unsigned long width;
+ size_t size;
+
+ substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ if (substream != NULL) {
+ runtime = substream->runtime;
+ if (atomic_read(&runtime->mmap_count))
+ goto __direct;
+ if ((err = snd_pcm_oss_make_ready(substream)) < 0)
+ return err;
+ format = snd_pcm_oss_format_from(runtime->oss.format);
+ width = snd_pcm_format_physical_width(format);
+ if (runtime->oss.buffer_used > 0) {
+#ifdef OSS_DEBUG
+ printk("sync: buffer_used\n");
+#endif
+ size = (8 * (runtime->oss.period_bytes - runtime->oss.buffer_used) + 7) / width;
+ snd_pcm_format_set_silence(format,
+ runtime->oss.buffer + runtime->oss.buffer_used,
+ size);
+ err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes);
+ if (err < 0)
+ return err;
+ } else if (runtime->oss.period_ptr > 0) {
+#ifdef OSS_DEBUG
+ printk("sync: period_ptr\n");
+#endif
+ size = runtime->oss.period_bytes - runtime->oss.period_ptr;
+ snd_pcm_format_set_silence(format,
+ runtime->oss.buffer,
+ size * 8 / width);
+ err = snd_pcm_oss_sync1(substream, size);
+ if (err < 0)
+ return err;
+ }
+ /*
+ * The ALSA's period might be a bit large than OSS one.
+ * Fill the remain portion of ALSA period with zeros.
+ */
+ size = runtime->control->appl_ptr % runtime->period_size;
+ if (size > 0) {
+ size = runtime->period_size - size;
+ if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) {
+ size = (runtime->frame_bits * size) / 8;
+ while (size > 0) {
+ mm_segment_t fs;
+ size_t size1 = size < runtime->oss.period_bytes ? size : runtime->oss.period_bytes;
+ size -= size1;
+ size1 *= 8;
+ size1 /= runtime->sample_bits;
+ snd_pcm_format_set_silence(runtime->format,
+ runtime->oss.buffer,
+ size1);
+ fs = snd_enter_user();
+ snd_pcm_lib_write(substream, (void __user *)runtime->oss.buffer, size1);
+ snd_leave_user(fs);
+ }
+ } else if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) {
+ void __user *buffers[runtime->channels];
+ memset(buffers, 0, runtime->channels * sizeof(void *));
+ snd_pcm_lib_writev(substream, buffers, size);
+ }
+ }
+ /*