/*
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#if 0
#define PLUGIN_DEBUG
#endif

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../include/driver.h"
#include "../include/minors.h"
#include "../include/pcm.h"
#include "../include/info.h"

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_voices(snd_pcm_oss_file_t *pcm_oss_file);
static int snd_pcm_oss_get_format(snd_pcm_oss_file_t *pcm_oss_file);

EXPORT_NO_SYMBOLS;

int snd_pcm_oss_plugin_clear(snd_pcm_runtime_t *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;
}

int snd_pcm_oss_plugin_append(snd_pcm_runtime_t *runtime, snd_pcm_plugin_t *plugin)
{
	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_ratio(snd_pcm_runtime_t *runtime, long val)
{
	if (runtime->buf.block.frag_size == runtime->oss.fragment_size)
		return val;
	if (runtime->buf.block.frag_size < runtime->oss.fragment_size)
		return val * (runtime->oss.fragment_size / runtime->buf.block.frag_size);
	return val / (runtime->buf.block.frag_size / runtime->oss.fragment_size);
}

static int snd_pcm_oss_format_from(int format)
{
	switch (format) {
	case SND_PCM_AFMT_MU_LAW:	return SND_PCM_SFMT_MU_LAW;
	case SND_PCM_AFMT_A_LAW:	return SND_PCM_SFMT_A_LAW;
	case SND_PCM_AFMT_IMA_ADPCM:	return SND_PCM_SFMT_IMA_ADPCM;
	case SND_PCM_AFMT_U8:		return SND_PCM_SFMT_U8;
	case SND_PCM_AFMT_S16_LE:	return SND_PCM_SFMT_S16_LE;
	case SND_PCM_AFMT_S16_BE:	return SND_PCM_SFMT_S16_BE;
	case SND_PCM_AFMT_S8:		return SND_PCM_SFMT_S8;
	case SND_PCM_AFMT_U16_LE:	return SND_PCM_SFMT_U16_LE;
	case SND_PCM_AFMT_U16_BE:	return SND_PCM_SFMT_U16_BE;
	case SND_PCM_AFMT_MPEG:		return SND_PCM_SFMT_MPEG;
	default:			return SND_PCM_SFMT_U8;
	}
}

static int snd_pcm_oss_format_to(int format)
{
	switch (format) {
	case SND_PCM_SFMT_MU_LAW:	return SND_PCM_AFMT_MU_LAW;
	case SND_PCM_SFMT_A_LAW:	return SND_PCM_AFMT_A_LAW;
	case SND_PCM_SFMT_IMA_ADPCM:	return SND_PCM_AFMT_IMA_ADPCM;
	case SND_PCM_SFMT_U8:		return SND_PCM_AFMT_U8;
	case SND_PCM_SFMT_S16_LE:	return SND_PCM_AFMT_S16_LE;
	case SND_PCM_SFMT_S16_BE:	return SND_PCM_AFMT_S16_BE;
	case SND_PCM_SFMT_S8:		return SND_PCM_AFMT_S8;
	case SND_PCM_SFMT_U16_LE:	return SND_PCM_AFMT_U16_LE;
	case SND_PCM_SFMT_U16_BE:	return SND_PCM_AFMT_U16_BE;
	case SND_PCM_SFMT_MPEG:		return SND_PCM_AFMT_MPEG;
	default:			return -EINVAL;
	}
}

static int snd_pcm_oss_change_params(snd_pcm_subchn_t *subchn)
{
	unsigned int n;
	unsigned sr, nc, sz, bsz;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_pcm_channel_info_t hwinfo;
	snd_pcm_channel_params_t params, hwparams;
	snd_pcm_channel_params_t params1, hwparams1;
	snd_pcm_channel_setup_t setup;
	int err;

	if (!runtime->oss.params)
		return 0;
	runtime->oss.params = 0;
	sr = snd_pcm_oss_get_rate(subchn->oss.file);
	nc = snd_pcm_oss_get_voices(subchn->oss.file);
	/* if bellow line is correct, the OSS kernel code is really ugly */
	sz = snd_pcm_oss_get_format(subchn->oss.file);
	runtime->oss.params = 1;

	if (sr < 1 || nc < 1 || sz < 1) {
		sr = 8000;
		nc = 1;
		sz = 8;
	}
	
	sz = (sr * nc * sz) / 8;
			
	memset(&hwinfo, 0, sizeof(hwinfo));
	hwinfo.channel = subchn->channel;
	err = snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_INFO, (long)&hwinfo);
	if (err < 0) {
		snd_printd("snd_pcm_oss_change_params: SND_PCM_IOCTL_CHANNEL_INFO failed\n");
		return err;
	}
	if (runtime->oss.fragment == 0) {
		bsz = hwinfo.buffer_size;
		while (bsz > sz)
			bsz /= 2;
		if (bsz == hwinfo.buffer_size)
			bsz /= 2;
		if (runtime->oss.subdivision == 0) {
			runtime->oss.subdivision = 4;
			if ((bsz / runtime->oss.subdivision) > 4096)
				runtime->oss.subdivision *= 2;
			if ((bsz / runtime->oss.subdivision) < 4096)
				runtime->oss.subdivision = 1;
		}
		bsz /= runtime->oss.subdivision;
		if (bsz < 16)
			bsz = 16;
		runtime->oss.fragment_size = bsz;
	} else {
		runtime->oss.fragment_size = 1 << (runtime->oss.fragment & 0xffff);
		if (runtime->oss.fragment_size > (hwinfo.buffer_size / 2))
			runtime->oss.fragment_size = hwinfo.buffer_size / 2;
		bsz = runtime->oss.fragment_size;
	}
	if (hwinfo.min_fragment_size)
		if (bsz < hwinfo.min_fragment_size)
			bsz = hwinfo.min_fragment_size;
	if (hwinfo.max_fragment_size)
		if (bsz > hwinfo.max_fragment_size)
			bsz = hwinfo.max_fragment_size;
	bsz &= ~7;
	n = hwinfo.buffer_size / bsz;
	if (n > 128)
		n = 128;
	if (runtime->oss.fragment)
		if (n > ((runtime->oss.fragment >> 16) & 0xffff) + 1)
			n = ((runtime->oss.fragment >> 16) & 0xffff) + 1;
	runtime->oss.fragments = n;
	runtime->oss.fragment_size = bsz;
	if (subchn->oss.setup) {
		if (subchn->oss.setup->fragments > 1)
			runtime->oss.fragments = subchn->oss.setup->fragments;
		if (subchn->oss.setup->fragment_size > 64)
			runtime->oss.fragment_size = subchn->oss.setup->fragment_size;
	}

	memset(&params, 0, sizeof(params));
	params.channel = subchn->channel;
	params.mode = SND_PCM_MODE_BLOCK;
	params.format.format = snd_pcm_oss_format_from(runtime->oss.format);
	params.format.interleave = 1;
	params.format.voices = runtime->oss.voices;
	params.format.rate = runtime->oss.rate;
	params.sync = runtime->sync_group;
	if (runtime->oss.trigger) {
		params.start_mode = subchn->channel == SND_PCM_CHANNEL_PLAYBACK ?
					SND_PCM_START_FULL : SND_PCM_START_DATA;
	} else {
		params.start_mode = SND_PCM_START_GO;
	}
	params.stop_mode = SND_PCM_STOP_STOP;
	params.buf.block.frag_size = runtime->oss.fragment_size;
	params.buf.block.frags_min = 1;
	params.buf.block.frags_max = runtime->oss.fragments - 1;

	snd_pcm_oss_plugin_clear(runtime);
	if (!(runtime->flags & SND_PCM_FLG_OSS_MMAP)) {
		if ((err = snd_pcm_plugin_hwparams(&params, &hwinfo, &hwparams)) < 0)
			return err;
		
		memcpy(&params1, &params, sizeof(params));
		memcpy(&hwparams1, &hwparams, sizeof(hwparams));

		/* add necessary plugins */
		if ((err = snd_pcm_plugin_format(runtime, &params1, 
						 &hwparams1, &hwinfo)) < 0) {
			snd_pcm_oss_plugin_clear(runtime);
			return err;
		}
	} else {
		memcpy(&params1, &params, sizeof(params));
		memcpy(&hwparams1, &params, sizeof(params));
	}

	hwparams1.buf.block.frag_size = snd_pcm_plugin_hardware_size(runtime, subchn->channel, hwparams1.buf.block.frag_size);

	snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_FLUSH, 0);

	pdprintf("format = %i, voices = %i, rate = %i, frag_size = %i, frags_max = %i\n",
		 hwparams1.format.format, hwparams1.format.voices, hwparams1.format.rate,
		 hwparams1.buf.block.frag_size, hwparams1.buf.block.frags_max);

	if ((err = snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_PARAMS, (long)&hwparams1)) < 0) {
		snd_printd("snd_pcm_oss_change_params: SND_PCM_IOCTL_CHANNEL_PARAMS failed\n");
		return err;
	}

	memset(&setup, 0, sizeof(setup));
	setup.channel = subchn->channel;
	setup.mode = SND_PCM_MODE_BLOCK;
	if ((err = snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_SETUP, (long)&setup)) < 0) {
		snd_printd("snd_pcm_oss_change_params: SND_PCM_IOCTL_CHANNEL_SETUP failed\n");
		return err;
	}
	pdprintf("setup: format = %i, voices = %i, rate = %i, frag_size = %i, frags_max = %i\n",
		 setup.format.format, setup.format.voices, setup.format.rate,
		 setup.buf.block.frag_size, setup.buf.block.frags_max);
	
	runtime->oss.fragments = hwparams1.buf.block.frags_max + 1;
	if (runtime->oss.fragments > runtime->frags)
		runtime->oss.fragments = runtime->frags;
	runtime->oss.fragment_size = snd_pcm_plugin_transfer_size(runtime, subchn->channel, setup.buf.block.frag_size);

	pdprintf("oss fragments = %i (%i), fragment size = %i (%i)\n",
		 runtime->oss.fragments, runtime->oss.mmap_fragments,
		 runtime->oss.fragment_size, runtime->oss.mmap_fragment_size);

	if (runtime->flags & SND_PCM_FLG_OSS_MMAP) {
		if (runtime->oss.fragment_size * runtime->oss.fragments !=
		    runtime->oss.mmap_fragment_size * runtime->oss.mmap_fragments)
			return -EIO;
	}

	if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK) {
		if (hwparams.format.format != setup.format.format)
			runtime->oss.format = snd_pcm_oss_format_to(setup.format.format);
		if (hwparams.format.voices != setup.format.voices)
			runtime->oss.voices = setup.format.voices;
		if (hwparams.format.rate != setup.format.rate)
			runtime->oss.rate = setup.format.rate;
	}
	else {
		if (params.format.format != params1.format.format)
			runtime->oss.format = snd_pcm_oss_format_to(params1.format.format);
		if (params.format.voices != params1.format.voices)
			runtime->oss.voices = params1.format.voices;
		if (params.format.rate != params1.format.rate)
			runtime->oss.rate = params1.format.rate;
	}

	runtime->oss.params = 0;
	runtime->oss.prepare = 1;
	return 0;
}

static int snd_pcm_oss_get_active_subchn(snd_pcm_oss_file_t *pcm_oss_file, snd_pcm_subchn_t **r_subchn)
{
	int idx, err;
	snd_pcm_subchn_t *result = NULL, *subchn;

	if (r_subchn)
		*r_subchn = NULL;
	for (idx = 0; idx < 2; idx++) {
		subchn = pcm_oss_file->chn[idx];
		if (subchn == NULL)
			continue;
		if (result == NULL)
			result = subchn;
		if (subchn->runtime->oss.params)
			if ((err = snd_pcm_oss_change_params(subchn)) < 0)
				return err;
	}
	if (r_subchn)
		*r_subchn = result;
	return 0;
}

static int snd_pcm_oss_prepare(snd_pcm_subchn_t * subchn)
{
	int err;
	snd_pcm_runtime_t *runtime = subchn->runtime;

	if (!runtime->oss.prepare)
		return 0;
	err = snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_PREPARE, 0);
	if (err < 0) {
		snd_printd("snd_pcm_oss_prepare: SND_PCM_IOCTL_CHANNEL_PREPARE failed\n");
		return err;
	}
	runtime->oss.prepare = 0;
	return 0;
}

static int snd_pcm_oss_make_ready(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime;
	int tmp;

	if (subchn == NULL)
		return 0;
	runtime = subchn->runtime;
	if (runtime->oss.params) {
		tmp = snd_pcm_oss_get_active_subchn(subchn->oss.file, NULL);
		if (tmp < 0)
			return tmp;
		if (runtime->oss.params)
			return -EIO;
	}
	if (runtime->oss.prepare) {
		tmp = snd_pcm_oss_prepare(subchn);
		if (tmp < 0)
			return tmp;
	}
	return 0;
}

static char *snd_pcm_oss_alloc(snd_pcm_runtime_t * runtime, long len)
{
	int idx;

	for (idx = 0; idx < 2; idx++) {
		if (runtime->oss.xbuffer_lock[idx])
			continue;
		if (runtime->oss.xbuffer_size[idx] >= len) {
			runtime->oss.xbuffer_lock[idx] = 1;
			return runtime->oss.xbuffer[idx];		
		}
	}
	for (idx = 0; idx < 2; idx++) {
		if (runtime->oss.xbuffer_lock[idx])
			continue;
		if (runtime->oss.xbuffer[idx] == NULL) {
			runtime->oss.xbuffer[idx] = snd_vmalloc(len);
			if (runtime->oss.xbuffer[idx]) {
				runtime->oss.xbuffer_size[idx] = len;
				runtime->oss.xbuffer_lock[idx] = 1;
			}
			return runtime->oss.xbuffer[idx];
		}
	}
	for (idx = 0; idx < 2; idx++) {
		if (runtime->oss.xbuffer_lock[idx])
			continue;
		snd_vfree(runtime->oss.xbuffer[idx]);
		runtime->oss.xbuffer[idx] = NULL;
		runtime->oss.xbuffer_size[idx] = 0;
		runtime->oss.xbuffer_lock[idx] = 0;
		runtime->oss.xbuffer[idx] = snd_vmalloc(len);
		if (runtime->oss.xbuffer[idx]) {
			runtime->oss.xbuffer_size[idx] = len;
			runtime->oss.xbuffer_lock[idx] = 1;
		}
		return runtime->oss.xbuffer[idx];
	}
	return NULL;
}

static void snd_pcm_oss_alloc_unlock(snd_pcm_runtime_t * runtime, char *buf)
{
	int idx;

	if (buf == NULL)
		return;
	for (idx = 0; idx < 2; idx++) {
		if (runtime->oss.xbuffer[idx] == buf) {
			runtime->oss.xbuffer_lock[idx] = 0;
			return;
		}
	}
}

static void snd_pcm_oss_alloc_free(snd_pcm_runtime_t * runtime)
{
	int idx;
	
	for (idx = 0; idx < 2; idx++) {
		if (runtime->oss.xbuffer[idx]) {
			snd_vfree(runtime->oss.xbuffer[idx]);
			runtime->oss.xbuffer[idx] = NULL;
			runtime->oss.xbuffer_size[idx] = 0;
			runtime->oss.xbuffer_lock[idx] = 0;
		}
	}
}

static long snd_pcm_oss_write2(snd_pcm_subchn_t * subchn, char *src_ptr, long src_size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_pcm_plugin_t *plugin;
	char *dst_ptr;
	long dst_size, result, tmp;
	mm_segment_t fs;
	int in_kernel = (src_ptr == runtime->oss.buffer);
	int src_alloced = 0;

	plugin = runtime->oss.plugin_first;
	while (plugin) {
		if (!in_kernel) {
			if (runtime->oss.buffer == NULL) {
				runtime->oss.buffer = snd_vmalloc(src_size);
				if (runtime->oss.buffer == NULL) {
					result = -ENOMEM;
					goto __end;
				}
			}
			if (copy_from_user(runtime->oss.buffer, src_ptr, src_size)) {
				result = -EFAULT;
				goto __end;
			}
			src_ptr = runtime->oss.buffer;
			in_kernel = 1;
		}
		dst_size = plugin->dst_size ? plugin->dst_size(plugin, src_size) : src_size;
		dst_ptr = snd_pcm_oss_alloc(runtime, dst_size);
		if (dst_ptr == NULL) {
			result = -ENOMEM;
			goto __end;
		}
		tmp = plugin->transfer(plugin, src_ptr, src_size, dst_ptr, dst_size);
		if (src_alloced) 
			snd_pcm_oss_alloc_unlock(runtime, src_ptr);
		else
			src_alloced = 1;
		src_ptr = dst_ptr;
		if (tmp <= 0) {
			result = tmp;
			goto __end;
		}

		src_size = tmp;
		plugin = plugin->next;
	}
	while (1) {
		if (in_kernel)
			fs = snd_enter_user();
		tmp = snd_pcm_lib_write(subchn, src_ptr, src_size);
		if (in_kernel)
			snd_leave_user(fs);
		if (tmp <= 0) {
			if (*runtime->status == SND_PCM_STATUS_UNDERRUN) {
				runtime->oss.prepare = 1;
				tmp = snd_pcm_oss_make_ready(subchn);
				if (tmp >= 0)
					continue;
			}
			result = tmp;
			goto __end;
		}
		if (tmp != src_size)
			result = snd_pcm_plugin_transfer_size(runtime, subchn->channel, tmp);
		break;
	}
	result = snd_pcm_plugin_transfer_size(runtime, subchn->channel, tmp);
 __end:
	if (src_alloced) 
		snd_pcm_oss_alloc_unlock(runtime, src_ptr);
	return result;
}

static long snd_pcm_oss_write1(snd_pcm_subchn_t * subchn, const char *buf, long count)
{
	long result = 0, tmp;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	mm_segment_t fs;

	if ((tmp = snd_pcm_oss_make_ready(subchn)) < 0)
		return tmp;
	while (count > 0) {
		if (count < runtime->oss.fragment_size || runtime->oss.buffer_used > 0) {
			if (runtime->oss.buffer == NULL) {
				runtime->oss.buffer = snd_vmalloc(runtime->oss.fragment_size);
				if (runtime->oss.buffer == NULL)
					return result > 0 ? result : -ENOMEM;
				runtime->oss.buffer_used = 0;
			}
			tmp = count;
			if (tmp + runtime->oss.buffer_used > runtime->oss.fragment_size)
				tmp = runtime->oss.fragment_size - runtime->oss.buffer_used;
			if (tmp > 0) {
				if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp))
					return result > 0 ? result : -EFAULT;
			}
			runtime->oss.buffer_used += tmp;
			buf += tmp;
			count -= tmp;
			result += tmp;
			if (runtime->oss.buffer_used == runtime->oss.fragment_size) {
				fs = snd_enter_user();
				tmp = snd_pcm_oss_write2(subchn, runtime->oss.buffer, runtime->oss.fragment_size);
				snd_leave_user(fs);
				if (tmp <= 0)
					return result > 0 ? result : tmp;
				runtime->oss.buffer_used = 0;
			}
		} else {
			tmp = snd_pcm_oss_write2(subchn, (char *)buf, runtime->oss.fragment_size);
			if (tmp <= 0)
				return result > 0 ? result : tmp;
			buf += tmp;
			count -= tmp;
			result += tmp;
		}
	}
	if (runtime->frag_used > 0 && *runtime->status == SND_PCM_STATUS_PREPARED &&
	    runtime->oss.trigger)
		snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
	return result;
}

static long snd_pcm_oss_read2(snd_pcm_subchn_t * subchn, char *final_ptr, long final_size)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_pcm_plugin_t *plugin;
	char *src_ptr, *dst_ptr;
	long src_size, dst_size, result, tmp;
	mm_segment_t fs;
	int in_kernel;
	int src_alloced = 0;

	plugin = runtime->oss.plugin_first;
	if (plugin) {
		src_size = snd_pcm_plugin_hardware_size(runtime, subchn->channel, final_size);
		src_ptr = snd_pcm_oss_alloc(runtime, src_size);
		if (src_ptr == NULL) {
			result = -ENOMEM;
			goto __end;
		}
		src_alloced = 1;
		in_kernel = 1;
	} else {
		src_size = final_size;
		src_ptr = final_ptr;
		in_kernel = (src_ptr == runtime->oss.buffer);
	}
		
	while (1) {
		if (in_kernel)
			fs = snd_enter_user();
		tmp = snd_pcm_lib_read(subchn, src_ptr, src_size);
		if (in_kernel)
			snd_leave_user(fs);
		if (tmp <= 0) {
			if (*runtime->status == SND_PCM_STATUS_OVERRUN) {
				runtime->oss.prepare = 1;
				tmp = snd_pcm_oss_make_ready(subchn);
				if (tmp >= 0)
					continue;
			}
			result = tmp;
			goto __end;
		}
		src_size = tmp;
		break;
	}

	while (plugin) {
		dst_size = plugin->dst_size ? plugin->dst_size(plugin, src_size) : src_size;
		if (plugin->next == NULL && final_ptr == runtime->oss.buffer) {
			dst_ptr = final_ptr;
		} else {
			dst_ptr = snd_pcm_oss_alloc(runtime, dst_size);
			if (dst_ptr == NULL) {
				result = -ENOMEM;
				goto __end;
			}
		}
		tmp = plugin->transfer(plugin, src_ptr, src_size, dst_ptr, dst_size);
		if (src_alloced) 
			snd_pcm_oss_alloc_unlock(runtime, src_ptr);
		else
			src_alloced = 1;
		src_ptr = dst_ptr;
		if (tmp <= 0) {
			result = tmp;
			goto __end;
		}
		src_size = tmp;
		plugin = plugin->next;
	}
	if (src_ptr != final_ptr) {
		if (copy_to_user(final_ptr, src_ptr, src_size)) {
			result = -EFAULT;
			goto __end;
		}
	}
	result = src_size;
 __end:
	if (src_alloced)
		snd_pcm_oss_alloc_unlock(runtime, src_ptr);
	return result;
}

static long snd_pcm_oss_read1(snd_pcm_subchn_t * subchn, char *buf, long count)
{
	long result = 0, tmp;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	mm_segment_t fs;

	if ((tmp = snd_pcm_oss_make_ready(subchn)) < 0)
		return tmp;
	while (count > 0) {
		if (count < runtime->oss.fragment_size || runtime->oss.buffer_used > 0) {
			if (runtime->oss.buffer == NULL) {
				runtime->oss.buffer = snd_vmalloc(runtime->oss.fragment_size);
				if (runtime->oss.buffer == NULL)
					return result > 0 ? result : -ENOMEM;
				runtime->oss.buffer_used = 0;
			}
			if (runtime->oss.buffer_used == 0) {
				fs = snd_enter_user();
				tmp = snd_pcm_oss_read2(subchn, runtime->oss.buffer, runtime->oss.fragment_size);
				snd_leave_user(fs);
				if (tmp <= 0)
					return result > 0 ? result : tmp;
				runtime->oss.buffer_used = runtime->oss.fragment_size;
			}
			tmp = count;
			if (tmp > runtime->oss.buffer_used)
				tmp = runtime->oss.buffer_used;
			if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.fragment_size - runtime->oss.buffer_used), tmp))
				return result > 0 ? result : -EFAULT;
			buf += tmp;
			count -= tmp;
			result += tmp;
			runtime->oss.buffer_used -= tmp;
		} else {
			tmp = snd_pcm_oss_read2(subchn, (char *)buf, runtime->oss.fragment_size);
			if (tmp <= 0)
				return result > 0 ? result : tmp;
			buf += tmp;
			count -= tmp;
			result += tmp;
		}
	}
	return result;
}

static int snd_pcm_oss_reset(snd_pcm_oss_file_t *pcm_oss_file)
{
	snd_pcm_subchn_t *subchn;

	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	if (subchn != NULL) {
		snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_DRAIN, 0);
		subchn->runtime->oss.prepare = 1;
	}
	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];
	if (subchn != NULL) {
		snd_pcm_kernel_capture_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_FLUSH, 0);
		subchn->runtime->oss.prepare = 1;
	}
	return 0;
}

static int snd_pcm_oss_sync(snd_pcm_oss_file_t *pcm_oss_file)
{
	int err = 0, neutral;
	snd_pcm_subchn_t *subchn;
	snd_pcm_runtime_t *runtime;
	long result;
	mm_segment_t fs;

	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	if (subchn != NULL) {
		if ((err = snd_pcm_oss_make_ready(subchn)) < 0)
			return err;
		runtime = subchn->runtime;
		while (runtime->oss.buffer && runtime->oss.buffer_used > 0) {
			neutral = snd_pcm_format_silence(runtime->format.format);
			memset(runtime->oss.buffer + runtime->oss.buffer_used, neutral, runtime->oss.fragment_size - runtime->oss.buffer_used);
			fs = snd_enter_user();
			result = snd_pcm_oss_write2(subchn, runtime->oss.buffer, runtime->oss.fragment_size);
			snd_leave_user(fs);
			if (result == 0 || result == -EAGAIN) {
				interruptible_sleep_on_timeout(&runtime->sleep, HZ / 10);
			} else if (result > 0) {
				runtime->oss.buffer_used = 0;
			} else {
				err = result;
				break;
			}
		}
		err = snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_FLUSH, 0);
		runtime->oss.buffer_used = 0;
		if (runtime->oss.buffer) {
			snd_vfree(runtime->oss.buffer);
			runtime->oss.buffer = NULL;
		}
		snd_pcm_oss_alloc_free(runtime);
		runtime->oss.prepare = 1;
	}

	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];
	if (subchn != NULL) {
		if ((err = snd_pcm_oss_make_ready(subchn)) < 0)
			return err;
		runtime = subchn->runtime;
		err = snd_pcm_kernel_capture_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_FLUSH, 0);
		runtime->oss.buffer_used = 0;
		if (runtime->oss.buffer) {
			snd_vfree(runtime->oss.buffer);
			runtime->oss.buffer = NULL;
		}
		snd_pcm_oss_alloc_free(runtime);
		runtime->oss.prepare = 1;
	}
	return err;
}

static int snd_pcm_oss_set_rate1(snd_pcm_subchn_t * subchn, int rate)
{
	snd_pcm_runtime_t *runtime;

	if (subchn == NULL)
		return 0;
	runtime = subchn->runtime;
	if (rate < 1000)
		rate = 1000;
	if (runtime->oss.rate != rate)
		runtime->oss.params = 1;
	return runtime->oss.rate = rate;
}

static int snd_pcm_oss_set_rate(snd_pcm_oss_file_t *pcm_oss_file, int rate)
{
	int err, idx;

	for (idx = 1; idx >= 0; --idx) {
		snd_pcm_subchn_t *subchn = pcm_oss_file->chn[idx];
		if (subchn == NULL)
			continue;
		if ((err = snd_pcm_oss_set_rate1(subchn, rate)) < 0)
			return err;
	}
	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_subchn_t *subchn;
	int err;
	
	if ((err = snd_pcm_oss_get_active_subchn(pcm_oss_file, &subchn)) < 0)
		return err;
	return subchn->runtime->oss.rate;
}

static int snd_pcm_oss_set_voices1(snd_pcm_subchn_t * subchn, int voices)
{
	snd_pcm_runtime_t *runtime;

	if (subchn == NULL)
		return 0;
	runtime = subchn->runtime;
	if (voices < 1)
		voices = 1;
	if (runtime->oss.voices != voices)
		runtime->oss.params = 1;
	return runtime->oss.voices = voices;
}

static int snd_pcm_oss_set_voices(snd_pcm_oss_file_t *pcm_oss_file, int voices)
{
	int idx, err;	
	
	for (idx = 1; idx >= 0; --idx) {
		snd_pcm_subchn_t *subchn = pcm_oss_file->chn[idx];
		if (subchn == NULL)
			continue;
		if ((err = snd_pcm_oss_set_voices1(subchn, voices)) < 0)
			return err;
	}
	return snd_pcm_oss_get_voices(pcm_oss_file);
}

static int snd_pcm_oss_get_voices(snd_pcm_oss_file_t *pcm_oss_file)
{
	snd_pcm_subchn_t *subchn;
	int err;
	
	if ((err = snd_pcm_oss_get_active_subchn(pcm_oss_file, &subchn)) < 0)
		return err;
	return subchn->runtime->oss.voices;
}

static int snd_pcm_oss_get_block_size(snd_pcm_oss_file_t *pcm_oss_file)
{
	snd_pcm_subchn_t *subchn;
	int err;
	
	if ((err = snd_pcm_oss_get_active_subchn(pcm_oss_file, &subchn)) < 0)
		return err;
	return subchn->runtime->oss.fragment_size;
}

static int snd_pcm_oss_get_formats(snd_pcm_oss_file_t *pcm_oss_file)
{
	int result;

	result = SND_PCM_AFMT_MU_LAW | SND_PCM_AFMT_U8 |
		SND_PCM_AFMT_S16_LE | SND_PCM_AFMT_S16_BE |
		SND_PCM_AFMT_S8 | SND_PCM_AFMT_U16_LE |
		SND_PCM_AFMT_U16_BE;       
	return result;
}

static int snd_pcm_oss_set_format1(snd_pcm_subchn_t * subchn, int format)
{
	snd_pcm_runtime_t *runtime;

	if (subchn == NULL)
		return 0;
	runtime = subchn->runtime;
	if (runtime->oss.format != format)
		runtime->oss.params = 1;
	return runtime->oss.format = format;
}

static int snd_pcm_oss_set_format(snd_pcm_oss_file_t *pcm_oss_file, int format)
{
	int formats, err, idx;
	
	if (format != SND_PCM_AFMT_QUERY) {
		formats = snd_pcm_oss_get_formats(pcm_oss_file);
		if (!(formats & format))
			format = SND_PCM_AFMT_U8;
		for (idx = 1; idx >= 0; --idx) {
			snd_pcm_subchn_t *subchn = pcm_oss_file->chn[idx];
			if (subchn == NULL)
				continue;
			if ((err = snd_pcm_oss_set_format1(subchn, format)) < 0)
				return err;
		}
	}
	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_subchn_t *subchn;
	int err;
	
	if ((err = snd_pcm_oss_get_active_subchn(pcm_oss_file, &subchn)) < 0)
		return err;
	return subchn->runtime->oss.format;
}

static int snd_pcm_oss_set_subdivide1(snd_pcm_subchn_t * subchn, int subdivide)
{
	snd_pcm_runtime_t *runtime;

	if (subchn == NULL)
		return 0;
	runtime = subchn->runtime;
	if (/* runtime->oss.subdivision || */ runtime->oss.fragment)
		return -EINVAL;		/* too late to change */
	if (subdivide == 0)
		return -EINVAL;
	if (subdivide != 1 && subdivide != 2 && subdivide != 4 &&
	    subdivide != 8 && subdivide != 16)
		return -EINVAL;
#if 0
	printk("subdivide = %i\n", subdivide);
#endif
	if (runtime->oss.subdivision == subdivide)
		return subdivide;
	runtime->oss.params = 1;
	return runtime->oss.subdivision = 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_subchn_t *subchn = pcm_oss_file->chn[idx];
		if (subchn == NULL)
			continue;
		if ((err = snd_pcm_oss_set_subdivide1(subchn, subdivide)) < 0)
			return err;
	}
	return err;
}

static int snd_pcm_oss_set_fragment1(snd_pcm_subchn_t *subchn, int fragment)
{
	snd_pcm_runtime_t *runtime;
	int bytes, count;

	if (subchn == NULL)
		return 0;
	runtime = subchn->runtime;
	if (/* runtime->oss.subdivision || */ runtime->oss.fragment)
		return -EINVAL;		/* too late to change */
	if (fragment == 0)
		return -EINVAL;
	bytes = fragment & 0xffff;
	count = (fragment >> 16) & 0xffff;
	if (count == 0 || count > 128) {
		count = 128;
	} else if (count < 128) {
		count++;
	}
	if (bytes < 4 || bytes > 17)	/* <16 || >512k */
		return -EINVAL;
	bytes |= (count-1) << 16;
	if (runtime->oss.fragment == bytes && runtime->oss.subdivision == 1)
		return bytes;
	runtime->oss.fragment = bytes;
	runtime->oss.subdivision = 1;	/* disable SUBDIVIDE */
	runtime->oss.params = 1;
	return runtime->oss.fragment;
}

static int snd_pcm_oss_set_fragment(snd_pcm_oss_file_t *pcm_oss_file, int fragment)
{
	int err = -EINVAL, idx;

	for (idx = 1; idx >= 0; --idx) {
		snd_pcm_subchn_t *subchn = pcm_oss_file->chn[idx];
		if (subchn == NULL)
			continue;
		if ((err = snd_pcm_oss_set_fragment1(subchn, fragment)) < 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_subchn_t * subchn)
{
	if (subchn == NULL)
		return 0;
	/* nothing? */
	return 0;
}

static int snd_pcm_oss_get_caps(snd_pcm_oss_file_t *pcm_oss_file)
{
	int result, idx;
	
	result = SND_PCM_CAP_TRIGGER | SND_PCM_CAP_MMAP	| SND_PCM_CAP_DUPLEX;
;
	for (idx = 0; idx < 2; idx++) {
		snd_pcm_subchn_t *subchn = pcm_oss_file->chn[idx];
		if (subchn == NULL) {
			result &= ~SND_PCM_CAP_DUPLEX;
			continue;
		}
		result |= snd_pcm_oss_get_caps1(subchn);
	}
	result |= 0x0001;	/* revision - same as SB AWE 64 */
	return result;
}

static void snd_pcm_oss_fill_sync(snd_pcm_sync_t *sync)
{
	sync->id32[0] = 0xffffffff;
	sync->id32[1] = 0xaa55aa55;
	sync->id32[2] = 0x5a5a5a5a;
	sync->id32[3] = current->pid;
}

static int snd_pcm_oss_set_trigger(snd_pcm_oss_file_t *pcm_oss_file, int trigger)
{
	snd_pcm_runtime_t *runtime;
	snd_pcm_sync_t sync;
	snd_pcm_subchn_t *psubchn = NULL, *csubchn = NULL;
	int err, cmd;
	
	psubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	csubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];

	if (psubchn && (trigger & SND_PCM_ENABLE_PLAYBACK)) {
		if ((err = snd_pcm_oss_make_ready(psubchn)) < 0)
			return err;
	}
	if (csubchn && (trigger && SND_PCM_ENABLE_CAPTURE)) {
		if ((err = snd_pcm_oss_make_ready(csubchn)) < 0)
			return err;
	}
	if ((trigger & (SND_PCM_ENABLE_PLAYBACK|SND_PCM_ENABLE_CAPTURE)) ==
		       (SND_PCM_ENABLE_PLAYBACK|SND_PCM_ENABLE_CAPTURE) &&
	    psubchn && csubchn &&
	    psubchn->runtime->oss.sync_trigger &&
	    csubchn->runtime->oss.sync_trigger) {
	    	if (psubchn->runtime->oss.trigger ||
	    	    psubchn->runtime->oss.trigger)
			goto __normal;
		snd_pcm_oss_fill_sync(&sync);
		if ((err = snd_pcm_kernel_playback_ioctl(psubchn, SND_PCM_IOCTL_SYNC_GO, (long)&sync)) < 0)
			return err;
		return 0;
	}
      __normal:
      	if (psubchn) {
      		runtime = psubchn->runtime;
		if (trigger & SND_PCM_ENABLE_PLAYBACK) {
			if (runtime->oss.trigger)
				return 0;
			runtime->oss.trigger = 1;
			runtime->start_mode = SND_PCM_START_FULL;
			cmd = SND_PCM_IOCTL_CHANNEL_GO;
		} else {
			if (!runtime->oss.trigger)
				return 0;
			runtime->oss.trigger = 0;
			runtime->start_mode = SND_PCM_START_GO;
			cmd = SND_PCM_IOCTL_CHANNEL_DRAIN;
			runtime->oss.prepare = 1;
		}
		snd_pcm_kernel_playback_ioctl(psubchn, cmd, 0);
	}
	if (csubchn) {
      		runtime = csubchn->runtime;
		if (trigger & SND_PCM_ENABLE_CAPTURE) {
			if (runtime->oss.trigger)
				return 0;
			runtime->oss.trigger = 1;
			runtime->start_mode = SND_PCM_START_DATA;
			cmd = SND_PCM_IOCTL_CHANNEL_GO;
		} else {
			if (!runtime->oss.trigger)
				return 0;
			runtime->oss.trigger = 0;
			runtime->start_mode = SND_PCM_START_GO;
			cmd = SND_PCM_IOCTL_CHANNEL_FLUSH;
			runtime->oss.prepare = 1;
		}
		snd_pcm_kernel_capture_ioctl(csubchn, cmd, 0);
	}
	return 0;
}

static int snd_pcm_oss_get_trigger(snd_pcm_oss_file_t *pcm_oss_file)
{
	snd_pcm_subchn_t *psubchn = NULL, *csubchn = NULL;
	int result = 0;

	psubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	csubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];
	if (psubchn && psubchn->runtime && psubchn->runtime->oss.trigger)
		result |= SND_PCM_ENABLE_PLAYBACK;
	if (csubchn && csubchn->runtime && csubchn->runtime->oss.trigger)
		result |= SND_PCM_ENABLE_CAPTURE;
	return result;
}

static int snd_pcm_oss_get_odelay(snd_pcm_oss_file_t *pcm_oss_file)
{
	snd_pcm_subchn_t *subchn;
	snd_pcm_runtime_t *runtime;
	snd_pcm_channel_status_t status;
	int err;

	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	if (subchn == NULL)
		return -EINVAL;
	if ((err = snd_pcm_oss_make_ready(subchn)) < 0)
		return err;
	runtime = subchn->runtime;
	if (runtime->oss.params || runtime->oss.prepare)
		return 0;
	memset(&status, 0, sizeof(status));
	err = snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_STATUS, (long)&status);
	if (err < 0)
		return err;
	return snd_pcm_oss_ratio(runtime, status.count);
}

static int snd_pcm_oss_get_ptr(snd_pcm_oss_file_t *pcm_oss_file, int channel, struct snd_pcm_count_info * _info)
{	
	snd_pcm_subchn_t *subchn;
	snd_pcm_runtime_t *runtime;
	snd_pcm_channel_status_t status;
	struct snd_pcm_count_info info;
	int err;

	if (_info == NULL)
		return -EFAULT;
	subchn = pcm_oss_file->chn[channel];
	if (subchn == NULL)
		return -EINVAL;
	if ((err = snd_pcm_oss_make_ready(subchn)) < 0)
		return err;
	runtime = subchn->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;
	}
	memset(&status, 0, sizeof(status));
	status.channel = channel;
	err = snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_STATUS, (long)&status);
	if (err < 0)
		return err;
	info.bytes = snd_pcm_oss_ratio(runtime, status.scount);
	if (runtime->flags & SND_PCM_FLG_OSS_MMAP) {
		info.blocks = status.count;
		info.ptr = snd_pcm_oss_ratio(runtime, status.scount % (runtime->frags * runtime->buf.block.frag_size));
	} else {
		info.blocks = runtime->frags - (status.free / runtime->buf.block.frag_size);
		info.ptr = snd_pcm_oss_ratio(runtime, status.scount % (runtime->frags * runtime->buf.block.frag_size));
	}
	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 channel, struct snd_pcm_buffer_info *_info)
{
	snd_pcm_subchn_t *subchn;
	snd_pcm_runtime_t *runtime;
	snd_pcm_channel_status_t status;
	struct snd_pcm_buffer_info info;
	int err;
	unsigned int tmp;

	if (_info == NULL)
		return -EFAULT;
	subchn = pcm_oss_file->chn[channel];
	if (subchn == NULL)
		return -EINVAL;
	runtime = subchn->runtime;

	if (runtime->oss.params &&
	    (err = snd_pcm_oss_change_params(subchn)) < 0)
		return err;

	info.fragsize = runtime->oss.fragment_size;
	info.fragstotal = runtime->oss.fragments;
	memset(&status, 0, sizeof(status));
	if (runtime->oss.prepare) {
		info.bytes = 0;
		info.fragments = 0;
		status.count = 0;
		status.free = runtime->frags * runtime->buf.block.frag_size;
	} else {
		err = snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_STATUS, (long)&status);
		if (err < 0)
			return err;
		info.bytes = snd_pcm_oss_ratio(runtime, status.count);
	}
	if (channel == SND_PCM_CHANNEL_PLAYBACK) {
		info.bytes = info.fragsize*info.fragstotal - info.bytes;
		info.fragments = status.free / runtime->buf.block.frag_size;
	} else {
		info.fragments = status.count / runtime->buf.block.frag_size;
	}
	/* very experimental stuff to get Quake2 working */
	runtime->oss.fragment = (info.fragments - 1) << 16;
	for (tmp = info.fragsize; tmp > 1; tmp >>= 1)
		runtime->oss.fragment++;
	runtime->oss.subdivision = 1;	/* disable SUBDIVIDE */
	// printk("space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", info.bytes, info.fragments, info.fragstotal, info.fragsize);
	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 channel, struct snd_pcm_buffer_description * _info)
{
	// it won't be probably implemented
	// snd_printd("TODO: snd_pcm_oss_get_mapbuf\n");
	return -EINVAL;
}

static struct snd_stru_pcm_oss_setup *snd_pcm_oss_look_for_setup(snd_pcm_t *pcm, int channel, const char *task_name)
{
	const char *ptr, *ptrl;
	snd_pcm_oss_setup_t *setup;

	down(&pcm->chn[channel].oss.setup_mutex);
	for (setup = pcm->chn[channel].oss.setup_list; setup; setup = setup->next) {
		if (!strcmp(setup->task_name, task_name)) {
			up(&pcm->chn[channel].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->chn[channel].oss.setup_list; setup; setup = setup->next) {
		if (!strcmp(setup->task_name, ptrl)) {
			up(&pcm->chn[channel].oss.setup_mutex);
			return setup;
		}
	}
      __not_found:
	up(&pcm->chn[channel].oss.setup_mutex);
	return NULL;
}

static void snd_pcm_oss_init_subchn(snd_pcm_subchn_t *subchn,
				    snd_pcm_oss_setup_t *setup,
				    int minor)
{
	snd_pcm_runtime_t *runtime;

	subchn->oss.oss = 1;
	subchn->oss.setup = setup;
	runtime = subchn->runtime;
	runtime->oss.params = 1;
	runtime->oss.trigger = 1;
	runtime->oss.rate = 8000;
	switch (minor) {
	case SND_MINOR_OSS_PCM_8:
		runtime->oss.format = SND_PCM_AFMT_U8;
		break;
	case SND_MINOR_OSS_PCM_16:
		runtime->oss.format = SND_PCM_FMT_S16_LE;
		break;
	default:
		runtime->oss.format = SND_PCM_AFMT_MU_LAW;
	}
	runtime->oss.voices = 1;
	runtime->oss.fragment = 0;
	runtime->oss.subdivision = 0;
	snd_pcm_clear_values(subchn);
}

static void snd_pcm_oss_release_subchn(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime;
	runtime = subchn->runtime;
	if (runtime->oss.buffer)
		snd_vfree(runtime->oss.buffer);
	snd_pcm_oss_plugin_clear(runtime);
	subchn->oss.file = NULL;
	subchn->oss.oss = 0;
}

static int snd_pcm_oss_release_file(snd_pcm_oss_file_t *pcm_oss_file)
{
	snd_pcm_subchn_t *subchn;
	snd_pcm_channel_t *chn;
	int cidx;

	snd_debug_check(pcm_oss_file == NULL, -ENXIO);
	for (cidx = 0; cidx < 2; ++cidx) {
		subchn = pcm_oss_file->chn[cidx];
		if (subchn == NULL)
			continue;
		chn = subchn->pchn;
		if (*subchn->runtime->status == SND_PCM_STATUS_RUNNING)
			snd_pcm_channel_go(subchn, SND_PCM_TRIGGER_STOP);
		chn->close(chn->private_data, subchn);
		subchn->ffile = NULL;
#if 0
		/* AB: Delayed as now it's not used */
		snd_pcm_oss_remove_file(chn, pcm_oss_file);
#endif
		snd_pcm_oss_release_subchn(subchn);
		snd_pcm_release_subchn(subchn);
	}
	snd_magic_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,
				 unsigned short 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_subchn_t *psubchn = NULL, *csubchn = NULL;
	snd_pcm_channel_t *chn;

	snd_debug_check(rpcm_oss_file == NULL, -EINVAL);
	*rpcm_oss_file = NULL;

	pcm_oss_file = snd_magic_kcalloc(snd_pcm_oss_file_t, 0, GFP_KERNEL);
	if (pcm_oss_file == NULL) {
		return -ENOMEM;
	}

	if ((file->f_mode & FMODE_WRITE) && !(psetup && psetup->disable)) {
		if ((err = snd_pcm_open_subchn(pcm, SND_PCM_CHANNEL_PLAYBACK,
					       &psubchn)) < 0) {
			snd_pcm_oss_release_file(pcm_oss_file);
			return err;
		}
		pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK] = psubchn;
	}
	if ((file->f_mode & FMODE_READ) && !(csetup && csetup->disable)) {
		if ((err = snd_pcm_open_subchn(pcm, SND_PCM_CHANNEL_CAPTURE, 
					       &csubchn)) < 0) {
			snd_pcm_oss_release_file(pcm_oss_file);
			return err;
		}
		pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE] = csubchn;
	}
	
	if (psubchn == NULL && csubchn == NULL) {
		snd_pcm_oss_release_file(pcm_oss_file);
		return -EINVAL;
	}
	if (psubchn != NULL) {
		psubchn->oss.file = pcm_oss_file;
		chn = psubchn->pchn;
		if ((err = chn->open(chn->private_data, psubchn)) < 0) {
			snd_pcm_oss_release_file(pcm_oss_file);
			return err;
		}
		psubchn->ffile = file;
		snd_pcm_oss_init_subchn(psubchn, psetup, minor);
#if 0
		/* AB: Delayed as now it's not used */
		snd_pcm_oss_add_file(chn, pcm_oss_file);
#endif
	}
	if (csubchn != NULL) {
		csubchn->oss.file = pcm_oss_file;
		chn = csubchn->pchn;
		if ((err = chn->open(chn->private_data, csubchn)) < 0) {
			snd_pcm_oss_release_file(pcm_oss_file);
			return err;
		}
		csubchn->ffile = file;
		snd_pcm_oss_init_subchn(csubchn, csetup, minor);
#if 0
		/* AB: Delayed as now it's not used */
		snd_pcm_oss_add_file(chn, pcm_oss_file);
#endif
	}

	file->private_data = pcm_oss_file;
	*rpcm_oss_file = pcm_oss_file;
	return 0;
}


static int snd_pcm_oss_open(unsigned short minor, int cardnum,
                            int device, struct file *file)
{
	int err;
	char task_name[32];
	snd_pcm_t *pcm;
	snd_pcm_subchn_t *psubchn = NULL, *csubchn = NULL;
	snd_pcm_runtime_t *pruntime = NULL, *cruntime = NULL;
	snd_pcm_oss_file_t *pcm_oss_file;
	snd_pcm_oss_setup_t *psetup = NULL, *csetup = NULL;
	int nonblock;

	pcm = snd_pcm_devices[(cardnum * SND_PCM_DEVICES) + device];
	if (pcm == NULL)
		return -ENODEV;
	if (snd_task_name(current, task_name, sizeof(task_name)) < 0)
		return -EFAULT;
	if (file->f_mode & FMODE_WRITE)
		psetup = snd_pcm_oss_look_for_setup(pcm, SND_PCM_CHANNEL_PLAYBACK, task_name);
	if (file->f_mode & FMODE_READ)
		csetup = snd_pcm_oss_look_for_setup(pcm, SND_PCM_CHANNEL_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;
	}

	while (1) {
		down(&pcm->open_mutex);
		err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file,
					    minor, psetup, csetup);
		if (err >= 0)
			break;
		up(&pcm->open_mutex);
		if (err == -EAGAIN) {
			if (nonblock)
				return -EBUSY;
		} else {
			return err;
		}
		interruptible_sleep_on(&pcm->open_wait);
		if (signal_pending(current))
			return -ERESTARTSYS;
	}

	MOD_INC_USE_COUNT;
	pcm->card->use_inc(pcm->card);
	up(&pcm->open_mutex);

        psubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
        csubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];

        if (psubchn != NULL && csubchn != NULL) {
		pruntime = psubchn->runtime;
		cruntime = csubchn->runtime;
                if (memcmp(&pruntime->sync, &cruntime->sync, sizeof(pruntime->sync)))
                        goto __normal;
                if (pruntime->sync.id[0] == 0 && pruntime->sync.id[1] == 0 &&
                    pruntime->sync.id[2] == 0 && pruntime->sync.id[3] == 0)
                        goto __normal;
                snd_pcm_oss_fill_sync(&pruntime->sync_group);
                snd_pcm_oss_fill_sync(&cruntime->sync_group);
                pruntime->oss.sync_trigger =
	        cruntime->oss.sync_trigger = 1;
        }
 __normal:
	return 0;
}

static int snd_pcm_oss_release(unsigned short minor, int cardnum,
                               int device, struct file *file)
{
	snd_pcm_t *pcm;
	snd_pcm_subchn_t *subchn;
	snd_pcm_oss_file_t *pcm_oss_file;

	pcm_oss_file = snd_magic_cast(snd_pcm_oss_file_t, file->private_data, -ENXIO);
	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	if (subchn == NULL)
		subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];
	snd_debug_check(subchn == NULL, -ENXIO);
	pcm = subchn->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);
	pcm->card->use_dec(pcm->card);
	MOD_DEC_USE_COUNT;
	return 0;
}

static int snd_pcm_oss_ioctl(struct file *file,
                             unsigned int cmd, unsigned long arg)
{
	snd_pcm_oss_file_t *pcm_oss_file;
	int res;

	pcm_oss_file = snd_magic_cast(snd_pcm_oss_file_t, file->private_data, -ENXIO);
	if (cmd == SND_OSS_GETVERSION)
		return put_user(SND_OSS_VERSION, (int *)arg) ? -EFAULT : 0;
	if (((cmd >> 8) & 0xff) == 'M')	{	/* mixer ioctl - for OSS (grrr) compatibility */
		snd_pcm_subchn_t *subchn;
		int idx;
		for (idx = 0; idx < 2; ++idx) {
			subchn = pcm_oss_file->chn[idx];
			if (subchn != NULL)
				break;
		}
		snd_debug_check(subchn == NULL, -ENXIO);
		return snd_mixer_oss_ioctl_card(subchn->pcm->card, cmd, arg);
	}
	if (((cmd >> 8) & 0xff) != 'P')
		return -EINVAL;
	switch (cmd) {
	case SND_PCM_IOCTL_OSS_RESET:
		return snd_pcm_oss_reset(pcm_oss_file);
	case SND_PCM_IOCTL_OSS_SYNC:
		return snd_pcm_oss_sync(pcm_oss_file);
	case SND_PCM_IOCTL_OSS_RATE:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		if ((res = snd_pcm_oss_set_rate(pcm_oss_file, res))<0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETRATE:
		res = snd_pcm_oss_get_rate(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_STEREO:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		res = res > 0 ? 2 : 1;
		if ((res = snd_pcm_oss_set_voices(pcm_oss_file, res)) < 0)
			return res;
		return put_user(--res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETBLKSIZE:
		res = snd_pcm_oss_get_block_size(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_FORMAT:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		res = snd_pcm_oss_set_format(pcm_oss_file, res);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETFORMAT:
		res = snd_pcm_oss_get_format(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_CHANNELS:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		res = snd_pcm_oss_set_voices(pcm_oss_file, res);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETCHANNELS:
		res = snd_pcm_oss_get_voices(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_FILTER:
	case SND_PCM_IOCTL_OSS_GETFILTER:
		return -EIO;
	case SND_PCM_IOCTL_OSS_POST:	/* to do */
		return 0;
	case SND_PCM_IOCTL_OSS_SUBDIVIDE:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		res = snd_pcm_oss_set_subdivide(pcm_oss_file, res);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_SETFRAGMENT:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		res = snd_pcm_oss_set_fragment(pcm_oss_file, res);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETFORMATS:
		res = snd_pcm_oss_get_formats(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETPBKSPACE:
	case SND_PCM_IOCTL_OSS_GETRECSPACE:
		return snd_pcm_oss_get_space(pcm_oss_file,
			cmd == SND_PCM_IOCTL_OSS_GETRECSPACE ?
				SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK,
			(struct snd_pcm_buffer_info *) arg);
	case SND_PCM_IOCTL_OSS_NONBLOCK:
		return snd_pcm_oss_nonblock(file);
	case SND_PCM_IOCTL_OSS_GETCAPS:
		res = snd_pcm_oss_get_caps(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETTRIGGER:
		res = snd_pcm_oss_get_trigger(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_SETTRIGGER:
		if (get_user(res, (int *)arg))
			return -EFAULT;
		res = snd_pcm_oss_set_trigger(pcm_oss_file, res);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_GETRECPTR:
	case SND_PCM_IOCTL_OSS_GETPBKPTR:
		return snd_pcm_oss_get_ptr(pcm_oss_file,
			cmd == SND_PCM_IOCTL_OSS_GETRECPTR ?
				SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK,
			(struct snd_pcm_count_info *) arg);
	case SND_PCM_IOCTL_OSS_MAPRECBUFFER:
	case SND_PCM_IOCTL_OSS_MAPPBKBUFFER:
		return snd_pcm_oss_get_mapbuf(pcm_oss_file,
			cmd == SND_PCM_IOCTL_OSS_MAPRECBUFFER ?
				SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK,
			(struct snd_pcm_buffer_description *) arg);
	case SND_PCM_IOCTL_OSS_SYNCRO:
		/* stop DMA now.. */
		return 0;
	case SND_PCM_IOCTL_OSS_DUPLEX:
		if (snd_pcm_oss_get_caps(pcm_oss_file) & SND_PCM_CAP_DUPLEX)
			return 0;
		return -EIO;
	case SND_PCM_IOCTL_OSS_GETODELAY:
		res = snd_pcm_oss_get_odelay(pcm_oss_file);
		if (res < 0)
			return res;
		return put_user(res, (int *)arg) ? -EFAULT : 0;
	case SND_PCM_IOCTL_OSS_PROFILE:
		return 0;	/* silently ignore */
	default:
		snd_printd("pcm_oss: unknown command = 0x%x\n", cmd);
	}
	return -EINVAL;
}

static long snd_pcm_oss_read(struct file *file, char *buf, long count)
{
	snd_pcm_oss_file_t *pcm_oss_file;
	snd_pcm_subchn_t *subchn;

	pcm_oss_file = snd_magic_cast(snd_pcm_oss_file_t, file->private_data, -ENXIO);
	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];
	if (subchn == NULL)
		return -ENXIO;
	return snd_pcm_oss_read1(subchn, buf, count);
}

static long snd_pcm_oss_write(struct file *file, const char *buf, long count)
{
	snd_pcm_oss_file_t *pcm_oss_file;
	snd_pcm_subchn_t *subchn;
	long result;

	pcm_oss_file = snd_magic_cast(snd_pcm_oss_file_t, file->private_data, -ENXIO);
	subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	if (subchn == NULL)
		return -ENXIO;
	up(&file->f_dentry->d_inode->i_sem);
	result = snd_pcm_oss_write1(subchn, buf, count);
	down(&file->f_dentry->d_inode->i_sem);
	return result;
}

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_subchn_t *psubchn = NULL, *csubchn = NULL;
	
	pcm_oss_file = snd_magic_cast(snd_pcm_oss_file_t, file->private_data, 0);

	psubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	csubchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];

	if (psubchn != NULL) {
		if (psubchn->runtime->oss.trigger)
			snd_pcm_oss_make_ready(psubchn);
		else
			pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK] = NULL;
	}
	if (csubchn != NULL) {
		if (*csubchn->runtime->status == SND_PCM_STATUS_OVERRUN)
			csubchn->runtime->oss.prepare = 1;
		snd_pcm_oss_make_ready(csubchn);
		if (*csubchn->runtime->status == SND_PCM_STATUS_PREPARED)
			snd_pcm_kernel_capture_ioctl(csubchn, SND_PCM_IOCTL_CHANNEL_GO, 0);
	}

	mask = 0;
	if (psubchn != NULL) {
		poll_wait(file, &psubchn->runtime->sleep, wait);
		if (snd_pcm_playback_ok(psubchn))
			mask |= POLLOUT | POLLWRNORM;
	}
	if (csubchn != NULL) {
		poll_wait(file, &csubchn->runtime->sleep, wait);
		if (snd_pcm_capture_ok(csubchn))
			mask |= POLLIN | POLLRDNORM;
	}

	pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK] = psubchn;
	return mask;
}

static void snd_pcm_oss_vma_notify_data(void *client, void *data)
{
	snd_pcm_subchn_t *subchn = snd_magic_cast(snd_pcm_subchn_t, client, );
	snd_pcm_runtime_t *runtime;

        if (subchn == NULL)
		return;
	runtime = subchn->runtime;
	if (subchn->channel == SND_PCM_CHANNEL_PLAYBACK)
		snd_pcm_kernel_playback_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_DRAIN, 0);
	else
		snd_pcm_kernel_capture_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_FLUSH, 0);
	if (runtime) {
		runtime->flags &= ~SND_PCM_FLG_OSS_MMAP;
		runtime->mmap_data = NULL;
		runtime->mmap_data_vma = NULL;
	}
	MOD_DEC_USE_COUNT;
}

static int snd_pcm_oss_mmap(struct inode *inode, struct file *file,
                            struct vm_area_struct *area)
{
	snd_pcm_oss_file_t *pcm_oss_file;
	unsigned long size;
	snd_dma_area_t *pdma;
	snd_pcm_subchn_t *subchn = NULL;
	snd_pcm_runtime_t *runtime;
	snd_vma_t *vma;
	int err;

	pcm_oss_file = snd_magic_cast(snd_pcm_oss_file_t, file->private_data, -ENXIO);
	if ((area->vm_flags & (VM_READ | VM_WRITE)) == (VM_READ | VM_WRITE))
		return -EINVAL;
	if (area->vm_flags & VM_READ) {
		subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_CAPTURE];
	} else if (area->vm_flags & VM_WRITE) {
		subchn = pcm_oss_file->chn[SND_PCM_CHANNEL_PLAYBACK];
	} else {
		return -EINVAL;
	}
	if (subchn == NULL)
		return -ENXIO;
	runtime = subchn->runtime;
	if (!(runtime->hw->chninfo & SND_PCM_CHNINFO_MMAP_VALID))
		return -EIO;
	if ((err = snd_pcm_oss_make_ready(subchn)) < 0)
		return err;
	if (*runtime->status != SND_PCM_STATUS_READY &&
	    *runtime->status != SND_PCM_STATUS_PREPARED)
		return -EBADFD;
	if (runtime->oss.plugin_first != NULL)
		return -EIO;
	pdma = runtime->dma_area;
	if (pdma == NULL)
		return -ENOENT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 3, 25)
	if (area->vm_pgoff != 0)
#else
	if (area->vm_offset != 0)
#endif
		return -EINVAL;
	size = area->vm_end - area->vm_start;
	if (size != runtime->oss.fragments * runtime->oss.fragment_size)
		snd_printk("snd-pcm-oss: wrong mmap() size 0x%x, should be 0x%x\n", (unsigned int) size, runtime->oss.fragments * runtime->oss.fragment_size);
	// printk("remap: to=0x%x from=0x%x size=0x%x\n", (int) area->vm_start, (int) virt_to_bus(pdma->buf), (int) size);
	vma = snd_kcalloc(sizeof(snd_vma_t), GFP_KERNEL);
	if (vma == NULL)
		return -ENOMEM;
	if (remap_page_range(area->vm_start,
			     virt_to_phys(pdma->buf),
			     size,
			     area->vm_page_prot)) {
		snd_kfree(vma);
		return -EAGAIN;
	}
	if (area->vm_ops) {
		snd_kfree(vma);
		return -EINVAL;	/* Hmm... shouldn't happen */
	}
	vma->area = area;
	vma->notify_data = pdma->buf;
	vma->notify_size = size;
	vma->notify = snd_pcm_oss_vma_notify_data;
	vma->notify_client = subchn;
	area->vm_file = file;
	snd_vma_add(vma);
	runtime->mmap_data = pdma->buf;
	runtime->mmap_data_vma = vma;
	runtime->flags |= SND_PCM_FLG_OSS_MMAP;
	runtime->oss.mmap_fragments = runtime->oss.fragments;
	runtime->oss.mmap_fragment_size = runtime->oss.fragment_size;
	MOD_INC_USE_COUNT;
	return 0;
}

/*
 *  /proc interface
 */

static void snd_pcm_oss_proc_read1(snd_info_buffer_t * buffer,
                                   struct snd_stru_pcm_oss_setup *setup,
                                   const char *direction)
{
	while (setup) {
		snd_iprintf(buffer, "%s %s %u %u%s%s%s\n",
			    direction,
			    setup->task_name,
			    setup->fragments,
			    setup->fragment_size,
			    setup->disable ? " disable" : "",
			    setup->block ? " block" : "",
			    setup->nonblock ? " non-block" : "");
		setup = setup->next;
	}
}

static void snd_pcm_oss_proc_read(snd_info_buffer_t * buffer,
				  void *private_data)
{
	snd_pcm_t *pcm = snd_magic_cast(snd_pcm_t, private_data, );
	snd_pcm_channel_t * pchn;

	pchn = &pcm->chn[SND_PCM_CHANNEL_PLAYBACK];
	down(&pchn->oss.setup_mutex);
	snd_pcm_oss_proc_read1(buffer, pchn->oss.setup_list, "Playback");
	up(&pchn->oss.setup_mutex);
	pchn = &pcm->chn[SND_PCM_CHANNEL_CAPTURE];
	down(&pchn->oss.setup_mutex);
	snd_pcm_oss_proc_read1(buffer, pchn->oss.setup_list, "Capture");
	up(&pchn->oss.setup_mutex);
}

static void snd_pcm_oss_proc_free_setup_list(snd_pcm_channel_t * pchn)
{
	int idx;
	snd_pcm_subchn_t *subchn;
	struct snd_stru_pcm_oss_setup *setup, *setupn;

	for (idx = 0, subchn = pchn->subchn;
	     idx < pchn->subchn_count; idx++, subchn = subchn->next)
		subchn->oss.setup = NULL;
	for (setup = pchn->oss.setup_list, pchn->oss.setup_list = NULL;
	     setup; setup = setupn) {
		setupn = setup->next;
		snd_kfree(setup->task_name);
		snd_kfree(setup);
	}
	pchn->oss.setup_list = NULL;
}

static void snd_pcm_oss_proc_write(snd_info_buffer_t * buffer,
                                   void *private_data)
{
	snd_pcm_t *pcm = snd_magic_cast(snd_pcm_t, private_data, );
	snd_pcm_channel_t *pchn;
	char line[512], str[32], task_name[32], *ptr;
	int idx, idx1;
	snd_pcm_oss_setup_t *setup, *setup1, template;

	while (!snd_info_get_line(buffer, line, sizeof(line))) {
		pchn = NULL;
		if (!strncmp(line, "Playback ", 9)) {
			pchn = &pcm->chn[SND_PCM_CHANNEL_PLAYBACK];
			idx = 9;
		} else if (!strncmp(line, "Capture ", 8)) {
			pchn = &pcm->chn[SND_PCM_CHANNEL_CAPTURE];
			idx = 8;
		} else {
			buffer->error = -EINVAL;
			continue;
		}
		down(&pchn->oss.setup_mutex);
		memset(&template, 0, sizeof(template));
		ptr = snd_info_get_str(task_name, line + idx, sizeof(task_name));
		if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) {
			snd_pcm_oss_proc_free_setup_list(pchn);
			up(&pchn->oss.setup_mutex);
			continue;
		}
		for (setup = pchn->oss.setup_list; setup; setup = setup->next) {
			if (!strcmp(setup->task_name, task_name)) {
				memcpy(&template, setup, sizeof(template));
				break;
			}
		}
		ptr = snd_info_get_str(str, ptr, sizeof(str));
		template.fragments = simple_strtoul(str, NULL, 10);
		ptr = snd_info_get_str(str, ptr, sizeof(str));
		template.fragment_size = simple_strtoul(str, NULL, 10);
		for (idx1 = 31; idx1 >= 0; idx1--)
			if (template.fragment_size & (1 << idx1))
				break;
		for (idx1--; idx1 >= 0; idx1--)
			template.fragment_size &= ~(1 << idx1);
		do {
			ptr = snd_info_get_str(str, ptr, sizeof(str));
			if (!strcmp(str, "disable")) {
				template.disable = 1;
			} else if (!strcmp(str, "block")) {
				template.block = 1;
			} else if (!strcmp(str, "non-block")) {
				template.nonblock = 1;
			}
		} while (*str);
		if (setup == NULL) {
			setup = (struct snd_stru_pcm_oss_setup *) snd_kmalloc(sizeof(struct snd_stru_pcm_oss_setup), GFP_KERNEL);
			if (setup) {
				if (pchn->oss.setup_list == NULL) {
					pchn->oss.setup_list = setup;
				} else {
					for (setup1 = pchn->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)
			memcpy(setup, &template, sizeof(template));
		up(&pchn->oss.setup_mutex);
	}
}

static void snd_pcm_oss_proc_init(snd_pcm_t * pcm)
{
	snd_info_entry_t *entry;
	char name[16];

	sprintf(name, "pcmD%io", pcm->device);
	if ((entry = snd_info_create_entry(pcm->card, name)) != NULL) {
		entry->private_data = pcm;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 8192;
		entry->t.text.read = snd_pcm_oss_proc_read;
		entry->t.text.write_size = 8192;
		entry->t.text.write = snd_pcm_oss_proc_write;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	pcm->oss.proc_ctrl_entry = entry;
}

static void snd_pcm_oss_proc_done(snd_pcm_t * pcm)
{
	if (pcm->oss.proc_ctrl_entry) {
		snd_info_unregister(pcm->oss.proc_ctrl_entry);
		pcm->oss.proc_ctrl_entry = NULL;
		snd_pcm_oss_proc_free_setup_list(&pcm->chn[0]);
		snd_pcm_oss_proc_free_setup_list(&pcm->chn[1]);
	}
}

/*
 *  ENTRY functions
 */

static snd_minor_t snd_pcm_oss_reg =
{
	comment:	"digital audio",
	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,
	ioctl:		snd_pcm_oss_ioctl,
	mmap:		snd_pcm_oss_mmap,
};

static int snd_pcm_oss_register_minor(unsigned short native_minor,
				      snd_pcm_t * pcm)
{
	char name[128];

	pcm->oss.reg = 0;
	if (pcm->device < 2) {
		sprintf(name, "dsp%i%i", pcm->card->number, pcm->device);
		if (snd_register_oss_device(SND_OSS_DEVICE_TYPE_PCM,
				pcm->card, pcm->device, &snd_pcm_oss_reg,
				name) < 0) {
			snd_printk("unable to register OSS PCM device %i:%i\n", pcm->card->number, pcm->device);
		} else {
			if (pcm->device == 0) {
				sprintf(name, "%s%s", pcm->name, pcm->info_flags & SND_PCM_INFO_DUPLEX ? " (DUPLEX)" : "");
				snd_oss_info_register(SND_OSS_INFO_DEV_AUDIO,
						      pcm->card->number,
						      name);
			}
			pcm->oss.reg = 1;
			snd_pcm_oss_proc_init(pcm);
		}
	}
	return 0;
}

static int snd_pcm_oss_unregister_minor(unsigned short native_minor,
				        snd_pcm_t * pcm)
{
	if (pcm->oss.reg) {
		snd_unregister_oss_device(SND_OSS_DEVICE_TYPE_PCM,
					  pcm->card, pcm->device);
		if (pcm->device == 0)
			snd_oss_info_unregister(SND_OSS_INFO_DEV_AUDIO, pcm->card->number);
		pcm->oss.reg = 0;
		snd_pcm_oss_proc_done(pcm);
	}
	return 0;
}

static struct snd_stru_pcm_notify snd_pcm_oss_notify =
{
	snd_pcm_oss_register_minor,
	snd_pcm_oss_unregister_minor,
	NULL
};

static int __init alsa_pcm_oss_init(void)
{
	int err;

	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)
