/*
 * Purpose: Client/server audio device pair for oss_userdev
 *
 * This file implements the actual client/server device pair. There will be
 * separate oss_userdev instance for each process that has opened the
 * client side.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "oss_userdev_cfg.h"
#include <oss_userdev_exports.h>
#include "userdev.h"
static void userdev_free_device_pair (userdev_devc_t *devc);

extern int userdev_visible_clientnodes;

static void
transfer_audio (userdev_portc_t * server_portc, dmap_t * dmap_from,
		dmap_t * dmap_to)
{
  int l = dmap_from->fragment_size;
  unsigned char *fromp, *top;

  if (dmap_to->fragment_size != l)
    {
      cmn_err (CE_WARN, "Fragment size mismatch (%d != %d)\n",
	       dmap_to->fragment_size, l);

      /* Perform emergency stop */
      server_portc->input_triggered = 0;
      server_portc->output_triggered = 0;
      server_portc->peer->input_triggered = 0;
      server_portc->peer->output_triggered = 0;
      return;
    }

  fromp =
    dmap_from->dmabuf + (dmap_from->byte_counter % dmap_from->bytes_in_use);
  top = dmap_to->dmabuf + (dmap_to->byte_counter % dmap_to->bytes_in_use);

  memcpy (top, fromp, l);

}

static void
handle_input (userdev_portc_t * server_portc)
{
  userdev_portc_t *client_portc = server_portc->peer;

  if (client_portc->output_triggered)
    {
      transfer_audio (server_portc,
		      audio_engines[client_portc->audio_dev]->dmap_out,
		      audio_engines[server_portc->audio_dev]->dmap_in);
      oss_audio_outputintr (client_portc->audio_dev, 0);
    }

  oss_audio_inputintr (server_portc->audio_dev, 0);
}

static void
handle_output (userdev_portc_t * server_portc)
{
  userdev_portc_t *client_portc = server_portc->peer;

  if (client_portc->input_triggered)
    {
      transfer_audio (server_portc,
		      audio_engines[server_portc->audio_dev]->dmap_out,
		      audio_engines[client_portc->audio_dev]->dmap_in);
      oss_audio_inputintr (client_portc->audio_dev, 0);
    }

  oss_audio_outputintr (server_portc->audio_dev, 0);
}

static void
userdev_cb (void *pc)
{
/*
 * This timer callback routine will get called 100 times/second. It handles
 * movement of audio data between the client and server sides.
 */
  userdev_portc_t *server_portc = pc;
  userdev_devc_t *devc = server_portc->devc;
  int tmout = devc->poll_ticks;

  if (tmout < 1)
    tmout = 1;

  devc->timeout_id = 0;	/* No longer valid */

  if (server_portc->input_triggered)
    handle_input (server_portc);

  if (server_portc->output_triggered)
    handle_output (server_portc);

  /* Retrigger timer callback */
  if (server_portc->input_triggered || server_portc->output_triggered)
    devc->timeout_id = timeout (userdev_cb, server_portc, tmout);
}

static int
userdev_check_input (int dev)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  if (!portc->peer->output_triggered)
    {
      return OSS_ECONNRESET;
    }
  return 0;
}

static int
userdev_check_output (int dev)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;

  if (!portc->peer->input_triggered)
    {
      return OSS_ECONNRESET;
    }

  if (portc->peer->open_mode == 0)
    return OSS_EIO;
  return 0;
}

static void
setup_sample_format (userdev_portc_t * portc)
{
  adev_t *adev;
  userdev_devc_t *devc = portc->devc;
  int fragsize, frame_size;

  frame_size = devc->channels * devc->fmt_bytes;
  if (frame_size == 0)
    frame_size = 4;

  fragsize = (devc->rate * frame_size * devc->poll_ticks) / OSS_HZ;	/* Number of bytes/fragment */
  devc->rate = fragsize * 100 / frame_size;

/* Setup the server side */
  adev = audio_engines[portc->audio_dev];
  adev->min_block = adev->max_block = fragsize;

/* Setup the client side */
  adev = audio_engines[portc->peer->audio_dev];
  adev->min_block = adev->max_block = fragsize;

  adev->max_rate = adev->min_rate = devc->rate;
  adev->iformat_mask = devc->fmt;
  adev->oformat_mask = devc->fmt;
  adev->xformat_mask = devc->fmt;
  adev->min_channels = adev->max_channels = devc->channels;
}

static int
userdev_server_set_rate (int dev, int arg)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  adev_t *client_adev = audio_engines[portc->peer->audio_dev];

  if (arg == 0)
    return devc->rate;

  if (portc->peer->input_triggered || portc->peer->output_triggered)
    return devc->rate;

  if (arg < 5000)
    arg = 5000;
  if (arg > MAX_RATE)
    arg = MAX_RATE;

  /* Force the sample rate to be multiple of 100 */
  arg = (arg / 100) * 100;

  devc->rate = arg;

  client_adev->min_rate = arg;
  client_adev->max_rate = arg;

  setup_sample_format (portc);

  return devc->rate = arg;
}

/*ARGSUSED*/
static int
userdev_client_set_rate (int dev, int arg)
{
  userdev_devc_t *devc = audio_engines[dev]->devc;

  return devc->rate;
}

static short
userdev_server_set_channels (int dev, short arg)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  adev_t *client_adev = audio_engines[portc->peer->audio_dev];

  if (arg == 0)
    return devc->channels;

  if (portc->peer->input_triggered || portc->peer->output_triggered)
    return devc->channels;

  if (arg < 1)
    arg = 1;
  if (arg > MAX_CHANNELS)
    arg = MAX_CHANNELS;

  devc->channels = arg;
  client_adev->min_channels=client_adev->max_channels=arg;

  setup_sample_format (portc);

  return devc->channels;
}

/*ARGSUSED*/
static short
userdev_client_set_channels (int dev, short arg)
{
  userdev_devc_t *devc = audio_engines[dev]->devc;

  return devc->channels;	/* Server side channels */
}

static unsigned int
userdev_server_set_format (int dev, unsigned int arg)
{
  userdev_devc_t *devc = audio_engines[dev]->devc;
  userdev_portc_t *portc = audio_engines[dev]->portc;
  adev_t *client_adev = audio_engines[portc->peer->audio_dev];

  if (arg == 0)
    return devc->fmt;

  if (portc->peer->input_triggered || portc->peer->output_triggered)
    return devc->fmt;

  switch (arg)
    {
    case AFMT_S16_NE:
      devc->fmt_bytes = 2;
      break;

    case AFMT_S32_NE:
      devc->fmt_bytes = 4;
      break;

    default:			/* Unsupported format */
      arg = AFMT_S16_NE;
      devc->fmt_bytes = 2;

    }

  devc->fmt = arg;

  client_adev->oformat_mask = arg;
  client_adev->iformat_mask = arg;

  setup_sample_format (portc);

  return devc->fmt;
}

/*ARGSUSED*/
static unsigned int
userdev_client_set_format (int dev, unsigned int arg)
{
  userdev_devc_t *devc = audio_engines[dev]->devc;

  return devc->fmt;	/* Server side sample format */
}

static void userdev_trigger (int dev, int state);

static void
userdev_reset (int dev)
{
  userdev_trigger (dev, 0);
}

/*ARGSUSED*/
static int
userdev_server_open (int dev, int mode, int open_flags)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  if (portc == NULL || portc->peer == NULL)
    return OSS_ENXIO;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  portc->open_mode = mode;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  devc->open_count++;

  return 0;
}

/*ARGSUSED*/
static int
userdev_client_open (int dev, int mode, int open_flags)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  if (portc == NULL || portc->peer == NULL)
    return OSS_ENXIO;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  portc->open_mode = mode;
  devc->open_count++;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
wipe_audio_buffers(userdev_devc_t *devc)
{
/*
 * Write silence to the audio buffers when only one of the sides
 * is open. This prevents the device from looping the last fragments
 * written to the device.
 */
	dmap_t *dmap;

	dmap = audio_engines[devc->client_portc.audio_dev]->dmap_out;
	if (dmap != NULL && dmap->dmabuf != NULL)
	   memset(dmap->dmabuf, 0, dmap->buffsize);

	dmap = audio_engines[devc->client_portc.audio_dev]->dmap_in;
	if (dmap != NULL && dmap->dmabuf != NULL)
	   memset(dmap->dmabuf, 0, dmap->buffsize);

	dmap = audio_engines[devc->server_portc.audio_dev]->dmap_out;
	if (dmap != NULL && dmap->dmabuf != NULL)
	   memset(dmap->dmabuf, 0, dmap->buffsize);

	dmap = audio_engines[devc->server_portc.audio_dev]->dmap_in;
	if (dmap != NULL && dmap->dmabuf != NULL)
	   memset(dmap->dmabuf, 0, dmap->buffsize);
}

/*ARGSUSED*/
static void
userdev_server_close (int dev, int mode)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  int open_count;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->open_mode = 0;

  /* Stop the client side because there is no server */
  portc->peer->input_triggered = 0;
  portc->peer->output_triggered = 0;
  open_count = --devc->open_count;

  if (open_count == 0)
     userdev_free_device_pair (devc);
  else
     wipe_audio_buffers(devc);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static void
userdev_client_close (int dev, int mode)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  int open_count;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->open_mode = 0;

  open_count = --devc->open_count;

  if (open_count == 0)
     userdev_free_device_pair (devc);
  else
     wipe_audio_buffers(devc);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
userdev_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  switch (cmd)
    {
    case SNDCTL_GETLABEL:
      {
	/*
	 * Return an empty string so that this feature can be tested.
	 * Complete functionality is to be implemented later.
	 */
	oss_label_t *s = (oss_label_t *) arg;
	memset (s, 0, sizeof (oss_label_t));
	return 0;
      }
      break;

    case SNDCTL_GETSONG:
      {
	/*
	 * Return an empty string so that this feature can be tested.
	 * Complete functionality is to be implemented later.
	 */
	oss_longname_t *s = (oss_longname_t *) arg;
	memset (s, 0, sizeof (oss_longname_t));
	return 0;
      }
      break;
    }

  return OSS_EINVAL;
}

static void
set_adev_name(int dev, const char *name)
{
  adev_t *adev = audio_engines[dev];

  strcpy(adev->name, name);

#ifdef CONFIG_OSS_VMIX
  if (adev->vmix_mixer != NULL)
     vmix_change_devnames(adev->vmix_mixer, name);
#endif
	
}

static int
create_instance(int dev, userdev_create_t *crea)
{
  userdev_devc_t *devc = audio_engines[dev]->devc;
  char tmp_name[64];

  devc->match_method = crea->match_method;
  devc->match_key = crea->match_key;
  devc->create_flags = crea->flags;

  devc->poll_ticks = (crea->poll_interval * OSS_HZ) / 1000;

  if (devc->poll_ticks < 1) 
     devc->poll_ticks = 1;

  crea->poll_interval = (1000*devc->poll_ticks) / OSS_HZ;

  if (crea->poll_interval<1)
     crea->poll_interval = 1;

  crea->name[sizeof(crea->name)-1]=0; /* Overflow protectgion */

  sprintf(tmp_name, "%s (server)", crea->name);
  tmp_name[49]=0;
  set_adev_name (devc->client_portc.audio_dev, crea->name);
  set_adev_name (devc->server_portc.audio_dev, tmp_name);

  strcpy(crea->devnode, audio_engines[devc->client_portc.audio_dev]->devnode);

  return 0;
}

static int
userdev_set_control (int dev, int ctl, unsigned int cmd, int value)
{
  	userdev_devc_t *devc = mixer_devs[dev]->devc;

	if (ctl < 0 || ctl >= USERDEV_MAX_MIXERS)
	   return OSS_EINVAL;

	if (cmd == SNDCTL_MIX_READ)
	   {
		   return devc->mixer_values[ctl];
	   }

	devc->mixer_values[ctl] = value;
	devc->modify_counter++;

	return 0;
}

/*ARGSUSED*/
static int
userdev_server_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  switch (cmd)
    {
    case USERDEV_CREATE_INSTANCE:
	    {
		userdev_create_t *crea = (userdev_create_t *)arg;

	    	return create_instance(dev, crea);
	    }
	    break;

    case USERDEV_GET_CLIENTCOUNT:
	    {
  		userdev_devc_t *devc = audio_engines[dev]->devc;

		return *arg = (devc->client_portc.open_mode != 0);
	    }
	    break;

   /*
    * Mixer related ioctl calls
    */

    case USERDEV_CREATE_MIXGROUP:
	    {
  		userdev_devc_t *devc = audio_engines[dev]->devc;
		userdev_mixgroup_t *grp=(userdev_mixgroup_t*)arg;
		int group;

		grp->name[sizeof(grp->name)-1]=0; /* Buffer overflow protection */
		if ((group=mixer_ext_create_group(devc->mixer_dev, grp->parent, grp->name))<0)
			return group;
		grp->num = group;
		return 0;
	    }
	    break;

    case USERDEV_CREATE_MIXCTL:
	    {
  		userdev_devc_t *devc = audio_engines[dev]->devc;
		userdev_mixctl_t *c=(userdev_mixctl_t*)arg;
  		oss_mixext *ext;
		int ctl;

		c->name[sizeof(c->name)-1]=0; /* Buffer overflow protection */

		if (c->index < 0 || c->index >= USERDEV_MAX_MIXERS)
		   return OSS_EINVAL;

		if ((ctl = mixer_ext_create_control (devc->mixer_dev,
			       c->parent,
			       c->index,
			       userdev_set_control,
			       c->type,
			       c->name,
			       c->maxvalue,
			       c->flags)) < 0)
	    		return ctl;

		c->num = ctl;
  		ext = mixer_find_ext (devc->mixer_dev, ctl);

		ext->minvalue = c->offset;
		ext->control_no= c->control_no;
		ext->rgbcolor = c->rgbcolor;

		if (c->type == MIXT_ENUM)
		{
			memcpy(ext->enum_present, c->enum_present, sizeof(ext->enum_present));
  			mixer_ext_set_strings (devc->mixer_dev, ctl, c->enum_choises, 0);
		}

		return 0;
	    }
	    break;

    case USERDEV_GET_MIX_CHANGECOUNT:
	    {
  		userdev_devc_t *devc = audio_engines[dev]->devc;

		return *arg = devc->modify_counter;
	    }
	    break;

    case USERDEV_SET_MIXERS:
	    {
  		userdev_devc_t *devc = audio_engines[dev]->devc;

		memcpy(devc->mixer_values, arg, sizeof(devc->mixer_values));
		mixer_devs[devc->mixer_dev]->modify_counter++;
//cmn_err(CE_CONT, "Set %08x %08x\n", devc->mixer_values[0], devc->mixer_values[1]);
		return 0;
	    }
	    break;

    case USERDEV_GET_MIXERS:
	    {
  		userdev_devc_t *devc = audio_engines[dev]->devc;

		memcpy(arg, devc->mixer_values, sizeof(devc->mixer_values));
		return 0;
	    }
	    break;

    }

  return userdev_ioctl(dev, cmd, arg);
}

/*ARGSUSED*/
static void
userdev_output_block (int dev, oss_native_word buf, int count, int fragsize,
			int intrflag)
{
}

/*ARGSUSED*/
static void
userdev_start_input (int dev, oss_native_word buf, int count, int fragsize,
		       int intrflag)
{
}

static void
userdev_trigger (int dev, int state)
{
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;

  if (portc->open_mode & OPEN_READ)	/* Handle input */
    {
      portc->input_triggered = !!(state & OPEN_READ);
    }

  if (portc->open_mode & OPEN_WRITE)	/* Handle output */
    {
      portc->output_triggered = !!(state & OPEN_WRITE);
    }

  if (portc->output_triggered || portc->input_triggered)	/* Something is going on */
    {
      int tmout = devc->poll_ticks;

      if (tmout < 1)
	tmout = 1;

      if (portc->port_type != PT_SERVER)
	portc = portc->peer;	/* Switch to the server side */

      if (portc->output_triggered || portc->input_triggered)	/* Something is going on */
	if (devc->timeout_id == 0)
	{
	  devc->timeout_id = timeout (userdev_cb, portc, tmout);
	}
    }
  else
    {
      if (portc->port_type == PT_SERVER)
	if (devc->timeout_id != 0)
	  {
	    untimeout (devc->timeout_id);
	    devc->timeout_id = 0;
	  }
    }
}

/*ARGSUSED*/
static int
userdev_server_prepare_for_input (int dev, int bsize, int bcount)
{
  oss_native_word flags;

  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->input_triggered = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
userdev_server_prepare_for_output (int dev, int bsize, int bcount)
{
  oss_native_word flags;

  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->output_triggered = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
userdev_client_prepare_for_input (int dev, int bsize, int bcount)
{
  oss_native_word flags;
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->input_triggered = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
userdev_client_prepare_for_output (int dev, int bsize, int bcount)
{
  oss_native_word flags;
  userdev_portc_t *portc = audio_engines[dev]->portc;
  userdev_devc_t *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->output_triggered = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
userdev_alloc_buffer (int dev, dmap_t * dmap, int direction)
{
#define MY_BUFFSIZE (64*1024)
  if (dmap->dmabuf != NULL)
    return 0;
  dmap->dmabuf_phys = 0;	/* Not mmap() capable */
  dmap->dmabuf = KERNEL_MALLOC (MY_BUFFSIZE);
  if (dmap->dmabuf == NULL)
    return OSS_ENOSPC;
  dmap->buffsize = MY_BUFFSIZE;

  return 0;
}

/*ARGSUSED*/
static int
userdev_free_buffer (int dev, dmap_t * dmap, int direction)
{
  if (dmap->dmabuf == NULL)
    return 0;
  KERNEL_FREE (dmap->dmabuf);

  dmap->dmabuf = NULL;
  return 0;
}

#if 0
static int
userdev_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
}
#endif

/*ARGSUSED*/
static int
userdev_ioctl_override (int dev, unsigned int cmd, ioctl_arg arg)
{
/*
 * Purpose of this ioctl override function is to intercept mixer
 * ioctl calls made on the client side and to hide everything
 * outside the userdev instance from the application.
 *
 * Note that this ioctl is related with the client side audio device. However
 * if /dev/mixer points to this (audio) device then all mixer acess will
 * be redirected too. Also the vmix driver will redirect mixer/system ioctl
 * calls to this function.
 */
  int err;
  userdev_devc_t *devc = audio_engines[dev]->devc;
  adev_t *adev = audio_engines[devc->client_portc.audio_dev];

  switch (cmd)
  {
  case SNDCTL_MIX_NRMIX:
	  return *arg=1;
	  break;

  case SNDCTL_MIX_NREXT:
	  *arg = devc->mixer_dev;
	  return OSS_EAGAIN; /* Continue with the default handler */
	  break;

  case SNDCTL_SYSINFO:
	  {
	  /*
	   * Fake SNDCTL_SYSINFO to report just one mixer device which is
	   * the one associated with the client.
	   */
	    oss_sysinfo *info = (oss_sysinfo *) arg;
	    int i;

	    if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0)
	       return err;

	    /*
	     * Hide all non-oss_userdev devices
	     */
	    strcpy (info->product, "OSS (userdev)");
	    info->nummixers = 1;
	    info->numcards = 1;
	    info->numaudios = 1;
	    for (i = 0; i < 8; i++)
	       info->openedaudio[i] = 0;

	    return 0;
	  }
	  break;

  case SNDCTL_MIXERINFO:
	{
		oss_mixerinfo *info = (oss_mixerinfo *) arg;

		info->dev = devc->mixer_dev; /* Redirect to oss_userdev mixer */

	    	if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0)
	           return err;

		strcpy(info->name, adev->name);
		info->card_number = 0;
		return 0;
	}

  case SNDCTL_AUDIOINFO:
  case SNDCTL_AUDIOINFO_EX:
	{
		oss_audioinfo *info = (oss_audioinfo *) arg;

		info->dev = devc->client_portc.audio_dev;

		cmd = SNDCTL_ENGINEINFO;

	    	if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0)
	           return err;

		info->card_number = 0;
		return 0;
	}

    case SNDCTL_CARDINFO:
      {
	oss_card_info *info = (oss_card_info *) arg;

	info->card = adev->card_number; /* Redirect to oss_userdev0 */

    	if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0)
           return err;

	info->card = 0;
	return 0;
      }

  case SNDCTL_MIX_EXTINFO:
      {
	      oss_mixext *ext = (oss_mixext*)arg;

	      ext->dev = devc->mixer_dev;

	      if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0)
	           return err;

	      if (ext->type == MIXT_DEVROOT)
	      {
		oss_mixext_root *root = (oss_mixext_root *) ext->data;
		strncpy(root->name, adev->name, 48);
		root->name[47]=0;
	      }

	      return 0;
      }
      break;

  case SNDCTL_MIX_READ:
  case SNDCTL_MIX_WRITE:
      {
	      oss_mixer_value *ent = (oss_mixer_value*)arg;

	      ent->dev = devc->mixer_dev;

	      return OSS_EAGAIN; 	/* Redirect */
      }
      break;

  default:
	  return OSS_EAGAIN;
  }
}

static audiodrv_t userdev_server_driver = {
  userdev_server_open,
  userdev_server_close,
  userdev_output_block,
  userdev_start_input,
  userdev_server_ioctl,
  userdev_server_prepare_for_input,
  userdev_server_prepare_for_output,
  userdev_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  userdev_trigger,
  userdev_server_set_rate,
  userdev_server_set_format,
  userdev_server_set_channels,
  NULL,
  NULL,
  userdev_check_input,
  userdev_check_output,
  userdev_alloc_buffer,
  userdev_free_buffer,
  NULL,
  NULL,
  NULL				/* userdev_get_buffer_pointer */
};

static audiodrv_t userdev_client_driver = {
  userdev_client_open,
  userdev_client_close,
  userdev_output_block,
  userdev_start_input,
  userdev_ioctl,
  userdev_client_prepare_for_input,
  userdev_client_prepare_for_output,
  userdev_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  userdev_trigger,
  userdev_client_set_rate,
  userdev_client_set_format,
  userdev_client_set_channels,
  NULL,
  NULL,
  userdev_check_input,
  userdev_check_output,
  userdev_alloc_buffer,
  userdev_free_buffer,
  NULL,
  NULL,
  NULL,	// userdev_get_buffer_pointer
  NULL,	// userdev_calibrate_speed
  NULL,	// userdev_sync_control
  NULL,	// userdev_prepare_to_stop
  NULL,	// userdev_get_input_pointer
  NULL,	// userdev_get_output_pointer
  NULL,	// userdev_bind
  NULL,	// userdev_setup_fragments
  NULL,	// userdev_redirect
  userdev_ioctl_override
};

static int
userdev_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{

  if (cmd == SOUND_MIXER_READ_CAPS)
    return *arg = SOUND_CAP_NOLEGACY;

#if 0
  if (cmd == SOUND_MIXER_READ_DEVMASK ||
      cmd == SOUND_MIXER_READ_RECMASK || cmd == SOUND_MIXER_READ_RECSRC)
    return *arg = 0;

  if (cmd == SOUND_MIXER_READ_VOLUME || cmd == SOUND_MIXER_READ_PCM)
    return *arg = 100 | (100 << 8);
  if (cmd == SOUND_MIXER_WRITE_VOLUME || cmd == SOUND_MIXER_WRITE_PCM)
    return *arg = 100 | (100 << 8);
#endif
  return OSS_EINVAL;
}

static mixer_driver_t userdev_mixer_driver = {
  userdev_mixer_ioctl
};

static int
install_server (userdev_devc_t * devc)
{
  userdev_portc_t *portc = &devc->server_portc;
  int adev;

  int opts =
    ADEV_STEREOONLY | ADEV_16BITONLY | ADEV_VIRTUAL |
    ADEV_FIXEDRATE | ADEV_SPECIAL | ADEV_HIDDEN | ADEV_DUPLEX;

  memset (portc, 0, sizeof (*portc));

  portc->devc = devc;
  portc->port_type = PT_SERVER;

  if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    "User space audio device server side",
				    &userdev_server_driver,
				    sizeof (audiodrv_t),
				    opts, SUPPORTED_FORMATS, devc, -1)) < 0)
    {
      return adev;
    }

  audio_engines[adev]->portc = portc;
  audio_engines[adev]->min_rate = 5000;
  audio_engines[adev]->max_rate = MAX_RATE;
  audio_engines[adev]->min_channels = 1;
  audio_engines[adev]->max_channels = MAX_CHANNELS;
  audio_engines[adev]->vmix_mixer=NULL;
  strcpy(audio_engines[adev]->devnode, userdev_server_devnode);

  portc->audio_dev = adev;

  return adev;
}

static int 
null_mixer_init(int de)
{
	return 0;
}

static void
userdev_create_mixer(userdev_devc_t * devc)
{
  if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
				     devc->osdev,
				     devc->osdev,
				     "OSS userdev mixer",
				     &userdev_mixer_driver,
				     sizeof (mixer_driver_t), devc)) < 0)
    {
	devc->mixer_dev = -1;
	return;
    }
  mixer_ext_set_init_fn (devc->mixer_dev, null_mixer_init, USERDEV_MAX_MIXERS*2);
}

static int
install_client (userdev_devc_t * devc)
{
  userdev_portc_t *portc = &devc->client_portc;
  int adev;

  int opts =
    ADEV_STEREOONLY | ADEV_16BITONLY | ADEV_VIRTUAL | ADEV_DUPLEX |
    ADEV_FIXEDRATE | ADEV_SPECIAL | ADEV_LOOP;

  memset (portc, 0, sizeof (*portc));

  userdev_create_mixer(devc);

  portc->devc = devc;
  portc->port_type = PT_CLIENT;

  if (!userdev_visible_clientnodes && !(devc->create_flags & USERDEV_F_VMIX_PRIVATENODE))
  {
     opts |= ADEV_HIDDEN;
  }

  if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    "User space audio device",
				    &userdev_client_driver,
				    sizeof (audiodrv_t),
				    opts, SUPPORTED_FORMATS, devc, -1)) < 0)
    {
      return adev;
    }

  if (!userdev_visible_clientnodes) /* Invisible client device nodes */
     strcpy(audio_engines[adev]->devnode, userdev_client_devnode);

  audio_engines[adev]->portc = portc;
  audio_engines[adev]->mixer_dev = devc->mixer_dev;
  audio_engines[adev]->min_rate = 5000;
  audio_engines[adev]->max_rate = MAX_RATE;
  audio_engines[adev]->min_channels = 1;
  audio_engines[adev]->max_channels = MAX_CHANNELS;

  portc->audio_dev = adev;
#ifdef CONFIG_OSS_VMIX
  vmix_attach_audiodev(devc->osdev, adev, -1, 0);
#endif

  return adev;
}

int
userdev_create_device_pair(void)
{
  int client_engine, server_engine;
  userdev_devc_t *devc;
  oss_native_word flags;

  if ((devc=PMALLOC(userdev_osdev, sizeof (*devc))) == NULL)
     return OSS_ENOMEM;
  memset(devc, 0, sizeof(*devc));

  devc->osdev = userdev_osdev;
  MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);
  devc->active=1;

  devc->rate = 48000;
  devc->fmt = AFMT_S16_NE;
  devc->fmt_bytes = 2;
  devc->channels = 2;
  devc->poll_ticks = 10;

  if ((server_engine=install_server (devc)) < 0)
     return server_engine;

  if ((client_engine=install_client (devc)) < 0)
	return client_engine;

  devc->client_portc.peer = &devc->server_portc;
  devc->server_portc.peer = &devc->client_portc;

  /*
   * Insert the device to the list of available devices
   */
  MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags);
  devc->next_instance = userdev_active_device_list;
  userdev_active_device_list = devc;
  MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags);

  return server_engine;
}

static void
userdev_free_device_pair (userdev_devc_t *devc)
{
  oss_native_word flags;

  set_adev_name(devc->client_portc.audio_dev, "User space audio device");
  set_adev_name(devc->server_portc.audio_dev, "User space audio device server side");

  MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags);

  devc->match_method = 0;
  devc->match_key = 0;

  /*
   * Add to the free device pair list.
   */
  devc->next_instance = userdev_free_device_list;
  userdev_free_device_list = devc;

  /*
   * Remove the device pair from the active device list.
   */

  if (userdev_active_device_list == devc) /* First device in the list */
     {
	     userdev_active_device_list = userdev_active_device_list->next_instance;
     }
  else
     {
	     userdev_devc_t *this = userdev_active_device_list, *prev = NULL;

	     while (this != NULL)
	     {
		     if (this == devc)
			{
				prev->next_instance = this->next_instance; /* Remove */
				break;
			}

		     prev = this;
		     this = this->next_instance;
	     }
     }
  MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags);
}

void
userdev_reinit_instance(userdev_devc_t *devc)
{
	if (devc->mixer_dev < 0)
	   return;

  mixer_ext_rebuild_all (devc->mixer_dev, null_mixer_init, USERDEV_MAX_MIXERS*2);
}

void
userdev_delete_device_pair(userdev_devc_t *devc)
{
  if (!devc->active)
     return;

  devc->active = 0;
  MUTEX_CLEANUP(devc->mutex);
}

int
usrdev_find_free_device_pair(void)
{
  oss_native_word flags;
  userdev_devc_t *devc;

  MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags);

  if (userdev_free_device_list != NULL)
     {
	devc = userdev_free_device_list;
	userdev_free_device_list = userdev_free_device_list->next_instance;

  	devc->next_instance = userdev_active_device_list;
  	userdev_active_device_list = devc;

  	MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags);
	return devc->server_portc.audio_dev;
     }
  MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags);

  return OSS_ENXIO;
}
