/*
 * Purpose: OSS audio loopback (virtual) driver
 *
 * Description:
 * OSS audio loopback driver is a virtual/pseudo driver that can be used
 * for example to user land based virtual audio devices.
 *
 * Each audioloop instance has two sides or endpoints. The server side is
 * typically used by the application that implements the virtual audio device.
 * Client side in turn is used by any audio application that wants to record or
 * play audio. Server side device must be open before the client side can be
 * opened.
 *
 *
 *
 * CAUTION! Certain portc fields (mutex, rate/format) are only available
 *          on the server side portc structure. Care must be taken.
 *
 *
 *
 */
/*
 * 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_audioloop_cfg.h"

#define MAX_RATE 	192000
#define MAX_CHANNELS	64
#define SUPPORTED_FORMATS	(AFMT_S16_NE|AFMT_S32_NE)

extern int audioloop_instances;	/* Config option */

#define MAX_INSTANCES	16

typedef struct _audioloop_devc_t audioloop_devc_t;
typedef struct _audioloop_portc_t audioloop_portc_t;

struct _audioloop_portc_t
{
  audioloop_devc_t *devc;
  audioloop_portc_t *peer;
  int audio_dev;
  int open_mode;
  int port_type;
#define PT_CLIENT	1
#define PT_SERVER	2
  int instance;

  /* State variables */
  int input_triggered, output_triggered;
  oss_wait_queue_t *wq;

  /* Server side (only) fields */
  int rate;
  int channels;
  unsigned int fmt, fmt_bytes;
  oss_mutex_t mutex;
  timeout_id_t timeout_id;
};

struct _audioloop_devc_t
{
  oss_device_t *osdev;
  oss_mutex_t mutex;

  int num_instances;

  audioloop_portc_t *client_portc[MAX_INSTANCES];
  audioloop_portc_t *server_portc[MAX_INSTANCES];
};

#define MAX_ATTACH_COUNT	1
static audioloop_devc_t audioloop_devices[MAX_ATTACH_COUNT];
static int attach_count;

static void
transfer_audio (audioloop_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 (audioloop_portc_t * server_portc)
{
  audioloop_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 (audioloop_portc_t * server_portc)
{
  audioloop_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
audioloop_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.
 */
  audioloop_portc_t *server_portc = pc;
  int tmout = OSS_HZ / 100;

  if (tmout < 1)
    tmout = 1;

  server_portc->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)
    server_portc->timeout_id = timeout (audioloop_cb, server_portc, tmout);
}

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

static int
audioloop_check_output (int dev)
{
  audioloop_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 (audioloop_portc_t * portc)
{
  adev_t *adev;
  int fragsize, frame_size;

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

  fragsize = (portc->rate * frame_size) / 100;	/* Number of bytes/fragment (100Hz) */
  portc->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 = portc->rate;
  adev->iformat_mask = portc->fmt;
  adev->oformat_mask = portc->fmt;
  adev->xformat_mask = portc->fmt;
  adev->min_channels = adev->max_channels = portc->channels;
}

static int
audioloop_server_set_rate (int dev, int arg)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

  if (portc->peer->input_triggered || portc->peer->output_triggered)
    return portc->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;

  portc->rate = arg;

  setup_sample_format (portc);

  return portc->rate = arg;
}

/*ARGSUSED*/
static int
audioloop_client_set_rate (int dev, int arg)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

  return portc->peer->rate;
}

static short
audioloop_server_set_channels (int dev, short arg)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

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

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

  portc->channels = arg;

  setup_sample_format (portc);

  return portc->channels;
}

/*ARGSUSED*/
static short
audioloop_client_set_channels (int dev, short arg)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

  return portc->peer->channels;	/* Server side channels */
}

static unsigned int
audioloop_server_set_format (int dev, unsigned int arg)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

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

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

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

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

    }

  portc->fmt = arg;

  setup_sample_format (portc);

  return portc->fmt;
}

/*ARGSUSED*/
static unsigned int
audioloop_client_set_format (int dev, unsigned int arg)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

static void audioloop_trigger (int dev, int state);

static void
audioloop_reset (int dev)
{
  audioloop_trigger (dev, 0);
}

/*ARGSUSED*/
static int
audioloop_server_open (int dev, int mode, int open_flags)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;
  audioloop_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  adev_t *adev;

  if ((mode & OPEN_READ) && (mode & OPEN_WRITE))
    return OSS_EACCES;

  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);

/*
 * Update client device flags
 */
  adev = audio_engines[portc->peer->audio_dev];
  adev->flags &= ~(ADEV_NOINPUT | ADEV_NOOUTPUT);
  if (!(mode & OPEN_READ))
    adev->flags |= ADEV_NOOUTPUT;
  if (!(mode & OPEN_WRITE))
    adev->flags |= ADEV_NOINPUT;
  adev->enabled = 1;		/* Enable client side */

  return 0;
}

/*ARGSUSED*/
static int
audioloop_client_open (int dev, int mode, int open_flags)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;
  audioloop_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);
  return 0;
}

/*ARGSUSED*/
static void
audioloop_server_close (int dev, int mode)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;
  audioloop_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  audio_engines[portc->peer->audio_dev]->enabled = 0;	/* Disable client side */
  portc->open_mode = 0;

  /* Stop the client side */
  portc->peer->input_triggered = 0;
  portc->peer->output_triggered = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static void
audioloop_client_close (int dev, int mode)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;
  audioloop_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

  /* Stop the server side */
  portc->peer->input_triggered = 0;
  portc->peer->output_triggered = 0;


  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
audioloop_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;
}

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

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

static void
audioloop_trigger (int dev, int state)
{
  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

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

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

      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 (portc->timeout_id == 0)
	  portc->timeout_id = timeout (audioloop_cb, portc, tmout);
    }
  else
    {
      if (portc->port_type == PT_SERVER)
	if (portc->timeout_id != 0)
	  {
	    untimeout (portc->timeout_id);
	    portc->timeout_id = 0;
	  }
    }
}

/*ARGSUSED*/
static int
audioloop_server_prepare_for_input (int dev, int bsize, int bcount)
{
  oss_native_word flags;
  unsigned int status;

  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

  /*
   * Wake the client which may be in waiting in close() 
   */
  oss_wakeup (portc->peer->wq, &portc->mutex, &flags, POLLOUT | POLLWRNORM);

  if (!(portc->peer->open_mode & OPEN_WRITE))
    {
      /* Sleep until the client side becomes ready */
      oss_sleep (portc->wq, &portc->mutex, 0, &flags, &status);
      if (status & WK_SIGNAL)
	{
	  MUTEX_EXIT_IRQRESTORE (portc->mutex, flags);
	  return OSS_EINTR;
	}
    }
  MUTEX_EXIT_IRQRESTORE (portc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
audioloop_server_prepare_for_output (int dev, int bsize, int bcount)
{
  oss_native_word flags;
  unsigned int status;

  audioloop_portc_t *portc = audio_engines[dev]->portc;

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

  /*
   * Wake the client which may be in waiting in close() 
   */
  oss_wakeup (portc->peer->wq, &portc->mutex, &flags, POLLIN | POLLRDNORM);

  if (!(portc->peer->open_mode & OPEN_READ))
    {
      /* Sleep until the client side becomes ready */
      oss_sleep (portc->wq, &portc->mutex, 0, &flags, &status);
      if (status & WK_SIGNAL)
	{
	  MUTEX_EXIT_IRQRESTORE (portc->mutex, flags);
	  return OSS_EINTR;
	}
    }
  MUTEX_EXIT_IRQRESTORE (portc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
audioloop_client_prepare_for_input (int dev, int bsize, int bcount)
{
  oss_native_word flags;
  audioloop_portc_t *portc = audio_engines[dev]->portc;
  unsigned int status;

  MUTEX_ENTER_IRQDISABLE (portc->peer->mutex, flags);
  portc->input_triggered = 0;
  /* Wake the server side */
  oss_wakeup (portc->peer->wq, &portc->peer->mutex, &flags,
	      POLLIN | POLLRDNORM);

  /*
   * Delay a moment so that the server side gets chance to reinit itself
   * for next file/stream.
   */
  oss_sleep (portc->wq, &portc->peer->mutex, OSS_HZ, &flags, &status);
  MUTEX_EXIT_IRQRESTORE (portc->peer->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
audioloop_client_prepare_for_output (int dev, int bsize, int bcount)
{
  oss_native_word flags;
  audioloop_portc_t *portc = audio_engines[dev]->portc;
  unsigned int status;

  MUTEX_ENTER_IRQDISABLE (portc->peer->mutex, flags);
  portc->output_triggered = 0;
  /* Wake the server side */
  oss_wakeup (portc->peer->wq, &portc->peer->mutex, &flags,
	      POLLIN | POLLRDNORM);

  /*
   * Delay a moment so that the server side gets chance to reinit itself
   * for next file/stream.
   */
  oss_sleep (portc->wq, &portc->peer->mutex, OSS_HZ, &flags, &status);
  MUTEX_EXIT_IRQRESTORE (portc->peer->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
audioloop_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
audioloop_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
audioloop_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
}
#endif

static audiodrv_t audioloop_server_driver = {
  audioloop_server_open,
  audioloop_server_close,
  audioloop_output_block,
  audioloop_start_input,
  audioloop_ioctl,
  audioloop_server_prepare_for_input,
  audioloop_server_prepare_for_output,
  audioloop_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  audioloop_trigger,
  audioloop_server_set_rate,
  audioloop_server_set_format,
  audioloop_server_set_channels,
  NULL,
  NULL,
  audioloop_check_input,
  audioloop_check_output,
  audioloop_alloc_buffer,
  audioloop_free_buffer,
  NULL,
  NULL,
  NULL				/* audioloop_get_buffer_pointer */
};

static audiodrv_t audioloop_client_driver = {
  audioloop_client_open,
  audioloop_client_close,
  audioloop_output_block,
  audioloop_start_input,
  audioloop_ioctl,
  audioloop_client_prepare_for_input,
  audioloop_client_prepare_for_output,
  audioloop_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  audioloop_trigger,
  audioloop_client_set_rate,
  audioloop_client_set_format,
  audioloop_client_set_channels,
  NULL,
  NULL,
  audioloop_check_input,
  audioloop_check_output,
  audioloop_alloc_buffer,
  audioloop_free_buffer,
  NULL,
  NULL,
  NULL				/* audioloop_get_buffer_pointer */
};


static int
install_server (audioloop_devc_t * devc, int num)
{
  audioloop_portc_t *portc;
  char tmp[64], devname[16];
  int adev;

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

  if ((portc = PMALLOC (devc->osdev, sizeof (*portc))) == NULL)
    return OSS_ENOMEM;
  memset (portc, 0, sizeof (*portc));

  portc->devc = devc;
  MUTEX_INIT (devc->osdev, portc->mutex, MH_DRV + 1);
  if ((portc->wq = oss_create_wait_queue (devc->osdev, "audioloop")) == NULL)
    {
      cmn_err (CE_WARN, "Cannot create audioloop wait queue\n");
      return OSS_EIO;
    }

  portc->instance = num;
  portc->port_type = PT_SERVER;

  devc->server_portc[num] = portc;

  sprintf (devname, "server%d", num);

  sprintf (tmp, "Audio loopback %d server side", num);

  if ((adev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    tmp,
				    &audioloop_server_driver,
				    sizeof (audiodrv_t),
				    opts, SUPPORTED_FORMATS, devc, -1,
				    devname)) < 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]->caps |= PCM_CAP_HIDDEN;
  audio_engines[adev]->max_channels = MAX_CHANNELS;

  portc->audio_dev = adev;
  portc->rate = 48000;
  portc->fmt = AFMT_S16_NE;
  portc->fmt_bytes = 2;
  portc->channels = 2;

  return 0;
}


static int
install_client (audioloop_devc_t * devc, int num)
{
  audioloop_portc_t *portc;
  char tmp[64];
  int adev;

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

  if ((portc = PMALLOC (devc->osdev, sizeof (*portc))) == NULL)
    return OSS_ENOMEM;
  memset (portc, 0, sizeof (*portc));

  portc->devc = devc;
  if ((portc->wq = oss_create_wait_queue (devc->osdev, "audioloop")) == NULL)
    {
      cmn_err (CE_WARN, "Cannot create audioloop wait queue\n");
      return OSS_EIO;
    }

  portc->instance = num;
  portc->port_type = PT_CLIENT;

  devc->client_portc[num] = portc;

  sprintf (tmp, "Audio loopback %d", num);

  if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    tmp,
				    &audioloop_client_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]->enabled = 0;	/* Not enabled until server side is opened */

  portc->audio_dev = adev;


  return 0;
}

int
oss_audioloop_attach (oss_device_t * osdev)
{
  audioloop_devc_t *devc;
  int i;

  if (attach_count >= MAX_ATTACH_COUNT)
    {
      cmn_err (CE_WARN, "Attach limit reached (%d)\n", MAX_ATTACH_COUNT);
      return 0;
    }

  if (audioloop_instances < 1)
    audioloop_instances = 1;
  if (audioloop_instances > MAX_INSTANCES)
    audioloop_instances = MAX_INSTANCES;

  DDB (cmn_err
       (CE_CONT, "Initailzing audioloop %d instances\n",
	audioloop_instances));
  devc = &audioloop_devices[0];

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

  oss_register_device (osdev, "audioloop");

  for (i = 0; i < audioloop_instances; i++)
    {
      if (install_server (devc, i) < 0)
	break;

      if (install_client (devc, i) < 0)
	break;

      devc->client_portc[i]->peer = devc->server_portc[i];
      devc->server_portc[i]->peer = devc->client_portc[i];
      devc->num_instances = i + 1;
    }

  return 1;
}

int
oss_audioloop_detach (oss_device_t * osdev)
{
  audioloop_devc_t *devc = osdev->devc;
  int i;

  if (oss_disable_device (osdev) < 0)
    return 0;

  MUTEX_CLEANUP (devc->mutex);

  for (i = 0; i < devc->num_instances; i++)
    {
      MUTEX_CLEANUP (devc->server_portc[i]->mutex);
    }

  oss_unregister_device (osdev);

  return 1;
}
