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); +			} +		} +		/* +		 * finish sync: drain the buffer +		 */ +	      __direct: +		saved_f_flags = substream->ffile->f_flags; +		substream->ffile->f_flags &= ~O_NONBLOCK; +		err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); +		substream->ffile->f_flags = saved_f_flags; +		if (err < 0) +			return err; +		runtime->oss.prepare = 1; +	} + +	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; +	if (substream != NULL) { +		if ((err = snd_pcm_oss_make_ready(substream)) < 0) +			return err; +		runtime = substream->runtime; +		err = snd_pcm_kernel_capture_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); +		if (err < 0) +			return err; +		runtime->oss.buffer_used = 0; +		runtime->oss.prepare = 1; +	} +	return 0; +} + +static int snd_pcm_oss_set_rate(snd_pcm_oss_file_t *pcm_oss_file, int rate) +{ +	int idx; + +	for (idx = 1; idx >= 0; --idx) { +		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; +		snd_pcm_runtime_t *runtime; +		if (substream == NULL) +			continue; +		runtime = substream->runtime; +		if (rate < 1000) +			rate = 1000; +		else if (rate > 192000) +			rate = 192000; +		if (runtime->oss.rate != rate) { +			runtime->oss.params = 1; +			runtime->oss.rate = rate; +		} +	} +	return snd_pcm_oss_get_rate(pcm_oss_file); +} + +static int snd_pcm_oss_get_rate(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *substream; +	int err; +	 +	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) +		return err; +	return substream->runtime->oss.rate; +} + +static int snd_pcm_oss_set_channels(snd_pcm_oss_file_t *pcm_oss_file, unsigned int channels) +{ +	int idx; +	if (channels < 1) +		channels = 1; +	if (channels > 128) +		return -EINVAL; +	for (idx = 1; idx >= 0; --idx) { +		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; +		snd_pcm_runtime_t *runtime; +		if (substream == NULL) +			continue; +		runtime = substream->runtime; +		if (runtime->oss.channels != channels) { +			runtime->oss.params = 1; +			runtime->oss.channels = channels; +		} +	} +	return snd_pcm_oss_get_channels(pcm_oss_file); +} + +static int snd_pcm_oss_get_channels(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *substream; +	int err; +	 +	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) +		return err; +	return substream->runtime->oss.channels; +} + +static int snd_pcm_oss_get_block_size(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *substream; +	int err; +	 +	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) +		return err; +	return substream->runtime->oss.period_bytes; +} + +static int snd_pcm_oss_get_formats(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *substream; +	int err; +	int direct; +	snd_pcm_hw_params_t *params; +	unsigned int formats = 0; +	snd_mask_t format_mask; +	int fmt; + +	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) +		return err; +	if (atomic_read(&substream->runtime->mmap_count)) { +		direct = 1; +	} else { +		snd_pcm_oss_setup_t *setup = substream->oss.setup; +		direct = (setup != NULL && setup->direct); +	} +	if (!direct) +		return AFMT_MU_LAW | AFMT_U8 | +		       AFMT_S16_LE | AFMT_S16_BE | +		       AFMT_S8 | AFMT_U16_LE | +		       AFMT_U16_BE; +	params = kmalloc(sizeof(*params), GFP_KERNEL); +	if (!params) +		return -ENOMEM; +	_snd_pcm_hw_params_any(params); +	err = snd_pcm_hw_refine(substream, params); +	format_mask = *hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);  +	kfree(params); +	snd_assert(err >= 0, return err); +	for (fmt = 0; fmt < 32; ++fmt) { +		if (snd_mask_test(&format_mask, fmt)) { +			int f = snd_pcm_oss_format_to(fmt); +			if (f >= 0) +				formats |= f; +		} +	} +	return formats; +} + +static int snd_pcm_oss_set_format(snd_pcm_oss_file_t *pcm_oss_file, int format) +{ +	int formats, idx; +	 +	if (format != AFMT_QUERY) { +		formats = snd_pcm_oss_get_formats(pcm_oss_file); +		if (!(formats & format)) +			format = AFMT_U8; +		for (idx = 1; idx >= 0; --idx) { +			snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; +			snd_pcm_runtime_t *runtime; +			if (substream == NULL) +				continue; +			runtime = substream->runtime; +			if (runtime->oss.format != format) { +				runtime->oss.params = 1; +				runtime->oss.format = format; +			} +		} +	} +	return snd_pcm_oss_get_format(pcm_oss_file); +} + +static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *substream; +	int err; +	 +	if ((err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream)) < 0) +		return err; +	return substream->runtime->oss.format; +} + +static int snd_pcm_oss_set_subdivide1(snd_pcm_substream_t *substream, int subdivide) +{ +	snd_pcm_runtime_t *runtime; + +	if (substream == NULL) +		return 0; +	runtime = substream->runtime; +	if (subdivide == 0) { +		subdivide = runtime->oss.subdivision; +		if (subdivide == 0) +			subdivide = 1; +		return subdivide; +	} +	if (runtime->oss.subdivision || runtime->oss.fragshift) +		return -EINVAL; +	if (subdivide != 1 && subdivide != 2 && subdivide != 4 && +	    subdivide != 8 && subdivide != 16) +		return -EINVAL; +	runtime->oss.subdivision = subdivide; +	runtime->oss.params = 1; +	return subdivide; +} + +static int snd_pcm_oss_set_subdivide(snd_pcm_oss_file_t *pcm_oss_file, int subdivide) +{ +	int err = -EINVAL, idx; + +	for (idx = 1; idx >= 0; --idx) { +		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; +		if (substream == NULL) +			continue; +		if ((err = snd_pcm_oss_set_subdivide1(substream, subdivide)) < 0) +			return err; +	} +	return err; +} + +static int snd_pcm_oss_set_fragment1(snd_pcm_substream_t *substream, unsigned int val) +{ +	snd_pcm_runtime_t *runtime; + +	if (substream == NULL) +		return 0; +	runtime = substream->runtime; +	if (runtime->oss.subdivision || runtime->oss.fragshift) +		return -EINVAL; +	runtime->oss.fragshift = val & 0xffff; +	runtime->oss.maxfrags = (val >> 16) & 0xffff; +	if (runtime->oss.fragshift < 4)		/* < 16 */ +		runtime->oss.fragshift = 4; +	if (runtime->oss.maxfrags < 2) +		runtime->oss.maxfrags = 2; +	runtime->oss.params = 1; +	return 0; +} + +static int snd_pcm_oss_set_fragment(snd_pcm_oss_file_t *pcm_oss_file, unsigned int val) +{ +	int err = -EINVAL, idx; + +	for (idx = 1; idx >= 0; --idx) { +		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; +		if (substream == NULL) +			continue; +		if ((err = snd_pcm_oss_set_fragment1(substream, val)) < 0) +			return err; +	} +	return err; +} + +static int snd_pcm_oss_nonblock(struct file * file) +{ +	file->f_flags |= O_NONBLOCK; +	return 0; +} + +static int snd_pcm_oss_get_caps1(snd_pcm_substream_t *substream, int res) +{ + +	if (substream == NULL) { +		res &= ~DSP_CAP_DUPLEX; +		return res; +	} +#ifdef DSP_CAP_MULTI +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) +		if (substream->pstr->substream_count > 1) +			res |= DSP_CAP_MULTI; +#endif +	/* DSP_CAP_REALTIME is set all times: */ +	/* all ALSA drivers can return actual pointer in ring buffer */ +#if defined(DSP_CAP_REALTIME) && 0 +	{ +		snd_pcm_runtime_t *runtime = substream->runtime; +		if (runtime->info & (SNDRV_PCM_INFO_BLOCK_TRANSFER|SNDRV_PCM_INFO_BATCH)) +			res &= ~DSP_CAP_REALTIME; +	} +#endif +	return res; +} + +static int snd_pcm_oss_get_caps(snd_pcm_oss_file_t *pcm_oss_file) +{ +	int result, idx; +	 +	result = DSP_CAP_TRIGGER | DSP_CAP_MMAP	| DSP_CAP_DUPLEX | DSP_CAP_REALTIME; +	for (idx = 0; idx < 2; idx++) { +		snd_pcm_substream_t *substream = pcm_oss_file->streams[idx]; +		result = snd_pcm_oss_get_caps1(substream, result); +	} +	result |= 0x0001;	/* revision - same as SB AWE 64 */ +	return result; +} + +static void snd_pcm_oss_simulate_fill(snd_pcm_substream_t *substream, snd_pcm_uframes_t hw_ptr) +{ +	snd_pcm_runtime_t *runtime = substream->runtime; +	snd_pcm_uframes_t appl_ptr; +	appl_ptr = hw_ptr + runtime->buffer_size; +	appl_ptr %= runtime->boundary; +	runtime->control->appl_ptr = appl_ptr; +} + +static int snd_pcm_oss_set_trigger(snd_pcm_oss_file_t *pcm_oss_file, int trigger) +{ +	snd_pcm_runtime_t *runtime; +	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; +	int err, cmd; + +#ifdef OSS_DEBUG +	printk("pcm_oss: trigger = 0x%x\n", trigger); +#endif +	 +	psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +	csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + +	if (psubstream) { +		if ((err = snd_pcm_oss_make_ready(psubstream)) < 0) +			return err; +	} +	if (csubstream) { +		if ((err = snd_pcm_oss_make_ready(csubstream)) < 0) +			return err; +	} +      	if (psubstream) { +      		runtime = psubstream->runtime; +		if (trigger & PCM_ENABLE_OUTPUT) { +			if (runtime->oss.trigger) +				goto _skip1; +			if (atomic_read(&psubstream->runtime->mmap_count)) +				snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt); +			runtime->oss.trigger = 1; +			runtime->start_threshold = 1; +			cmd = SNDRV_PCM_IOCTL_START; +		} else { +			if (!runtime->oss.trigger) +				goto _skip1; +			runtime->oss.trigger = 0; +			runtime->start_threshold = runtime->boundary; +			cmd = SNDRV_PCM_IOCTL_DROP; +			runtime->oss.prepare = 1; +		} +		err = snd_pcm_kernel_playback_ioctl(psubstream, cmd, NULL); +		if (err < 0) +			return err; +	} + _skip1: +	if (csubstream) { +      		runtime = csubstream->runtime; +		if (trigger & PCM_ENABLE_INPUT) { +			if (runtime->oss.trigger) +				goto _skip2; +			runtime->oss.trigger = 1; +			runtime->start_threshold = 1; +			cmd = SNDRV_PCM_IOCTL_START; +		} else { +			if (!runtime->oss.trigger) +				goto _skip2; +			runtime->oss.trigger = 0; +			runtime->start_threshold = runtime->boundary; +			cmd = SNDRV_PCM_IOCTL_DROP; +			runtime->oss.prepare = 1; +		} +		err = snd_pcm_kernel_capture_ioctl(csubstream, cmd, NULL); +		if (err < 0) +			return err; +	} + _skip2: +	return 0; +} + +static int snd_pcm_oss_get_trigger(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; +	int result = 0; + +	psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +	csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; +	if (psubstream && psubstream->runtime && psubstream->runtime->oss.trigger) +		result |= PCM_ENABLE_OUTPUT; +	if (csubstream && csubstream->runtime && csubstream->runtime->oss.trigger) +		result |= PCM_ENABLE_INPUT; +	return result; +} + +static int snd_pcm_oss_get_odelay(snd_pcm_oss_file_t *pcm_oss_file) +{ +	snd_pcm_substream_t *substream; +	snd_pcm_runtime_t *runtime; +	snd_pcm_sframes_t delay; +	int err; + +	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +	if (substream == NULL) +		return -EINVAL; +	if ((err = snd_pcm_oss_make_ready(substream)) < 0) +		return err; +	runtime = substream->runtime; +	if (runtime->oss.params || runtime->oss.prepare) +		return 0; +	err = snd_pcm_kernel_playback_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); +	if (err == -EPIPE) +		delay = 0;	/* hack for broken OSS applications */ +	else if (err < 0) +		return err; +	return snd_pcm_oss_bytes(substream, delay); +} + +static int snd_pcm_oss_get_ptr(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct count_info __user * _info) +{	 +	snd_pcm_substream_t *substream; +	snd_pcm_runtime_t *runtime; +	snd_pcm_sframes_t delay; +	int fixup; +	struct count_info info; +	int err; + +	if (_info == NULL) +		return -EFAULT; +	substream = pcm_oss_file->streams[stream]; +	if (substream == NULL) +		return -EINVAL; +	if ((err = snd_pcm_oss_make_ready(substream)) < 0) +		return err; +	runtime = substream->runtime; +	if (runtime->oss.params || runtime->oss.prepare) { +		memset(&info, 0, sizeof(info)); +		if (copy_to_user(_info, &info, sizeof(info))) +			return -EFAULT; +		return 0; +	} +	if (stream == SNDRV_PCM_STREAM_PLAYBACK) { +		err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); +		if (err == -EPIPE || err == -ESTRPIPE || (! err && delay < 0)) { +			err = 0; +			delay = 0; +			fixup = 0; +		} else { +			fixup = runtime->oss.buffer_used; +		} +	} else { +		err = snd_pcm_oss_capture_position_fixup(substream, &delay); +		fixup = -runtime->oss.buffer_used; +	} +	if (err < 0) +		return err; +	info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size); +	if (atomic_read(&runtime->mmap_count)) { +		snd_pcm_sframes_t n; +		n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt; +		if (n < 0) +			n += runtime->boundary; +		info.blocks = n / runtime->period_size; +		runtime->oss.prev_hw_ptr_interrupt = delay; +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) +			snd_pcm_oss_simulate_fill(substream, delay); +		info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX; +	} else { +		delay = snd_pcm_oss_bytes(substream, delay) + fixup; +		info.blocks = delay / runtime->oss.period_bytes; +		if (stream == SNDRV_PCM_STREAM_PLAYBACK) +			info.bytes = (runtime->oss.bytes - delay) & INT_MAX; +		else +			info.bytes = (runtime->oss.bytes + delay) & INT_MAX; +	} +	if (copy_to_user(_info, &info, sizeof(info))) +		return -EFAULT; +	return 0; +} + +static int snd_pcm_oss_get_space(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct audio_buf_info __user *_info) +{ +	snd_pcm_substream_t *substream; +	snd_pcm_runtime_t *runtime; +	snd_pcm_sframes_t avail; +	int fixup; +	struct audio_buf_info info; +	int err; + +	if (_info == NULL) +		return -EFAULT; +	substream = pcm_oss_file->streams[stream]; +	if (substream == NULL) +		return -EINVAL; +	runtime = substream->runtime; + +	if (runtime->oss.params && +	    (err = snd_pcm_oss_change_params(substream)) < 0) +		return err; + +	info.fragsize = runtime->oss.period_bytes; +	info.fragstotal = runtime->periods; +	if (runtime->oss.prepare) { +		if (stream == SNDRV_PCM_STREAM_PLAYBACK) { +			info.bytes = runtime->oss.period_bytes * runtime->oss.periods; +			info.fragments = runtime->oss.periods; +		} else { +			info.bytes = 0; +			info.fragments = 0; +		} +	} else { +		if (stream == SNDRV_PCM_STREAM_PLAYBACK) { +			err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &avail); +			if (err == -EPIPE || err == -ESTRPIPE || (! err && avail < 0)) { +				avail = runtime->buffer_size; +				err = 0; +				fixup = 0; +			} else { +				avail = runtime->buffer_size - avail; +				fixup = -runtime->oss.buffer_used; +			} +		} else { +			err = snd_pcm_oss_capture_position_fixup(substream, &avail); +			fixup = runtime->oss.buffer_used; +		} +		if (err < 0) +			return err; +		info.bytes = snd_pcm_oss_bytes(substream, avail) + fixup; +		info.fragments = info.bytes / runtime->oss.period_bytes; +	} + +#ifdef OSS_DEBUG +	printk("pcm_oss: space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", info.bytes, info.fragments, info.fragstotal, info.fragsize); +#endif +	if (copy_to_user(_info, &info, sizeof(info))) +		return -EFAULT; +	return 0; +} + +static int snd_pcm_oss_get_mapbuf(snd_pcm_oss_file_t *pcm_oss_file, int stream, struct buffmem_desc __user * _info) +{ +	// it won't be probably implemented +	// snd_printd("TODO: snd_pcm_oss_get_mapbuf\n"); +	return -EINVAL; +} + +static snd_pcm_oss_setup_t *snd_pcm_oss_look_for_setup(snd_pcm_t *pcm, int stream, const char *task_name) +{ +	const char *ptr, *ptrl; +	snd_pcm_oss_setup_t *setup; + +	down(&pcm->streams[stream].oss.setup_mutex); +	for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) { +		if (!strcmp(setup->task_name, task_name)) { +			up(&pcm->streams[stream].oss.setup_mutex); +			return setup; +		} +	} +	ptr = ptrl = task_name; +	while (*ptr) { +		if (*ptr == '/') +			ptrl = ptr + 1; +		ptr++; +	} +	if (ptrl == task_name) { +		goto __not_found; +		return NULL; +	} +	for (setup = pcm->streams[stream].oss.setup_list; setup; setup = setup->next) { +		if (!strcmp(setup->task_name, ptrl)) { +			up(&pcm->streams[stream].oss.setup_mutex); +			return setup; +		} +	} +      __not_found: +	up(&pcm->streams[stream].oss.setup_mutex); +	return NULL; +} + +static void snd_pcm_oss_init_substream(snd_pcm_substream_t *substream, +				       snd_pcm_oss_setup_t *setup, +				       int minor) +{ +	snd_pcm_runtime_t *runtime; + +	substream->oss.oss = 1; +	substream->oss.setup = setup; +	runtime = substream->runtime; +	runtime->oss.params = 1; +	runtime->oss.trigger = 1; +	runtime->oss.rate = 8000; +	switch (SNDRV_MINOR_OSS_DEVICE(minor)) { +	case SNDRV_MINOR_OSS_PCM_8: +		runtime->oss.format = AFMT_U8; +		break; +	case SNDRV_MINOR_OSS_PCM_16: +		runtime->oss.format = AFMT_S16_LE; +		break; +	default: +		runtime->oss.format = AFMT_MU_LAW; +	} +	runtime->oss.channels = 1; +	runtime->oss.fragshift = 0; +	runtime->oss.maxfrags = 0; +	runtime->oss.subdivision = 0; +} + +static void snd_pcm_oss_release_substream(snd_pcm_substream_t *substream) +{ +	snd_pcm_runtime_t *runtime; +	runtime = substream->runtime; +	vfree(runtime->oss.buffer); +	snd_pcm_oss_plugin_clear(substream); +	substream->oss.file = NULL; +	substream->oss.oss = 0; +} + +static int snd_pcm_oss_release_file(snd_pcm_oss_file_t *pcm_oss_file) +{ +	int cidx; +	snd_assert(pcm_oss_file != NULL, return -ENXIO); +	for (cidx = 0; cidx < 2; ++cidx) { +		snd_pcm_substream_t *substream = pcm_oss_file->streams[cidx]; +		snd_pcm_runtime_t *runtime; +		if (substream == NULL) +			continue; +		runtime = substream->runtime; +		 +		snd_pcm_stream_lock_irq(substream); +		if (snd_pcm_running(substream)) +			snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); +		snd_pcm_stream_unlock_irq(substream); +		if (substream->open_flag) { +			if (substream->ops->hw_free != NULL) +				substream->ops->hw_free(substream); +			substream->ops->close(substream); +			substream->open_flag = 0; +		} +		substream->ffile = NULL; +		snd_pcm_oss_release_substream(substream); +		snd_pcm_release_substream(substream); +	} +	kfree(pcm_oss_file); +	return 0; +} + +static int snd_pcm_oss_open_file(struct file *file, +				 snd_pcm_t *pcm, +				 snd_pcm_oss_file_t **rpcm_oss_file, +				 int minor, +				 snd_pcm_oss_setup_t *psetup, +				 snd_pcm_oss_setup_t *csetup) +{ +	int err = 0; +	snd_pcm_oss_file_t *pcm_oss_file; +	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; +	unsigned int f_mode = file->f_mode; + +	snd_assert(rpcm_oss_file != NULL, return -EINVAL); +	*rpcm_oss_file = NULL; + +	pcm_oss_file = kcalloc(1, sizeof(*pcm_oss_file), GFP_KERNEL); +	if (pcm_oss_file == NULL) +		return -ENOMEM; + +	if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) && +	    (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)) +		f_mode = FMODE_WRITE; +	if ((f_mode & FMODE_WRITE) && !(psetup && psetup->disable)) { +		if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_PLAYBACK, +					       &psubstream)) < 0) { +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK] = psubstream; +	} +	if ((f_mode & FMODE_READ) && !(csetup && csetup->disable)) { +		if ((err = snd_pcm_open_substream(pcm, SNDRV_PCM_STREAM_CAPTURE,  +					       &csubstream)) < 0) { +			if (!(f_mode & FMODE_WRITE) || err != -ENODEV) { +				snd_pcm_oss_release_file(pcm_oss_file); +				return err; +			} else { +				csubstream = NULL; +			} +		} +		pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE] = csubstream; +	} +	 +	if (psubstream == NULL && csubstream == NULL) { +		snd_pcm_oss_release_file(pcm_oss_file); +		return -EINVAL; +	} +	if (psubstream != NULL) { +		psubstream->oss.file = pcm_oss_file; +		err = snd_pcm_hw_constraints_init(psubstream); +		if (err < 0) { +			snd_printd("snd_pcm_hw_constraint_init failed\n"); +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		if ((err = psubstream->ops->open(psubstream)) < 0) { +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		psubstream->open_flag = 1; +		err = snd_pcm_hw_constraints_complete(psubstream); +		if (err < 0) { +			snd_printd("snd_pcm_hw_constraint_complete failed\n"); +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		psubstream->ffile = file; +		snd_pcm_oss_init_substream(psubstream, psetup, minor); +	} +	if (csubstream != NULL) { +		csubstream->oss.file = pcm_oss_file; +		err = snd_pcm_hw_constraints_init(csubstream); +		if (err < 0) { +			snd_printd("snd_pcm_hw_constraint_init failed\n"); +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		if ((err = csubstream->ops->open(csubstream)) < 0) { +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		csubstream->open_flag = 1; +		err = snd_pcm_hw_constraints_complete(csubstream); +		if (err < 0) { +			snd_printd("snd_pcm_hw_constraint_complete failed\n"); +			snd_pcm_oss_release_file(pcm_oss_file); +			return err; +		} +		csubstream->ffile = file; +		snd_pcm_oss_init_substream(csubstream, csetup, minor); +	} + +	file->private_data = pcm_oss_file; +	*rpcm_oss_file = pcm_oss_file; +	return 0; +} + + +static int snd_pcm_oss_open(struct inode *inode, struct file *file) +{ +	int minor = iminor(inode); +	int cardnum = SNDRV_MINOR_OSS_CARD(minor); +	int device; +	int err; +	char task_name[32]; +	snd_pcm_t *pcm; +	snd_pcm_oss_file_t *pcm_oss_file; +	snd_pcm_oss_setup_t *psetup = NULL, *csetup = NULL; +	int nonblock; +	wait_queue_t wait; + +	snd_assert(cardnum >= 0 && cardnum < SNDRV_CARDS, return -ENXIO); +	device = SNDRV_MINOR_OSS_DEVICE(minor) == SNDRV_MINOR_OSS_PCM1 ? +		adsp_map[cardnum] : dsp_map[cardnum]; + +	pcm = snd_pcm_devices[(cardnum * SNDRV_PCM_DEVICES) + device]; +	if (pcm == NULL) { +		err = -ENODEV; +		goto __error1; +	} +	err = snd_card_file_add(pcm->card, file); +	if (err < 0) +		goto __error1; +	if (!try_module_get(pcm->card->module)) { +		err = -EFAULT; +		goto __error2; +	} +	if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { +		err = -EFAULT; +		goto __error; +	} +	if (file->f_mode & FMODE_WRITE) +		psetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, task_name); +	if (file->f_mode & FMODE_READ) +		csetup = snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, task_name); + +	nonblock = !!(file->f_flags & O_NONBLOCK); +	if (psetup && !psetup->disable) { +		if (psetup->nonblock) +			nonblock = 1; +		else if (psetup->block) +			nonblock = 0; +	} else if (csetup && !csetup->disable) { +		if (csetup->nonblock) +			nonblock = 1; +		else if (csetup->block) +			nonblock = 0; +	} +	if (!nonblock) +		nonblock = nonblock_open; + +	init_waitqueue_entry(&wait, current); +	add_wait_queue(&pcm->open_wait, &wait); +	down(&pcm->open_mutex); +	while (1) { +		err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file, +					    minor, psetup, csetup); +		if (err >= 0) +			break; +		if (err == -EAGAIN) { +			if (nonblock) { +				err = -EBUSY; +				break; +			} +		} else +			break; +		set_current_state(TASK_INTERRUPTIBLE); +		up(&pcm->open_mutex); +		schedule(); +		down(&pcm->open_mutex); +		if (signal_pending(current)) { +			err = -ERESTARTSYS; +			break; +		} +	} +	remove_wait_queue(&pcm->open_wait, &wait); +	up(&pcm->open_mutex); +	if (err < 0) +		goto __error; +	return err; + +      __error: +     	module_put(pcm->card->module); +      __error2: +      	snd_card_file_remove(pcm->card, file); +      __error1: +	return err; +} + +static int snd_pcm_oss_release(struct inode *inode, struct file *file) +{ +	snd_pcm_t *pcm; +	snd_pcm_substream_t *substream; +	snd_pcm_oss_file_t *pcm_oss_file; + +	pcm_oss_file = file->private_data; +	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +	if (substream == NULL) +		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; +	snd_assert(substream != NULL, return -ENXIO); +	pcm = substream->pcm; +	snd_pcm_oss_sync(pcm_oss_file); +	down(&pcm->open_mutex); +	snd_pcm_oss_release_file(pcm_oss_file); +	up(&pcm->open_mutex); +	wake_up(&pcm->open_wait); +	module_put(pcm->card->module); +	snd_card_file_remove(pcm->card, file); +	return 0; +} + +static long snd_pcm_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ +	snd_pcm_oss_file_t *pcm_oss_file; +	int __user *p = (int __user *)arg; +	int res; + +	pcm_oss_file = file->private_data; +	if (cmd == OSS_GETVERSION) +		return put_user(SNDRV_OSS_VERSION, p); +	if (cmd == OSS_ALSAEMULVER) +		return put_user(1, p); +#if defined(CONFIG_SND_MIXER_OSS) || (defined(MODULE) && defined(CONFIG_SND_MIXER_OSS_MODULE)) +	if (((cmd >> 8) & 0xff) == 'M')	{	/* mixer ioctl - for OSS compatibility */ +		snd_pcm_substream_t *substream; +		int idx; +		for (idx = 0; idx < 2; ++idx) { +			substream = pcm_oss_file->streams[idx]; +			if (substream != NULL) +				break; +		} +		snd_assert(substream != NULL, return -ENXIO); +		return snd_mixer_oss_ioctl_card(substream->pcm->card, cmd, arg); +	} +#endif +	if (((cmd >> 8) & 0xff) != 'P') +		return -EINVAL; +#ifdef OSS_DEBUG +	printk("pcm_oss: ioctl = 0x%x\n", cmd); +#endif +	switch (cmd) { +	case SNDCTL_DSP_RESET: +		return snd_pcm_oss_reset(pcm_oss_file); +	case SNDCTL_DSP_SYNC: +		return snd_pcm_oss_sync(pcm_oss_file); +	case SNDCTL_DSP_SPEED: +		if (get_user(res, p)) +			return -EFAULT; +		if ((res = snd_pcm_oss_set_rate(pcm_oss_file, res))<0) +			return res; +		return put_user(res, p); +	case SOUND_PCM_READ_RATE: +		res = snd_pcm_oss_get_rate(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_STEREO: +		if (get_user(res, p)) +			return -EFAULT; +		res = res > 0 ? 2 : 1; +		if ((res = snd_pcm_oss_set_channels(pcm_oss_file, res)) < 0) +			return res; +		return put_user(--res, p); +	case SNDCTL_DSP_GETBLKSIZE: +		res = snd_pcm_oss_get_block_size(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_SETFMT: +		if (get_user(res, p)) +			return -EFAULT; +		res = snd_pcm_oss_set_format(pcm_oss_file, res); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SOUND_PCM_READ_BITS: +		res = snd_pcm_oss_get_format(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_CHANNELS: +		if (get_user(res, p)) +			return -EFAULT; +		res = snd_pcm_oss_set_channels(pcm_oss_file, res); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SOUND_PCM_READ_CHANNELS: +		res = snd_pcm_oss_get_channels(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SOUND_PCM_WRITE_FILTER: +	case SOUND_PCM_READ_FILTER: +		return -EIO; +	case SNDCTL_DSP_POST: +		return snd_pcm_oss_post(pcm_oss_file); +	case SNDCTL_DSP_SUBDIVIDE: +		if (get_user(res, p)) +			return -EFAULT; +		res = snd_pcm_oss_set_subdivide(pcm_oss_file, res); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_SETFRAGMENT: +		if (get_user(res, p)) +			return -EFAULT; +		return snd_pcm_oss_set_fragment(pcm_oss_file, res); +	case SNDCTL_DSP_GETFMTS: +		res = snd_pcm_oss_get_formats(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_GETOSPACE: +	case SNDCTL_DSP_GETISPACE: +		return snd_pcm_oss_get_space(pcm_oss_file, +			cmd == SNDCTL_DSP_GETISPACE ? +				SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, +			(struct audio_buf_info __user *) arg); +	case SNDCTL_DSP_NONBLOCK: +		return snd_pcm_oss_nonblock(file); +	case SNDCTL_DSP_GETCAPS: +		res = snd_pcm_oss_get_caps(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_GETTRIGGER: +		res = snd_pcm_oss_get_trigger(pcm_oss_file); +		if (res < 0) +			return res; +		return put_user(res, p); +	case SNDCTL_DSP_SETTRIGGER: +		if (get_user(res, p)) +			return -EFAULT; +		return snd_pcm_oss_set_trigger(pcm_oss_file, res); +	case SNDCTL_DSP_GETIPTR: +	case SNDCTL_DSP_GETOPTR: +		return snd_pcm_oss_get_ptr(pcm_oss_file, +			cmd == SNDCTL_DSP_GETIPTR ? +				SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, +			(struct count_info __user *) arg); +	case SNDCTL_DSP_MAPINBUF: +	case SNDCTL_DSP_MAPOUTBUF: +		return snd_pcm_oss_get_mapbuf(pcm_oss_file, +			cmd == SNDCTL_DSP_MAPINBUF ? +				SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, +			(struct buffmem_desc __user *) arg); +	case SNDCTL_DSP_SETSYNCRO: +		/* stop DMA now.. */ +		return 0; +	case SNDCTL_DSP_SETDUPLEX: +		if (snd_pcm_oss_get_caps(pcm_oss_file) & DSP_CAP_DUPLEX) +			return 0; +		return -EIO; +	case SNDCTL_DSP_GETODELAY: +		res = snd_pcm_oss_get_odelay(pcm_oss_file); +		if (res < 0) { +			/* it's for sure, some broken apps don't check for error codes */ +			put_user(0, p); +			return res; +		} +		return put_user(res, p); +	case SNDCTL_DSP_PROFILE: +		return 0;	/* silently ignore */ +	default: +		snd_printd("pcm_oss: unknown command = 0x%x\n", cmd); +	} +	return -EINVAL; +} + +#ifdef CONFIG_COMPAT +/* all compatible */ +#define snd_pcm_oss_ioctl_compat	snd_pcm_oss_ioctl +#else +#define snd_pcm_oss_ioctl_compat	NULL +#endif + +static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ +	snd_pcm_oss_file_t *pcm_oss_file; +	snd_pcm_substream_t *substream; + +	pcm_oss_file = file->private_data; +	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; +	if (substream == NULL) +		return -ENXIO; +#ifndef OSS_DEBUG +	return snd_pcm_oss_read1(substream, buf, count); +#else +	{ +		ssize_t res = snd_pcm_oss_read1(substream, buf, count); +		printk("pcm_oss: read %li bytes (returned %li bytes)\n", (long)count, (long)res); +		return res; +	} +#endif +} + +static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ +	snd_pcm_oss_file_t *pcm_oss_file; +	snd_pcm_substream_t *substream; +	long result; + +	pcm_oss_file = file->private_data; +	substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +	if (substream == NULL) +		return -ENXIO; +	up(&file->f_dentry->d_inode->i_sem); +	result = snd_pcm_oss_write1(substream, buf, count); +	down(&file->f_dentry->d_inode->i_sem); +#ifdef OSS_DEBUG +	printk("pcm_oss: write %li bytes (wrote %li bytes)\n", (long)count, (long)result); +#endif +	return result; +} + +static int snd_pcm_oss_playback_ready(snd_pcm_substream_t *substream) +{ +	snd_pcm_runtime_t *runtime = substream->runtime; +	if (atomic_read(&runtime->mmap_count)) +		return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt; +	else +		return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames; +} + +static int snd_pcm_oss_capture_ready(snd_pcm_substream_t *substream) +{ +	snd_pcm_runtime_t *runtime = substream->runtime; +	if (atomic_read(&runtime->mmap_count)) +		return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt; +	else +		return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames; +} + +static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait) +{ +	snd_pcm_oss_file_t *pcm_oss_file; +	unsigned int mask; +	snd_pcm_substream_t *psubstream = NULL, *csubstream = NULL; +	 +	pcm_oss_file = file->private_data; + +	psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +	csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + +	mask = 0; +	if (psubstream != NULL) { +		snd_pcm_runtime_t *runtime = psubstream->runtime; +		poll_wait(file, &runtime->sleep, wait); +		snd_pcm_stream_lock_irq(psubstream); +		if (runtime->status->state != SNDRV_PCM_STATE_DRAINING && +		    (runtime->status->state != SNDRV_PCM_STATE_RUNNING || +		     snd_pcm_oss_playback_ready(psubstream))) +			mask |= POLLOUT | POLLWRNORM; +		snd_pcm_stream_unlock_irq(psubstream); +	} +	if (csubstream != NULL) { +		snd_pcm_runtime_t *runtime = csubstream->runtime; +		enum sndrv_pcm_state ostate; +		poll_wait(file, &runtime->sleep, wait); +		snd_pcm_stream_lock_irq(csubstream); +		if ((ostate = runtime->status->state) != SNDRV_PCM_STATE_RUNNING || +		    snd_pcm_oss_capture_ready(csubstream)) +			mask |= POLLIN | POLLRDNORM; +		snd_pcm_stream_unlock_irq(csubstream); +		if (ostate != SNDRV_PCM_STATE_RUNNING && runtime->oss.trigger) { +			snd_pcm_oss_file_t ofile; +			memset(&ofile, 0, sizeof(ofile)); +			ofile.streams[SNDRV_PCM_STREAM_CAPTURE] = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; +			runtime->oss.trigger = 0; +			snd_pcm_oss_set_trigger(&ofile, PCM_ENABLE_INPUT); +		} +	} + +	return mask; +} + +static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area) +{ +	snd_pcm_oss_file_t *pcm_oss_file; +	snd_pcm_substream_t *substream = NULL; +	snd_pcm_runtime_t *runtime; +	int err; + +#ifdef OSS_DEBUG +	printk("pcm_oss: mmap begin\n"); +#endif +	pcm_oss_file = file->private_data; +	switch ((area->vm_flags & (VM_READ | VM_WRITE))) { +	case VM_READ | VM_WRITE: +		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +		if (substream) +			break; +		/* Fall through */ +	case VM_READ: +		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; +		break; +	case VM_WRITE: +		substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; +		break; +	default: +		return -EINVAL; +	} +	/* set VM_READ access as well to fix memset() routines that do +	   reads before writes (to improve performance) */ +	area->vm_flags |= VM_READ; +	if (substream == NULL) +		return -ENXIO; +	runtime = substream->runtime; +	if (!(runtime->info & SNDRV_PCM_INFO_MMAP_VALID)) +		return -EIO; +	if (runtime->info & SNDRV_PCM_INFO_INTERLEAVED) +		runtime->access = SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; +	else +		return -EIO; +	 +	if (runtime->oss.params) { +		if ((err = snd_pcm_oss_change_params(substream)) < 0) +			return err; +	} +	if (runtime->oss.plugin_first != NULL) +		return -EIO; + +	if (area->vm_pgoff != 0) +		return -EINVAL; + +	err = snd_pcm_mmap_data(substream, file, area); +	if (err < 0) +		return err; +	runtime->oss.mmap_bytes = area->vm_end - area->vm_start; +	runtime->silence_threshold = 0; +	runtime->silence_size = 0; +#ifdef OSS_DEBUG +	printk("pcm_oss: mmap ok, bytes = 0x%x\n", runtime->oss.mmap_bytes); +#endif +	/* In mmap mode we never stop */ +	runtime->stop_threshold = runtime->boundary; + +	return 0; +} + +/* + *  /proc interface + */ + +static void snd_pcm_oss_proc_read(snd_info_entry_t *entry, +				  snd_info_buffer_t * buffer) +{ +	snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data; +	snd_pcm_oss_setup_t *setup = pstr->oss.setup_list; +	down(&pstr->oss.setup_mutex); +	while (setup) { +		snd_iprintf(buffer, "%s %u %u%s%s%s%s%s%s\n", +			    setup->task_name, +			    setup->periods, +			    setup->period_size, +			    setup->disable ? " disable" : "", +			    setup->direct ? " direct" : "", +			    setup->block ? " block" : "", +			    setup->nonblock ? " non-block" : "", +			    setup->partialfrag ? " partial-frag" : "", +			    setup->nosilence ? " no-silence" : ""); +		setup = setup->next; +	} +	up(&pstr->oss.setup_mutex); +} + +static void snd_pcm_oss_proc_free_setup_list(snd_pcm_str_t * pstr) +{ +	unsigned int idx; +	snd_pcm_substream_t *substream; +	snd_pcm_oss_setup_t *setup, *setupn; + +	for (idx = 0, substream = pstr->substream; +	     idx < pstr->substream_count; idx++, substream = substream->next) +		substream->oss.setup = NULL; +	for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL; +	     setup; setup = setupn) { +		setupn = setup->next; +		kfree(setup->task_name); +		kfree(setup); +	} +	pstr->oss.setup_list = NULL; +} + +static void snd_pcm_oss_proc_write(snd_info_entry_t *entry, +				   snd_info_buffer_t * buffer) +{ +	snd_pcm_str_t *pstr = (snd_pcm_str_t *)entry->private_data; +	char line[128], str[32], task_name[32], *ptr; +	int idx1; +	snd_pcm_oss_setup_t *setup, *setup1, template; + +	while (!snd_info_get_line(buffer, line, sizeof(line))) { +		down(&pstr->oss.setup_mutex); +		memset(&template, 0, sizeof(template)); +		ptr = snd_info_get_str(task_name, line, sizeof(task_name)); +		if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) { +			snd_pcm_oss_proc_free_setup_list(pstr); +			up(&pstr->oss.setup_mutex); +			continue; +		} +		for (setup = pstr->oss.setup_list; setup; setup = setup->next) { +			if (!strcmp(setup->task_name, task_name)) { +				template = *setup; +				break; +			} +		} +		ptr = snd_info_get_str(str, ptr, sizeof(str)); +		template.periods = simple_strtoul(str, NULL, 10); +		ptr = snd_info_get_str(str, ptr, sizeof(str)); +		template.period_size = simple_strtoul(str, NULL, 10); +		for (idx1 = 31; idx1 >= 0; idx1--) +			if (template.period_size & (1 << idx1)) +				break; +		for (idx1--; idx1 >= 0; idx1--) +			template.period_size &= ~(1 << idx1); +		do { +			ptr = snd_info_get_str(str, ptr, sizeof(str)); +			if (!strcmp(str, "disable")) { +				template.disable = 1; +			} else if (!strcmp(str, "direct")) { +				template.direct = 1; +			} else if (!strcmp(str, "block")) { +				template.block = 1; +			} else if (!strcmp(str, "non-block")) { +				template.nonblock = 1; +			} else if (!strcmp(str, "partial-frag")) { +				template.partialfrag = 1; +			} else if (!strcmp(str, "no-silence")) { +				template.nosilence = 1; +			} +		} while (*str); +		if (setup == NULL) { +			setup = (snd_pcm_oss_setup_t *) kmalloc(sizeof(snd_pcm_oss_setup_t), GFP_KERNEL); +			if (setup) { +				if (pstr->oss.setup_list == NULL) { +					pstr->oss.setup_list = setup; +				} else { +					for (setup1 = pstr->oss.setup_list; setup1->next; setup1 = setup1->next); +					setup1->next = setup; +				} +				template.task_name = snd_kmalloc_strdup(task_name, GFP_KERNEL); +			} else { +				buffer->error = -ENOMEM; +			} +		} +		if (setup) +			*setup = template; +		up(&pstr->oss.setup_mutex); +	} +} + +static void snd_pcm_oss_proc_init(snd_pcm_t *pcm) +{ +	int stream; +	for (stream = 0; stream < 2; ++stream) { +		snd_info_entry_t *entry; +		snd_pcm_str_t *pstr = &pcm->streams[stream]; +		if (pstr->substream_count == 0) +			continue; +		if ((entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root)) != NULL) { +			entry->content = SNDRV_INFO_CONTENT_TEXT; +			entry->mode = S_IFREG | S_IRUGO | S_IWUSR; +			entry->c.text.read_size = 8192; +			entry->c.text.read = snd_pcm_oss_proc_read; +			entry->c.text.write_size = 8192; +			entry->c.text.write = snd_pcm_oss_proc_write; +			entry->private_data = pstr; +			if (snd_info_register(entry) < 0) { +				snd_info_free_entry(entry); +				entry = NULL; +			} +		} +		pstr->oss.proc_entry = entry; +	} +} + +static void snd_pcm_oss_proc_done(snd_pcm_t *pcm) +{ +	int stream; +	for (stream = 0; stream < 2; ++stream) { +		snd_pcm_str_t *pstr = &pcm->streams[stream]; +		if (pstr->oss.proc_entry) { +			snd_info_unregister(pstr->oss.proc_entry); +			pstr->oss.proc_entry = NULL; +			snd_pcm_oss_proc_free_setup_list(pstr); +		} +	} +} + +/* + *  ENTRY functions + */ + +static struct file_operations snd_pcm_oss_f_reg = +{ +	.owner =	THIS_MODULE, +	.read =		snd_pcm_oss_read, +	.write =	snd_pcm_oss_write, +	.open =		snd_pcm_oss_open, +	.release =	snd_pcm_oss_release, +	.poll =		snd_pcm_oss_poll, +	.unlocked_ioctl =	snd_pcm_oss_ioctl, +	.compat_ioctl =	snd_pcm_oss_ioctl_compat, +	.mmap =		snd_pcm_oss_mmap, +}; + +static snd_minor_t snd_pcm_oss_reg = +{ +	.comment =	"digital audio", +	.f_ops =	&snd_pcm_oss_f_reg, +}; + +static void register_oss_dsp(snd_pcm_t *pcm, int index) +{ +	char name[128]; +	sprintf(name, "dsp%i%i", pcm->card->number, pcm->device); +	if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, +				    pcm->card, index, &snd_pcm_oss_reg, +				    name) < 0) { +		snd_printk("unable to register OSS PCM device %i:%i\n", pcm->card->number, pcm->device); +	} +} + +static int snd_pcm_oss_register_minor(snd_pcm_t * pcm) +{ +	pcm->oss.reg = 0; +	if (dsp_map[pcm->card->number] == (int)pcm->device) { +		char name[128]; +		int duplex; +		register_oss_dsp(pcm, 0); +		duplex = (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count > 0 &&  +			      pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count &&  +			      !(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)); +		sprintf(name, "%s%s", pcm->name, duplex ? " (DUPLEX)" : ""); +#ifdef SNDRV_OSS_INFO_DEV_AUDIO +		snd_oss_info_register(SNDRV_OSS_INFO_DEV_AUDIO, +				      pcm->card->number, +				      name); +#endif +		pcm->oss.reg++; +		pcm->oss.reg_mask |= 1; +	} +	if (adsp_map[pcm->card->number] == (int)pcm->device) { +		register_oss_dsp(pcm, 1); +		pcm->oss.reg++; +		pcm->oss.reg_mask |= 2; +	} + +	if (pcm->oss.reg) +		snd_pcm_oss_proc_init(pcm); + +	return 0; +} + +static int snd_pcm_oss_disconnect_minor(snd_pcm_t * pcm) +{ +	if (pcm->oss.reg) { +		if (pcm->oss.reg_mask & 1) { +			pcm->oss.reg_mask &= ~1; +			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, +						  pcm->card, 0); +		} +		if (pcm->oss.reg_mask & 2) { +			pcm->oss.reg_mask &= ~2; +			snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, +						  pcm->card, 1); +		} +	} +	return 0; +} + +static int snd_pcm_oss_unregister_minor(snd_pcm_t * pcm) +{ +	snd_pcm_oss_disconnect_minor(pcm); +	if (pcm->oss.reg) { +		if (dsp_map[pcm->card->number] == (int)pcm->device) { +#ifdef SNDRV_OSS_INFO_DEV_AUDIO +			snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number); +#endif +		} +		pcm->oss.reg = 0; +		snd_pcm_oss_proc_done(pcm); +	} +	return 0; +} + +static snd_pcm_notify_t snd_pcm_oss_notify = +{ +	.n_register =	snd_pcm_oss_register_minor, +	.n_disconnect = snd_pcm_oss_disconnect_minor, +	.n_unregister =	snd_pcm_oss_unregister_minor, +}; + +static int __init alsa_pcm_oss_init(void) +{ +	int i; +	int err; + +	/* check device map table */ +	for (i = 0; i < SNDRV_CARDS; i++) { +		if (dsp_map[i] < 0 || dsp_map[i] >= SNDRV_PCM_DEVICES) { +			snd_printk("invalid dsp_map[%d] = %d\n", i, dsp_map[i]); +			dsp_map[i] = 0; +		} +		if (adsp_map[i] < 0 || adsp_map[i] >= SNDRV_PCM_DEVICES) { +			snd_printk("invalid adsp_map[%d] = %d\n", i, adsp_map[i]); +			adsp_map[i] = 1; +		} +	} +	if ((err = snd_pcm_notify(&snd_pcm_oss_notify, 0)) < 0) +		return err; +	return 0; +} + +static void __exit alsa_pcm_oss_exit(void) +{ +	snd_pcm_notify(&snd_pcm_oss_notify, 1); +} + +module_init(alsa_pcm_oss_init) +module_exit(alsa_pcm_oss_exit)  |