diff options
Diffstat (limited to 'sound/core/oss/pcm_oss.c')
-rw-r--r-- | sound/core/oss/pcm_oss.c | 2530 |
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); + } + } + /* |