/*
 * Purpose: OSS mixer core.
 * Copyright (C) 4Front Technologies, 2002-2004. All rights reserved.
 *
 * Description:
 */
/*
 * 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_config.h>
#include <midi_core.h>
#include <stdarg.h>
oss_mutex_t oss_timing_mutex;

char *oss_license_string = OSS_LICENSE;

#ifdef DO_TIMINGS
static int timing_is_active = 0; /* 1=The readtimings utility has been active */
#endif

void *mixer_devs_p = NULL;
/*
 * Mixer device list
 */
mixer_operations_t **mixer_devs = NULL;
int num_mixers = 0;
int mixer_port_number = 0;
int current_mixer_card = -1;

/*
 * mix_cvt is used for scaling mixer levels by various drivers.
 */
char mix_cvt[101] = {
  0, 0, 3, 7, 10, 13, 16, 19, 21, 23, 26, 28, 30, 32, 34, 35, 37, 39, 40, 42,
  43, 45, 46, 47, 49, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
  65,
  65, 66, 67, 68, 69, 70, 70, 71, 72, 73, 73, 74, 75, 75, 76, 77, 77, 78, 79,
  79,
  80, 81, 81, 82, 82, 83, 84, 84, 85, 85, 86, 86, 87, 87, 88, 88, 89, 89, 90,
  90,
  91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 96, 97, 97, 98, 98, 98, 99,
  99,
  100
};

static int
get_mixer_info (int dev, ioctl_arg arg)
{
  mixer_info *info = (mixer_info *) arg;

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

  if (dev < 0 || dev >= num_mixers)
    return OSS_ENXIO;

  strcpy (info->id, mixer_devs[dev]->id);
  strncpy (info->name, mixer_devs[dev]->name, 32);
  info->name[31] = '\0';
  info->modify_counter = mixer_devs[dev]->modify_counter;

  return 0;
}

/*
 * Legacy mixer handling
 */

int
oss_legacy_mixer_ioctl (int mixdev, int audiodev, unsigned int cmd,
			ioctl_arg arg)
{
  int ret;

#ifdef DO_TIMINGS
    oss_timing_printf ("oss_legacy_mixer_ioctl(%d/%d, %08x) entered", mixdev,
	     audiodev, cmd);
#endif

  if (!(cmd & SIOC_OUT) && !(cmd & SIOC_IN))
    return OSS_EINVAL;

  if (arg == 0)
    return OSS_EINVAL;

  if (((cmd >> 8) & 0xff) != 'M')	/* Not a mixer ioctl */
    return OSS_EINVAL;

  if (mixdev < 0 || mixdev >= num_mixers)
    {
      cmn_err (CE_WARN, "Bad mixer device %d\n", mixdev);
      return OSS_EIO;
    }

  if (cmd == SOUND_MIXER_INFO)
    return get_mixer_info (mixdev, arg);

  if (!mixer_devs[mixdev]->enabled || mixer_devs[mixdev]->unloaded)
    return OSS_ENXIO;

  if (mixer_devs[mixdev]->d->ioctl == NULL)
    return OSS_EINVAL;

  if (IOC_IS_OUTPUT (cmd))
    mixer_devs[mixdev]->modify_counter++;

  ret = mixer_devs[mixdev]->d->ioctl (mixdev, audiodev, cmd, arg);

  return ret;
}

/*
 * Handling of initial mixer volumes
 */

static int muted_mixer_levels[32] = {0};

static int default_mixer_levels[32] = {
  0x3232,			/* Master Volume */
  0x3232,			/* Bass */
  0x3232,			/* Treble */
  0x4b4b,			/* FM */
  0x3232,			/* PCM */
  0x1515,			/* PC Speaker */
  0x2020,			/* Ext Line */
  0x2020,			/* Mic */
  0x4b4b,			/* CD */
  0x0000,			/* Recording monitor */
  0x4b4b,			/* Second PCM */
  0x4b4b,			/* Recording level */
  0x4b4b,			/* Input gain */
  0x4b4b,			/* Output gain */
  0x2020,			/* Line1 */
  0x2020,			/* Line2 */
  0x1515			/* Line3 (usually line in) */
};

/*
 * Table for configurable mixer volume handling
 */
static mixer_vol_table mixer_vols[MAX_MIXER_DEV];
static int num_mixer_volumes = 0;

int *
load_mixer_volumes (char *name, int *levels, int present)
{
  int i, n;
  extern int mixer_muted;

  if (mixer_muted)		/* Config setting from osscore.conf */
     return muted_mixer_levels;

  if (levels == NULL)
    levels = default_mixer_levels;

  for (i = 0; i < num_mixer_volumes; i++)
    if (strcmp (name, mixer_vols[i].name) == 0)
      {
	if (present)
	  mixer_vols[i].num = i;
	return mixer_vols[i].levels;
      }

  if (num_mixer_volumes >= MAX_MIXER_DEV)
    {
      cmn_err (CE_WARN, "Too many mixers (%s/%d/%d)\n",
	       name, num_mixer_volumes, MAX_MIXER_DEV);
      return levels;
    }

  n = num_mixer_volumes++;

  strcpy (mixer_vols[n].name, name);

  if (present)
    mixer_vols[n].num = n;
  else
    mixer_vols[n].num = -1;

  for (i = 0; i < 32; i++)
    mixer_vols[n].levels[i] = levels[i];
  return mixer_vols[n].levels;
}

/*
 * Mixer "extension" handling
 */

static int
oss_mixer_ext_info (oss_mixext * ent)
{

  int dev, ctrl;
  int extnr;

  if (ent == NULL)
    return OSS_EFAULT;

  if (ent->dev < 0 || ent->dev >= num_mixers)
    return OSS_ENXIO;

  dev = ent->dev;
  if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded)
    return OSS_ENXIO;
  touch_mixer (dev);

  ctrl = ent->ctrl;
  if (ent->ctrl < 0 || ent->ctrl >= mixer_devs[dev]->nr_ext)
    return OSS_EIDRM;
  extnr = ent->ctrl;

  memcpy ((char *) ent, (char *) &mixer_devs[dev]->extensions[extnr].ext,
	  sizeof (*ent));

  switch (ent->type)
    {
    case MIXT_MONOPEAK:
    case MIXT_STEREOPEAK:
    case MIXT_MONOVU:
    case MIXT_STEREOVU:
      /* Peak meters will need to be polled */
      ent->flags |= MIXF_POLL;
      break;
    }

/*
 * Read-only controls are likely to change their value spontaneously so
 * they should be polled.
 */
  if (!(ent->flags & MIXF_WRITEABLE))
     ent->flags |= MIXF_POLL;

  ent->ctrl = ctrl;
  return 0;
}

static int
mixer_ext_get_enuminfo (oss_mixer_enuminfo * ent)
{

  int dev, ctrl;

  if (ent == NULL)
    return OSS_EFAULT;

  dev = ent->dev;
  ctrl = ent->ctrl;
  memset (ent, 0, sizeof (*ent));

  if (dev < 0 || dev >= num_mixers)
    {
      return OSS_ENXIO;
    }

  touch_mixer (dev);

  if (ctrl < 0 || ctrl >= mixer_devs[dev]->nr_ext)
    {
      return OSS_EIDRM;
    }

  if (mixer_devs[dev]->extensions[ctrl].enum_info == NULL)
    {
      return OSS_EIO;
    }

  memcpy ((char *) ent,
	  (char *) mixer_devs[dev]->extensions[ctrl].enum_info,
	  sizeof (*ent));
  ent->ctrl = ctrl;
  return 0;
}

static int
mixer_ext_get_description (oss_mixer_enuminfo * ent)
{

  int dev, ctrl;
  char *s;

  if (ent == NULL)
    return OSS_EFAULT;

  dev = ent->dev;
  ctrl = ent->ctrl;
  memset (ent, 0, sizeof (*ent));

  if (dev < 0 || dev >= num_mixers)
    {
      return OSS_ENXIO;
    }

  touch_mixer (dev);

  if (ctrl < 0 || ctrl >= mixer_devs[dev]->nr_ext)
    {
      return OSS_EIDRM;
    }

  if (mixer_devs[dev]->extensions[ctrl].description == NULL)
    {
      return OSS_EIO;
    }

  s = mixer_devs[dev]->extensions[ctrl].description;

  strncpy (ent->strings, s, sizeof (ent->strings));
  ent->strings [sizeof (ent->strings) - 1] = '\0';
  ent->ctrl = ctrl;
  return 0;
}

int
mixer_ext_set_strings (int dev, int ctl, const char *s, int version)
{
/*
 * Note! The version parameter should usually be set to 0. However if the
 * 	 value set can change dynamically then it must be initially set to 1
 * 	 and later incremented every time the value set changes.
 * 	 This tells the applications to poll for updated values.
 */
  static oss_mixer_enuminfo ent;

  memset (&ent, 0, sizeof (ent));
  ent.dev = dev;
  ent.ctrl = ctl;
  ent.version = version;
  ent.nvalues = 0;
  strcpy (ent.strings, s);
  return mixer_ext_set_enum (&ent);
}

static int
rebuild_list (oss_mixer_enuminfo * ent)
{
  int i, n, l;

  n = 1;
  ent->nvalues = 0;
  ent->strindex[0] = 0;
  l = strlen (ent->strings);

  for (i = 0; i < l; i++)
    if (n < OSS_ENUM_MAXVALUE)
      {
	if (ent->strings[i] == ' ')
	  {
	    ent->strindex[n++] = i + 1;
	    ent->strings[i] = 0;
	  }
      }

  ent->nvalues = n;
  return 0;
}

int
mixer_ext_set_enum (oss_mixer_enuminfo * ent)
{
  int dev;
  int extnr;

  if (ent == NULL)
    return OSS_EFAULT;

  if (ent->dev < 0 || ent->dev >= num_mixers)
    return OSS_ENXIO;

  dev = ent->dev;
  touch_mixer (dev);

  if (ent->ctrl < 0 || ent->ctrl >= mixer_devs[dev]->nr_ext)
    return OSS_EIDRM;
  extnr = ent->ctrl;

  if (mixer_devs[dev]->extensions[extnr].enum_info == NULL)
    mixer_devs[dev]->extensions[extnr].enum_info =
      PMALLOC (mixer_devs[dev]->osdev, sizeof (*ent));

  if (mixer_devs[dev]->extensions[extnr].enum_info == NULL)
    return OSS_EIO;

  memcpy ((char *) mixer_devs[dev]->extensions[extnr].enum_info,
	  (char *) ent, sizeof (*ent));

  ent = mixer_devs[dev]->extensions[extnr].enum_info;

  if (ent->nvalues <= 0)
    return rebuild_list (ent);

  if (ent->nvalues >= OSS_ENUM_MAXVALUE)
    {
      mixer_devs[dev]->extensions[extnr].enum_info = NULL;
      return OSS_EIO;
    }

  return 0;
}

int
mixer_ext_set_description (int dev, int ctrl, const char *desc)
{
  int l = strlen(desc);

  if (dev < 0 || dev >= num_mixers)
    return OSS_ENXIO;

  touch_mixer (dev);

  if (ctrl < 0 || ctrl >= mixer_devs[dev]->nr_ext)
    return OSS_EIDRM;

  if (l > OSS_ENUM_STRINGSIZE) l = OSS_ENUM_STRINGSIZE;

    mixer_devs[dev]->extensions[ctrl].description =
      PMALLOC (mixer_devs[dev]->osdev, l);

  if (mixer_devs[dev]->extensions[ctrl].description == NULL)
    return OSS_EIO;

  strncpy (mixer_devs[dev]->extensions[ctrl].description, desc, l);
  mixer_devs[dev]->extensions[ctrl].description[l-1] = '\0';

  mixer_devs[dev]->extensions[ctrl].ext.flags |= MIXF_DESCR;

  return 0;
}

static int
mixer_ext_read (oss_mixer_value * val)
{

  int dev;
  int extnr;
  oss_mixext *ext;
  oss_mixext_desc *ext_desc;
  mixer_ext_fn func;

  if (val == NULL)
    return OSS_EFAULT;

  if (val->dev < 0 || val->dev >= num_mixers)
    return OSS_ENXIO;

  dev = val->dev;

  if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded)
    return OSS_ENXIO;
  touch_mixer (dev);

  if (val->ctrl < 0 || val->ctrl >= mixer_devs[dev]->nr_ext)
    return OSS_EIDRM;
  extnr = val->ctrl;

  ext_desc = &mixer_devs[dev]->extensions[extnr];
  ext = &ext_desc->ext;

  if (val->timestamp != ext->timestamp)
    {
      return OSS_EIDRM;
    }

  if (ext_desc->handler == NULL || !(ext->flags & MIXF_READABLE))
    return OSS_EFAULT;

  func = (mixer_ext_fn) ext_desc->handler;
  return (val->value = func (dev, ext->ctrl, SNDCTL_MIX_READ, val->value));
}

static int
mixer_ext_write (oss_mixer_value * val)
{

  int dev;
  int extnr;
  int err;
  oss_mixext *ext;
  oss_mixext_desc *ext_desc;
  mixer_ext_fn func;

  if (val == NULL)
    {
      cmn_err (CE_WARN, "NULL argument in mixer call\n");
      return OSS_EFAULT;
    }

  if (val->dev < 0 || val->dev >= num_mixers)
    return OSS_ENXIO;

  dev = val->dev;
  if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded)
    return OSS_ENXIO;
  touch_mixer (dev);

  if (val->ctrl < 0 || val->ctrl >= mixer_devs[dev]->nr_ext)
    return OSS_EIDRM;
  extnr = val->ctrl;

  ext_desc = &mixer_devs[dev]->extensions[extnr];
  ext = &ext_desc->ext;

  if (ext_desc->handler == NULL || !(ext->flags & MIXF_WRITEABLE))
    {
      cmn_err (CE_WARN, "NULL handler or control not writeable\n");
      return OSS_EFAULT;
    }

  if (val->timestamp != ext->timestamp)
    {
      return OSS_EIDRM;
    }

  func = (mixer_ext_fn) ext_desc->handler;
  mixer_devs[dev]->modify_counter++;
  err = val->value = func (dev, ext->ctrl, SNDCTL_MIX_WRITE, val->value);

  if (err >= 0)
    ext->update_counter++;
  return err;
}

int
mixer_ext_create_device (int dev, int maxentries)
{
  oss_mixext_root *mixroot;
  oss_mixext *mixext;
  oss_mixext_desc *mixext_desc;
  char *name = mixer_devs[dev]->name;
  char *id = mixer_devs[dev]->id;
  int i;

  maxentries++;			/* Needs space for the device root node */

  if (mixer_devs[dev]->max_ext == 0)
     {
        mixext_desc =
          PMALLOC (mixer_devs[dev]->osdev, sizeof (*mixext_desc) * maxentries);
        mixer_devs[dev]->max_ext = maxentries;
  	mixer_devs[dev]->extensions = mixext_desc;
     }
  else
     {
  	mixext_desc = mixer_devs[dev]->extensions;
     }

  if (mixext_desc == NULL)
    {
      cmn_err (CE_CONT, "Not enough memory for mixer%d (ext)\n", dev);
      return OSS_EIO;
    }

  mixer_devs[dev]->nr_ext = 1;
  mixer_devs[dev]->timestamp = GET_JIFFIES ();

  mixext = &mixext_desc->ext;
  mixext->dev = dev;
  mixext->ctrl = -1;		/* Undefined */
  mixext->type = MIXT_DEVROOT;
  mixext->maxvalue = 0;
  mixext->minvalue = 0;
  mixext->flags = 0;
  strcpy (mixext->id, "DEVROOT");
  mixext->parent = 0;		/* Link to itself */
  mixext->timestamp = mixer_devs[dev]->timestamp;
  mixext_desc->handler = NULL;
  memset (mixext->data, 0, sizeof (mixext->data));

  mixroot = (oss_mixext_root *) & mixext->data;

  for (i = 0; i < 15 && id[i]; i++)
    mixroot->id[i] = id[i];
  mixroot->id[15] = 0;

  for (i = 0; i < 47 && name[i]; i++)
    mixroot->name[i] = name[i];
  mixroot->name[47] = 0;

  return 0;
}

int
mixer_ext_create_group_flags (int dev, int parent, const char *id,
			      unsigned int flags)
{
  oss_mixext *mixext;
  oss_mixext_desc *mixext_desc, *parent_desc;
  int enumber;

  flags &= ~MIXF_DESCR;

  if (mixer_devs[dev]->extensions == NULL)
    {
      cmn_err (CE_WARN, "Mixer extensions not initialized for device %d\n",
	       dev);
      return OSS_EFAULT;
    }

  /*
   * Ensure that the parent node number is valid.
   */
  if (parent < 0 || parent >= mixer_devs[dev]->nr_ext)
     parent = 0;

  parent_desc =
    &mixer_devs[dev]->extensions[parent];
  mixext = &parent_desc->ext;

  if (mixext->type != MIXT_DEVROOT && mixext->type != MIXT_GROUP)
     parent = 0; /* Point to the root group */

  if (mixer_devs[dev]->nr_ext >= mixer_devs[dev]->max_ext)
    {
      cmn_err (CE_WARN, "Out of mixer controls for device %d/%s (%d)\n", dev,
	       mixer_devs[dev]->name, mixer_devs[dev]->max_ext);
      return OSS_ENOSPC;
    }

  mixext_desc =
    &mixer_devs[dev]->extensions[(enumber = mixer_devs[dev]->nr_ext++)];
  mixext = &mixext_desc->ext;
  mixext->dev = dev;
  mixext->ctrl = -1;		/* Undefined */
  mixext->type = MIXT_GROUP;
  mixext->maxvalue = 0;
  mixext->minvalue = 0;
  mixext->flags = flags | MIXF_FLAT;	/* Will be unflattened later if required */
  strcpy (mixext->id, id);
  mixext->parent = parent;
  mixext_desc->handler = NULL;
  mixext_desc->enum_info = NULL;
  memset (mixext->enum_present, 0xff, sizeof (mixext->enum_present));
  mixext->timestamp = mixer_devs[dev]->timestamp;
  mixext->control_no = -1;
  mixext->desc = 0;
  memset (mixext->data, 0, sizeof (mixext->data));

  return enumber;
}

int
mixer_ext_create_group (int dev, int parent, const char *id)
{
  return mixer_ext_create_group_flags (dev, parent, id, 0);
}

int
mixer_ext_truncate (int dev, int index)
{
  if (index < mixer_devs[dev]->nr_ext)
    {
      mixer_devs[dev]->nr_ext = index;
      mixer_devs[dev]->modify_counter++;
      mixer_devs[dev]->timestamp++;
    }
  return 0;
}

static void expand_names (int dev);
static void unflatten_group (int dev, int group);
static void touch_parents (int dev, int group);

int
mixer_ext_create_control (int dev, int parent, int ctrl, mixer_ext_fn func,
			  int type, const char *id, int maxvalue, int flags)
{
  oss_mixext *mixext;
  oss_mixext_desc *mixext_desc, *parent_desc;
  int enumber;

  flags &= ~MIXF_DESCR;

  if (mixer_devs[dev]->extensions == NULL)
    {
      cmn_err (CE_WARN, "Mixer extensions not initialized for device %d\n",
	       dev);
      return OSS_EFAULT;
    }

  if (mixer_devs[dev]->nr_ext >= mixer_devs[dev]->max_ext)
    {
      cmn_err (CE_WARN, "Out of mixer controls for device %d/%s (%d)\n", dev,
	       mixer_devs[dev]->name, mixer_devs[dev]->max_ext);
      return OSS_ENOSPC;
    }

  if (func == NULL)		/* No access function */
    flags &= ~(MIXF_READABLE | MIXF_WRITEABLE);

  /*
   * Ensure that the parent node number is valid.
   */
  if (parent < 0 || parent >= mixer_devs[dev]->nr_ext)
     parent = 0;

  parent_desc =
    &mixer_devs[dev]->extensions[parent];
  mixext = &parent_desc->ext;

  if (mixext->type != MIXT_DEVROOT && mixext->type != MIXT_GROUP)
     parent = 0; /* Point to the root group */


  mixext_desc =
    &mixer_devs[dev]->extensions[(enumber = mixer_devs[dev]->nr_ext++)];
  mixext = &mixext_desc->ext;
  mixext->dev = dev;
  mixext->ctrl = ctrl;
  mixext->type = type;
  mixext->maxvalue = maxvalue;
  mixext->minvalue = 0;
  mixext->flags = flags;
  strncpy (mixext->id, id, sizeof (mixext->id));
  mixext->id[sizeof (mixext->id) - 1] = 0;
  mixext->parent = parent;
  mixext->timestamp = mixer_devs[dev]->timestamp;
  mixext_desc->handler = (mixer_ext_fn) func;
  mixext_desc->enum_info = NULL;
  memset (mixext->data, 0, sizeof (mixext->data));
  memset (mixext->enum_present, 0xff, sizeof (mixext->enum_present));
  mixext->control_no = -1;
  mixext->desc = 0;

/*
 * Perform name expansion too if it has already been done for the
 * earlier controls. Note that this only gets done with rare devices
 * that add/remove controls on fly.
 *
 * TODO: Optimize this to expand only the current control. Scanning through
 * all the controls may be bit time consuming with future devices having 100s 
 * of controls.
 */
  if (mixer_devs[dev]->names_checked)
    expand_names (dev);
  else
    strcpy(mixext->extname, mixext->id);

/*
 * Mark groups with tall controls such as peak meters and sliders as
 * non-flat
 */

  switch (type)
    {
    case MIXT_SLIDER:
    case MIXT_MONOSLIDER:
    case MIXT_STEREOSLIDER:
    case MIXT_MONOSLIDER16:
    case MIXT_STEREOSLIDER16:
    case MIXT_MONOVU:
    case MIXT_STEREOVU:
    case MIXT_MONOPEAK:
    case MIXT_STEREOPEAK:
    case MIXT_MONODB:
    case MIXT_STEREODB:
    case MIXT_3D:
      unflatten_group (dev, parent);
      break;
    }

  touch_parents(dev, parent);

  return enumber;
}


oss_mixext *
mixer_find_ext (int dev, int enumber)
{
  oss_mixext_desc *mixext;

  if (dev < 0 || dev >= num_mixers)
    {
      return NULL;
    }
  touch_mixer (dev);

  if (enumber < 0 || enumber >= mixer_devs[dev]->nr_ext)
    {
      return NULL;
    }

  mixext = &mixer_devs[dev]->extensions[enumber];

  return &mixext->ext;
}

/*
 * Default read/write access functions
 */
int
mixer_ext_rw (int dev, int ctrl, unsigned int cmd, int value)
{
  int err;

  if (cmd == SNDCTL_MIX_READ)
    {
      if ((err =
	   oss_legacy_mixer_ioctl (dev, -1, MIXER_READ (ctrl),
				   (ioctl_arg) & value)) < 0)
	return err;
      return value;
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      if ((err =
	   oss_legacy_mixer_ioctl (dev, -1, MIXER_WRITE (ctrl),
				   (ioctl_arg) & value)) < 0)
	return err;
      return value;
    }

  return OSS_EINVAL;
}

int
mixer_ext_recrw (int dev, int ctrl, unsigned int cmd, int value)
{
  int caps, recmask, err;

  if ((err =
       oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_READ_CAPS,
			       (ioctl_arg) & caps)) < 0)
    caps = SOUND_CAP_EXCL_INPUT;	/* Default */

  if ((err =
       oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_READ_RECSRC,
			       (ioctl_arg) & recmask)) < 0)
    return err;

  if (cmd == SNDCTL_MIX_READ)
    return (recmask & (1 << ctrl)) ? 1 : 0;

  if (caps & SOUND_CAP_EXCL_INPUT)	/* Single recording source */
    recmask = 0;

  if (value)
    recmask |= (1 << ctrl);
  else
    recmask &= ~(1 << ctrl);

  if (recmask == 0)
    return 1;			/* Can't remove the only recording source */

  if ((err =
       oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_WRITE_RECSRC,
			       (ioctl_arg) & recmask)) < 0)
    return err;


  return (recmask & (1 << ctrl)) ? 1 : 0;
}

/*
 * Mixer extension initialization
 */

static void
store_name (oss_mixext * thisrec, char *name)
{
  int i;

  while (*name == '.')
    name++;
  strncpy (thisrec->extname, name, 32);
  thisrec->extname[31] = '\0';

  name = thisrec->extname;
  for (i = 0; i < strlen (name); i++)
    if (name[i] >= 'A' && name[i] <= 'Z')
      name[i] += 32;
}

static char *
cut_name (char *name)
{
  char *s = name;
  while (*s)
    if (*s++ == '_')
      return s;

  if (name[0] == '@')
    return &name[1];

  return name;
}

#include "mixerdefs.h"

static void
find_enum_defs (oss_mixext * thisrec, int dev, int ctl)
{
  int i;

  for (i = 0; mixer_defs[i].name != NULL; i++)
    if (strcmp (thisrec->extname, mixer_defs[i].name) == 0)
      {
	mixer_ext_set_strings (dev, ctl, mixer_defs[i].strings, 0);
	return;
      }
}

static void
expand_names (int dev)
{
  int i, n;
  oss_mixext_desc *mixext_desc;

  n = mixer_devs[dev]->nr_ext;
  mixer_devs[dev]->names_checked = 1;

  if (n < 1)
    return;

  for (i = 0; i < n; i++)
    {
      char tmp[100], *name;
      int parent = 0;
      oss_mixext *thisrec = NULL, *parentrec = NULL;

      mixext_desc = &mixer_devs[dev]->extensions[i];
      thisrec = &mixext_desc->ext;

      switch (thisrec->type)
	{
	case MIXT_DEVROOT:
	  thisrec->extname[0] = 0;
	  break;

	case MIXT_GROUP:
	  parent = thisrec->parent;
	  mixext_desc = &mixer_devs[dev]->extensions[parent];
	  parentrec = &mixext_desc->ext;
	  name = cut_name (thisrec->id);
	  if (parentrec->extname[0] == 0)
	    strcpy (tmp, name);
	  else
	    sprintf (tmp, "%s.%s", parentrec->extname, name);
	  store_name (thisrec, tmp);
	  break;

	case MIXT_STEREOSLIDER:
	case MIXT_STEREOSLIDER16:
	case MIXT_STEREODB:
	case MIXT_STEREOVU:
	case MIXT_MONODB:
	case MIXT_MONOSLIDER:
	case MIXT_MONOSLIDER16:
	case MIXT_SLIDER:
	case MIXT_MONOVU:
	case MIXT_MONOPEAK:
	case MIXT_STEREOPEAK:
	case MIXT_ONOFF:
	case MIXT_MUTE:
	case MIXT_ENUM:
	case MIXT_VALUE:
	case MIXT_HEXVALUE:
	case MIXT_3D:
	  parent = thisrec->parent;
	  mixext_desc = &mixer_devs[dev]->extensions[parent];
	  parentrec = &mixext_desc->ext;
	  name = cut_name (thisrec->id);
	  if (*thisrec->id == 0 || *thisrec->id == '-')	/* Special (hidden) names */
	    strcpy (thisrec->extname, parentrec->extname);
	  else
	    {
	      sprintf (tmp, "%s.%s", parentrec->extname, name);
	      store_name (thisrec, tmp);

	      if (thisrec->type == MIXT_ENUM)
		find_enum_defs (thisrec, dev, i);
	    }
	  break;

	case MIXT_MARKER:
	  break;

	default:;
	}
    }
/*
 * Fix duplicate names.
 */

  for (i = 0; i < n; i++)
    {
      char tmp[100];
      int j, dupes = 0;
      oss_mixext *thisrec = NULL;

      mixext_desc = &mixer_devs[dev]->extensions[i];
      thisrec = &mixext_desc->ext;

      if (thisrec->type == MIXT_GROUP)
	continue;

      strcpy (tmp, thisrec->extname);

      for (j = i + 1; j < n; j++)
	{
	  oss_mixext_desc *mixext_desc2;
	  oss_mixext *thisrec2 = NULL;
	  mixext_desc2 = &mixer_devs[dev]->extensions[j];
	  thisrec2 = &mixext_desc2->ext;

	  if (thisrec2->type == MIXT_GROUP)
	    continue;

	  if (strcmp (thisrec2->extname, tmp) == 0)
	    dupes++;
	}

      if (dupes > 0)		/* Need to fix duplicates */
	{
	  int count = 1, len;
	  char tmp2[32];

	  for (j = i; j < n; j++)
	    {
	      oss_mixext_desc *mixext_desc2;
	      oss_mixext *thisrec2 = NULL;
	      mixext_desc2 = &mixer_devs[dev]->extensions[j];
	      thisrec2 = &mixext_desc2->ext;

	      if (thisrec2->type != MIXT_GROUP)
		if (strcmp (thisrec2->extname, tmp) == 0)
		  {
		    sprintf (tmp2, "%d", count++);
		    tmp2[31] = '\0';
		    len = strlen (thisrec2->extname);
		    if (len >= sizeof (thisrec2->extname) - strlen (tmp2))
		      len = sizeof (thisrec2->extname) - strlen (tmp2) - 1;
		    strcpy (thisrec2->extname + len, tmp2);
		  }
	    }
	}
    }
}

static void
unflatten_group (int dev, int group)
{
/*
 * Clear the MIXF_FLAT flags from all parent groups (recursively):
 */
  int n;
  oss_mixext_desc *mixext_desc;
  oss_mixext *thisrec = NULL;

  n = mixer_devs[dev]->nr_ext;

  if (n < 1)
    return;

  if (group <= 0 || group >= n)
    return;

  mixext_desc = &mixer_devs[dev]->extensions[group];
  thisrec = &mixext_desc->ext;

  if (thisrec->type != MIXT_GROUP)	/* Not a group */
    return;

  if (!(thisrec->flags & MIXF_FLAT))	/* Already unflattened */
    return;

  thisrec->flags &= ~MIXF_FLAT;

  if (thisrec->parent >= group)	/* Broken link */
    return;

  unflatten_group (dev, thisrec->parent);	/* Unflatten the parent */
}

static void
touch_parents (int dev, int group)
{
  int n;
  oss_mixext_desc *mixext_desc;
  oss_mixext *thisrec = NULL;

  n = mixer_devs[dev]->nr_ext;

  if (n < 1)
    return;

  if (group <= 0 || group >= n)
    return;

  mixext_desc = &mixer_devs[dev]->extensions[group];
  thisrec = &mixext_desc->ext;

  while (thisrec->type != MIXT_DEVROOT)
    {
      if (thisrec->type != MIXT_GROUP)	/* Not a group */
        return;

      thisrec->update_counter++;

      if (thisrec->parent >= group)	/* Broken link */
        return;

      unflatten_group (dev, thisrec->parent);	/* Unflatten the parent */

      mixext_desc = &mixer_devs[dev]->extensions[thisrec->parent];
      thisrec = &mixext_desc->ext;
    }

  thisrec->update_counter++;
}

#define INPUT_MASK (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN)
#define OUTPUT_MASK (SOUND_MASK_PCM|SOUND_MASK_ALTPCM|SOUND_MASK_VOLUME| \
		SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_SYNTH| \
		SOUND_MASK_IMIX|SOUND_MASK_REARVOL|SOUND_MASK_CENTERVOL| \
		SOUND_MASK_SIDEVOL)
#define MONITOR_MASK (SOUND_MASK_SPEAKER|SOUND_MASK_LINE|SOUND_MASK_LINE| \
		SOUND_MASK_MIC|SOUND_MASK_CD|SOUND_MASK_LINE1|SOUND_MASK_LINE2| \
		SOUND_MASK_LINE3|SOUND_MASK_DIGITAL1|SOUND_MASK_DIGITAL2| \
		SOUND_MASK_DIGITAL3|SOUND_MASK_MONO|SOUND_MASK_PHONE| \
		SOUND_MASK_RADIO|SOUND_MASK_VIDEO)

void
touch_mixer (int dev)
{
  int i, n, devmask, recmask, stereomask, grp, root = 0, caps = 0;
  static char *id[] = SOUND_DEVICE_NAMES;
  int created = 0;

  if (mixer_devs[dev] == NULL || mixer_devs[dev]->unloaded
      || !mixer_devs[dev]->enabled)
    {
      return;
    }

/*
 * Create default mixer extension records if required.
 */
  if (mixer_devs[dev]->nr_ext > 0)	/* Already initialized */
    return;

/*
 * Compute number of required mixer extension entries
 */

  n = mixer_devs[dev]->nr_extra_ext;	/* Reserve space for the actual driver */
  if (n < 20)
    n = 20;

  if (oss_legacy_mixer_ioctl
      (dev, -1, SOUND_MIXER_READ_CAPS, (ioctl_arg) & caps) < 0)
    caps = 0;			/* Error */

  if (oss_legacy_mixer_ioctl
      (dev, -1, SOUND_MIXER_READ_DEVMASK, (ioctl_arg) & devmask) < 0)
    goto skip;			/* Error */

  /* Remove devices that are handled otherwise */
  devmask &= ~mixer_devs[dev]->ignore_mask;

  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
    if (devmask & (1 << i))	/* This control is supported */
      n++;

  n = n * 2;

  if (oss_legacy_mixer_ioctl
      (dev, -1, SOUND_MIXER_READ_RECMASK, (ioctl_arg) & recmask) < 0)
    goto skip;			/* Error */

  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
    if (recmask & (1 << i))	/* This control is also recording device */
      n++;

  root = 0;

#ifdef CONFIG_OSSD
  n += 20;			/* Space for OSSD use */
#endif

  n = n + 5;			/* The marker entry and some spare space */
  if ((root = mixer_ext_create_device (dev, n)) < 0)
    return;			/* Error */
  created = 1;

  if (oss_legacy_mixer_ioctl (dev, -1, SOUND_MIXER_READ_STEREODEVS,
			      (ioctl_arg) & stereomask) < 0)
    stereomask = -1;		/* Assume all stereo */

  if (!(caps & SOUND_CAP_NOLEGACY))	/* Don't (re)export the legacy mixer */
    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
      if (devmask & (1 << i))
	{
	  if ((grp =
	       mixer_ext_create_group_flags (dev, root, id[i],
					     MIXF_LEGACY)) > 0)
	    {
	      int cnum;
	      oss_mixext *ent;
	      int flags = 0;

	      /*
	       * Set the type hints for main and PCM volume controls
	       */

	      switch (i)
		{
		case SOUND_MIXER_VOLUME:
		case SOUND_MIXER_MONO:
		case SOUND_MIXER_REARVOL:
		case SOUND_MIXER_CENTERVOL:
		case SOUND_MIXER_SIDEVOL:
		  flags |= MIXF_MAINVOL;
		  break;

		case SOUND_MIXER_PCM:
		case SOUND_MIXER_ALTPCM:
		  flags |= MIXF_PCMVOL;
		  break;

		case SOUND_MIXER_RECLEV:
		case SOUND_MIXER_IGAIN:
		  flags |= MIXF_RECVOL;
		  break;

		case SOUND_MIXER_SYNTH:
		case SOUND_MIXER_SPEAKER:
		case SOUND_MIXER_LINE:
		case SOUND_MIXER_LINE1:
		case SOUND_MIXER_LINE2:
		case SOUND_MIXER_LINE3:
		case SOUND_MIXER_MIC:
		case SOUND_MIXER_CD:
		case SOUND_MIXER_DIGITAL1:
		case SOUND_MIXER_DIGITAL2:
		case SOUND_MIXER_DIGITAL3:
		case SOUND_MIXER_PHONE:
		case SOUND_MIXER_VIDEO:
		case SOUND_MIXER_RADIO:
		  flags |= MIXF_MONVOL;
		  break;
		}

	      if (stereomask & (1 << i))
		cnum = mixer_ext_create_control (dev, grp, i, mixer_ext_rw,
						 MIXT_STEREOSLIDER,
						 "", 100,
						 flags | MIXF_READABLE |
						 MIXF_WRITEABLE);
	      else
		cnum = mixer_ext_create_control (dev, grp, i, mixer_ext_rw,
						 MIXT_MONOSLIDER,
						 "", 100,
						 flags | MIXF_READABLE |
						 MIXF_WRITEABLE);

	      if ((ent = mixer_find_ext (dev, cnum)) != NULL)
		{
		  ent->control_no = i;

		  if ((1 << i) & INPUT_MASK)
		    ent->desc &= MIXEXT_SCOPE_INPUT;
		  if ((1 << i) & OUTPUT_MASK)
		    ent->desc &= MIXEXT_SCOPE_OUTPUT;
		  if ((1 << i) & MONITOR_MASK)
		    ent->desc &= MIXEXT_SCOPE_MONITOR;

		      /*
		       * Set the RGB color for some of the controls
		       * to match the usual jack color.
		       */

		      switch (i)
		      {
		      case SOUND_MIXER_MIC: ent->rgbcolor=OSS_RGB_PINK; break;
		      case SOUND_MIXER_LINE: ent->rgbcolor=OSS_RGB_BLUE; break;
		      case SOUND_MIXER_VOLUME: ent->rgbcolor=OSS_RGB_GREEN; break;
		      case SOUND_MIXER_REARVOL: ent->rgbcolor=OSS_RGB_BLACK; break;
		      case SOUND_MIXER_SIDEVOL: ent->rgbcolor=OSS_RGB_GRAY; break;
		      case SOUND_MIXER_CENTERVOL: ent->rgbcolor=OSS_RGB_ORANGE; break;
		      }
		 }

	      if (recmask & (1 << i))
		{
		  cnum =
		    mixer_ext_create_control (dev, grp, i, mixer_ext_recrw,
					      MIXT_ONOFF, "REC", 1,
					      MIXF_READABLE | MIXF_WRITEABLE | MIXF_RECVOL);
		  if ((ent = mixer_find_ext (dev, cnum)) != NULL)
		    {
		      ent->desc &= MIXEXT_SCOPE_RECSWITCH;
		    }
		}
	    }
	}

skip:
  if (!created)
    if ((root = mixer_ext_create_device (dev, n)) < 0)
      return;			/* Error */
  mixer_ext_create_control (dev, root, 0, NULL, MIXT_MARKER, "", 0, 0);

  if (mixer_devs[dev]->create_controls != NULL)
    mixer_devs[dev]->create_controls (dev);
  expand_names (dev);
}

int
mixer_ext_set_init_fn (int dev, mixer_create_controls_t func, int nextra)
{
/*
 * Set device dependent mixer extension initialization function and
 * reserve some extension entries for device dependent use.
 *
 * This initialization function will be called later when/if the
 * extended mixer is actually used.
 */
  if (dev < 0 || dev >= num_mixers)
    return OSS_ENXIO;

  mixer_devs[dev]->nr_extra_ext = nextra;
  mixer_devs[dev]->create_controls = func;
  return 0;
}

int
mixer_ext_rebuild_all (int dev, mixer_create_controls_t func, int nextra)
{
/*
 * Throw away all existing mixer controls and recreate the mixer.
 */
  if (dev < 0 || dev >= num_mixers)
    return OSS_ENXIO;
  mixer_devs[dev]->nr_ext = 0;

  mixer_devs[dev]->nr_extra_ext = nextra;
  mixer_devs[dev]->create_controls = func;

  touch_mixer (dev);
  if (mixer_devs[dev]->create_vmix_controls != NULL)
     {
        mixer_devs[dev]->create_vmix_controls(dev);
     }

  return 0;
}

int
mixer_ext_set_vmix_init_fn (int dev, mixer_create_controls_t func, int nextra,
			    void *devc)
{
/*
 * Set device dependent mixer extension initialization function and
 * reserve some extension entries for device dependent use.
 *
 * This initialization function will be called later when/if the
 * extended mixer is actually used.
 */
  if (dev < 0 || dev >= num_mixers)
    return OSS_ENXIO;

  mixer_devs[dev]->nr_extra_ext += nextra;
  mixer_devs[dev]->create_vmix_controls = func;
  mixer_devs[dev]->vmix_devc = devc;
  touch_mixer (dev);
  func (dev);
  expand_names (dev);
  return 0;
}

#ifdef VDEV_SUPPORT
static void
ainfo_combine_caps (oss_audioinfo * ainfo, adev_p adev)
{
  if (!(adev->flags & ADEV_NOOUTPUT))
    ainfo->caps |= DSP_CAP_OUTPUT;
  else
    ainfo->caps &= ~DSP_CAP_OUTPUT;

  if (!(adev->flags & ADEV_NOINPUT))
    ainfo->caps |= DSP_CAP_INPUT;
  else
    ainfo->caps &= ~DSP_CAP_INPUT;

  if (adev->flags & ADEV_DUPLEX)
    ainfo->caps |= DSP_CAP_DUPLEX;
  else
    ainfo->caps &= ~DSP_CAP_DUPLEX;

#ifdef ALLOW_BUFFER_MAPPING
  if (!(adev->flags & ADEV_NOMMAP))
    ainfo->caps |= DSP_CAP_MMAP;
#endif
}
#endif

#if 0
/*
 * Device list support is currently not used
 */
static int
check_list (oss_devlist_t * oldlist, oss_devlist_t * newlist)
{
/*
 * Check that the same devices are present in both lists. Any difference
 * indicates that the device configuration has changed (invalidates the list).
 */
#if MAX_AUDIO_DEVFILES > 64
#error Too many audio devices - fix this algorithm
#endif
  unsigned long long mask1, mask2;
  int i;

  if (newlist->ndevs != oldlist->ndevs)
    return 0;

  mask1 = 0LL;
  mask2 = 0LL;

  for (i = 0; i < oldlist->ndevs; i++)
    mask1 |= 1LL << oldlist->devices[i];
  for (i = 0; i < newlist->ndevs; i++)
    mask1 |= 1LL << newlist->devices[i];

  if (mask1 != mask2)
    return 0;

  return 1;
}
#endif

static int
get_engineinfo (int dev, oss_audioinfo * info, int combine_slaves)
{
  int dev_present = 0;
  int i;
  oss_native_word flags;

  adev_p adev, next;

  flags = 0;
  memset ((char *) info, 0, sizeof (*info));

  if (dev < 0 || dev >= num_audio_engines)
    return OSS_ENXIO;

  adev = audio_engines[dev];
  if (adev == NULL)
    {
      cmn_err (CE_WARN, "Internal error - adev==NULL (%d)\n", dev);
      return OSS_ENXIO;
    }

  if (!adev->unloaded && adev->enabled)
    dev_present = 1;

  if (dev_present)
    {
      MUTEX_ENTER_IRQDISABLE (adev->mutex, flags);
    }
  info->dev = dev;
  strcpy (info->name, adev->name);
  strcpy (info->handle, adev->handle);
  info->busy = adev->open_mode;
  info->caps = adev->caps;
  if (!(adev->flags & ADEV_NOINPUT))
    info->caps |= PCM_CAP_INPUT;
  if (!(adev->flags & ADEV_NOOUTPUT))
    info->caps |= PCM_CAP_OUTPUT;
  if (adev->flags & ADEV_SPECIAL)
    info->caps |= PCM_CAP_SPECIAL;
  if (adev->flags & ADEV_VIRTUAL)
    info->caps |= PCM_CAP_VIRTUAL;

  if (adev->flags & (ADEV_HIDDEN | ADEV_SHADOW))
    {
      info->caps |= PCM_CAP_HIDDEN;
    }

  if (adev->flags & ADEV_DUPLEX)
    {
      info->caps |= PCM_CAP_DUPLEX;
    }
  if (adev->d->adrv_trigger)	/* Supports SETTRIGGER */
    info->caps |= PCM_CAP_TRIGGER;
#ifdef ALLOW_BUFFER_MAPPING
  if (!(adev->flags & ADEV_NOMMAP))
    info->caps |= PCM_CAP_MMAP;
#endif

  info->oformats = adev->oformat_mask;
  info->iformats = adev->iformat_mask;
  info->pid = adev->pid;
  info->latency = adev->latency;
  *info->cmd = 0;
  strncpy (info->cmd, adev->cmd, sizeof (info->cmd));
  info->cmd[sizeof (info->cmd) - 1] = 0;

  strcpy (info->devnode, adev->devnode);

  if (!adev->unloaded && adev->enabled)
    {
      if (audio_engines[dev]->d->adrv_ioctl (dev, SNDCTL_GETSONG,
					     (ioctl_arg) info->song_name) ==
	  OSS_EINVAL)
	strcpy (info->song_name, adev->song_name);
      if (audio_engines[dev]->d->adrv_ioctl (dev, SNDCTL_GETLABEL,
					     (ioctl_arg) info->label) ==
	  OSS_EINVAL)
	strcpy (info->label, adev->label);
    }

  if (*info->label == 0)
    {
      strncpy (info->label, info->cmd, sizeof (info->label));
      info->label[sizeof (info->label) - 1] = 0;
    }

  info->magic = adev->magic;
  info->card_number = adev->card_number;
  info->port_number = adev->port_number;
  info->mixer_dev = adev->mixer_dev;
  info->legacy_device = adev->real_dev;
  info->rate_source = adev->rate_source;
  info->enabled = (adev->enabled && !adev->unloaded);
  info->flags = adev->flags;
  info->min_rate = adev->min_rate;
  info->max_rate = adev->max_rate;
  info->min_channels = adev->min_channels;
  info->max_channels = adev->max_channels;
  info->binding = adev->binding;
  info->nrates = adev->nrates;
  for (i = 0; i < info->nrates; i++)
    info->rates[i] = adev->rates[i];

  if (adev->next_out == NULL || !dev_present)
    info->next_play_engine = 0;
  else
    {
      info->next_play_engine = adev->next_out->engine_num;
      next = adev->next_out;

#ifdef VDEV_SUPPORT
      i = 0;
      while (combine_slaves && next != NULL && i++ < num_audio_engines)
	{
	  ainfo_combine_caps (info, next);
	  next = next->next_out;
	}
#endif
    }

  if (adev->next_in == NULL || !dev_present)
    info->next_rec_engine = 0;
  else
    {
      info->next_rec_engine = adev->next_in->engine_num;
      next = adev->next_in;

#ifdef VDEV_SUPPORT
      i=0;
      while (combine_slaves && next != NULL && i++ < num_audio_engines)
	{
	  ainfo_combine_caps (info, next);
	  next = next->next_in;
	}
#endif
    }

  if (dev_present)
    {
      MUTEX_EXIT_IRQRESTORE (adev->mutex, flags);
    }
  return 0;
}

#ifdef CONFIG_OSS_VMIX
static int
vmixctl_attach(vmixctl_attach_t *att)
{
	int err;
	oss_device_t *osdev;

	if (att->masterdev<0 || att->masterdev >= num_audio_engines)
	   return OSS_ENXIO;

	if (att->inputdev != -1)
	if (att->inputdev<0 || att->inputdev >= num_audio_engines)
	   return OSS_ENXIO;

	osdev=audio_engines[att->masterdev]->master_osdev;

	if ((err=vmix_attach_audiodev(osdev, att->masterdev, att->inputdev, att->attach_flags))<0)
	   return err;

	return 0;
}

static int
vmixctl_detach(vmixctl_attach_t *att)
{
	int err;
	oss_device_t *osdev;

	if (att->masterdev<0 || att->masterdev >= num_audio_engines)
	   return OSS_ENXIO;

	osdev=audio_engines[att->masterdev]->master_osdev;

	if ((err=vmix_detach_audiodev(att->masterdev))<0)
	   return err;

	return 0;
}

static int
vmixctl_rate(vmixctl_rate_t *rate)
{
	int err;

	if (rate->masterdev<0 || rate->masterdev >= num_audio_engines)
	   return OSS_ENXIO;

	if ((err=vmix_set_master_rate(rate->masterdev, rate->rate))<0)
	   return err;

	return 0;
}

static int
vmixctl_map_channels(vmixctl_map_t *map)
{
	int err;

	if (map->masterdev < 0 || map->masterdev >= num_audio_engines)
	   return OSS_ENXIO;

	if ((err = vmix_set_channel_map (map->masterdev, &map->map)) < 0)
	  return err;

	return 0;
}
#endif

int
oss_mixer_ext (int orig_dev, int class, unsigned int cmd, ioctl_arg arg)
{
  int val;
  int combine_slaves = 0;
#ifdef MANAGE_DEV_DSP
#ifdef VDEV_SUPPORT
  extern void oss_combine_write_lists (void);
#endif
#endif

  switch (cmd)
    {
    case SNDCTL_SYSINFO:	/* Formerly OSS_SYSINFO */
      {
	oss_sysinfo *info = (oss_sysinfo *) arg;
	int i;

	memset (info, 0, sizeof (*info));
	strcpy (info->product, "OSS");
	strncpy (info->version, OSS_VERSION_STRING, sizeof (info->version));
	info->version [sizeof (info->version) - 1] = '\0';
	strcpy (info->license, oss_license_string);
	info->versionnum = OSS_VERSION;

#ifdef OSS_HG_INFO
	/* Detailed Mercurial version */
	strncpy (info->revision_info, OSS_HG_INFO, sizeof(info->revision_info));
	info->revision_info[sizeof(info->revision_info)-1]=0;
#endif

	memset (info->options, 0, sizeof (info->options));

	info->numaudios = num_audio_devfiles;
	info->numaudioengines = num_audio_engines;
	for (i = 0; i < 8; i++)
	  info->openedaudio[i] = 0;
	for (i = 0; i < num_audio_engines; i++)
	  if (audio_engines[i] != NULL)
	    {
	      if (audio_engines[i]->flags & ADEV_OPENED)
		if (audio_engines[i]->next_out != NULL)
		  {
		    int x = audio_engines[i]->real_dev;
		    info->openedaudio[x / 32] |= 1 << (x % 32);
		  }
	    }
	for (i = 0; i < 8; i++)
	  info->openedmidi[i] = 0;
	for (i = 0; i < num_mididevs; i++)
	  if (midi_devs[i]->open_mode != 0)
	    info->openedmidi[i / 32] |= 1 << (i % 32);

	info->numsynths = 0;
#ifdef CONFIG_OSS_MIDI
	info->nummidis = num_mididevs;
#endif
	info->numtimers = oss_num_timers;
	info->nummixers = num_mixers;
	info->numcards = oss_num_cards;

	return 0;
      }
      break;

    case SNDCTL_MIX_NRMIX:
      return *arg = num_mixers;
      break;

    case SNDCTL_MIX_NREXT:	/* Return # of mixer extensions for device */
      val = *arg;
      *arg = 0;
      if (val==-1)
	 val=orig_dev;
      if (val < 0 || val >= num_mixers)
	return OSS_ENXIO;
      if (mixer_devs[val] == NULL || mixer_devs[val]->unloaded
	  || !mixer_devs[val]->enabled)
	{
	  return OSS_ENXIO;
	}


      touch_mixer (val);
      return *arg = mixer_devs[val]->nr_ext;
      break;

    case SNDCTL_MIX_EXTINFO:
      return oss_mixer_ext_info ((oss_mixext *) arg);
      break;

    case SNDCTL_MIX_ENUMINFO:
      return mixer_ext_get_enuminfo ((oss_mixer_enuminfo *) arg);
      break;

    case SNDCTL_MIX_DESCRIPTION:
      return mixer_ext_get_description ((oss_mixer_enuminfo *) arg);
      break;

    case SNDCTL_MIX_READ:
      return mixer_ext_read ((oss_mixer_value *) arg);
      break;

    case SNDCTL_MIX_WRITE:
      return mixer_ext_write ((oss_mixer_value *) arg);
      break;

    case OSS_GETVERSION:
      return *arg = OSS_VERSION;
      break;

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

	if (info == NULL)
	  return OSS_EFAULT;

	dev = info->dev;

	if (dev == -1)		/* Request for the current device */
	   {
	      oss_audio_set_error (orig_dev, E_PLAY,
				   OSSERR (1022,
					   "SNDCTL_AUDIOINFO called with dev=-1.."),
				   0);
	      /*
	       * Errordesc:
	       * Applications that try to obtain audio device information about
	       * the current device should call SNDCTL_ENGINEINFO instead of
	       * SNDCTL_AUDIOINFO.
	       *
	       * Audio file descriptors returned by open(2) are bound
	       * directly to specific audio engine instead of the
	       * device file.
	       */
	      return OSS_EINVAL;
	  }

	if (dev < 0 || dev >= num_audio_devfiles)
	  {
	    return OSS_EINVAL;
	  }

	if (audio_devfiles[dev] == NULL)
	  {
	    return OSS_EIO;
	  }

	if (dev >= 0)
	  {
	    dev = audio_devfiles[dev]->engine_num;	/* Get the engine number */
	    if (cmd == SNDCTL_AUDIOINFO && info->dev != -1)
	      combine_slaves = 1;
	  }
	return get_engineinfo (dev, info, combine_slaves);
      }
      break;

    case SNDCTL_ENGINEINFO:
      {
	int dev;
	oss_audioinfo *info = (oss_audioinfo *) arg;

	if (info == NULL)
	  return OSS_EFAULT;

	dev = info->dev;

	if (dev == -1)		/* Request for the current device */
	  switch (class)
	    {
	    case OSS_DEV_DSP:
	    case OSS_DEV_DSP_ENGINE:
	      dev = orig_dev;
	      break;

	    default:
	      cmn_err(CE_WARN, "Unrecognized device class %d for dev %d\n", class, orig_dev);
	      return OSS_EINVAL;
	    }

	if (dev < 0 || dev >= num_audio_engines)
	  {
	    return OSS_EINVAL;
	  }

	return get_engineinfo (dev, info, 0);
      }
      break;

#ifdef CONFIG_OSS_MIDI
    case SNDCTL_MIDIINFO:
      {
	int dev;
	oss_native_word flags;
	extern int oss_num_midi_clients;	/* midi.c */

	oss_midi_info *info = (oss_midi_info *) arg;
	mididev_t *mdev;

	dev = info->dev;

	if (dev == -1)		/* Request for the current device */
	  switch (class)
	    {
	    case OSS_DEV_MIDI:
	      /*
	       * Figure out the HW device connected to this client (orig_dev).
	       */
	      dev = orig_dev;
	      if (dev < 0 || dev >= oss_num_midi_clients)
		return OSS_ENXIO;
	      if (oss_midi_clients[dev]->mididev == NULL)
		return OSS_EBUSY;	/* No binding established (yet) */
	      dev = oss_midi_clients[dev]->mididev->dev;
	      break;

	    default:
	      return OSS_EINVAL;
	    }

	if (dev < 0 || dev >= num_mididevs)
	  {
	    return OSS_EINVAL;
	  }

	memset ((char *) info, 0, sizeof (*info));

	mdev = midi_devs[dev];
	MUTEX_ENTER_IRQDISABLE (mdev->mutex, flags);
	info->dev = dev;
	strcpy (info->name, mdev->name);
	strcpy (info->handle, mdev->handle);
	info->pid = mdev->pid;
	info->busy = mdev->open_mode;
	*info->cmd = 0;
	strncpy (info->cmd, mdev->cmd, sizeof (info->cmd));
	info->cmd[sizeof (info->cmd) - 1] = 0;
	info->magic = mdev->magic;
	info->card_number = mdev->card_number;
	strcpy (info->devnode, mdev->devnode);
	info->legacy_device = mdev->real_dev;
	info->port_number = mdev->port_number;
	info->enabled = mdev->enabled;
	info->flags = mdev->flags;
	info->caps = mdev->caps;
	info->latency = mdev->latency;
	if (!(info->caps & MIDI_CAP_INOUT))
	  info->caps |= MIDI_CAP_INOUT;
	if (mdev->flags & MFLAG_VIRTUAL)
	  info->caps |= MIDI_CAP_VIRTUAL;
	if (mdev->flags & MFLAG_CLIENT)
	  info->caps |= MIDI_CAP_CLIENT;
	if (mdev->flags & MFLAG_SERVER)
	  info->caps |= MIDI_CAP_SERVER;
	if (mdev->flags & MFLAG_INTERNAL)
	  info->caps |= MIDI_CAP_INTERNAL;
	if (mdev->flags & MFLAG_EXTERNAL)
	  info->caps |= MIDI_CAP_EXTERNAL;
	if (mdev->flags & MFLAG_MTC)
	  info->caps |= MIDI_CAP_MTC;
	if (midi_devs[dev]->enabled && !midi_devs[dev]->unloaded)
	  if (midi_devs[dev]->d->ioctl)
	    {
	      midi_devs[dev]->d->ioctl (dev, SNDCTL_GETSONG,
					(ioctl_arg) info->song_name);
	      midi_devs[dev]->d->ioctl (dev, SNDCTL_GETLABEL,
					(ioctl_arg) info->label);
	    }
	if (*info->label == 0)
	  {
	    strncpy (info->label, info->cmd, sizeof (info->label));
	    info->label[sizeof (info->label) - 1] = 0;
	  }
	MUTEX_EXIT_IRQRESTORE (mdev->mutex, flags);
      }
      return 0;
      break;
#endif

    case SNDCTL_CARDINFO:
      {
	int card, err;

	oss_card_info *info = (oss_card_info *) arg;
	card = info->card;

	if (card < 0 || card >= oss_num_cards)
	  {
	    return OSS_ENXIO;
	  }

	memset ((char *) info, 0, sizeof (*info));
	if ((err = oss_get_cardinfo (card, info)) < 0)
	  return err;
	info->card = card;
      }
      return 0;
      break;

    case SNDCTL_MIXERINFO:
      {
	int dev;

	oss_mixerinfo *info = (oss_mixerinfo *) arg;
	mixer_operations_t *mdev;

	dev = info->dev;

	if (dev == -1)		/* Request for the current device */
	  switch (class)
	    {
	    case OSS_DEV_MIXER:
	      dev = orig_dev;
	      break;

	    default:
	      return OSS_EINVAL;
	    }

	if (dev < 0 || dev >= num_mixers)
	  {
	    return OSS_ENXIO;
	  }

	if (mixer_devs[dev] == NULL)
	  return OSS_ENXIO;

	memset ((char *) info, 0, sizeof (*info));
	touch_mixer (dev);

	mdev = mixer_devs[dev];
	info->dev = dev;
	strncpy (info->name, mdev->name, sizeof (info->name));
	info->name[sizeof (info->name) - 1] = '\0';
	strcpy (info->id, mdev->id);
	strcpy (info->handle, mdev->handle);
	info->card_number = mdev->card_number;
	info->port_number = mdev->port_number;
	info->enabled = (mdev->enabled && !mdev->unloaded);
	info->magic = mdev->magic;
	info->caps = mdev->caps;
	info->flags = mdev->flags;
	info->modify_counter = mdev->modify_counter;
	info->nrext = mdev->nr_ext;
	info->priority = mdev->priority;
	strcpy (info->devnode, mdev->devnode);
	info->legacy_device = mdev->real_dev;
      }
      return 0;
      break;

    case OSSCTL_RENUM_AUDIODEVS:
      {
	oss_renumber_t *r = (oss_renumber_t *) arg;
	int i;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif

	if (r->n != num_audio_devfiles)	/* Wrong map size? */
	  {
	    cmn_err (CE_NOTE, "Legacy audio map size mismatch %d/%d\n",
		     r->n, num_audio_devfiles);
	    return OSS_EINVAL;
	  }

	for (i = 0; i < r->n; i++)
	  {
	    adev_p adev = audio_devfiles[i];

	    if (r->map[i] >= HARD_MAX_AUDIO_DEVFILES)	/* May be unnecessary check */
	      return OSS_EINVAL;

	    if (r->map[i] < -1)
	      r->map[i] = -1;

	    adev->real_dev = r->map[i];
	  }
      }
      return 0;
      break;

    case OSSCTL_RENUM_MIXERDEVS:
      {
	oss_renumber_t *r = (oss_renumber_t *) arg;
	int i;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif

	if (r->n != num_mixers)	/* Wrong map size? */
	  return OSS_EINVAL;

	for (i = 0; i < r->n; i++)
	  {
	    mixdev_p mdev = mixer_devs[i];

	    if (r->map[i] >= HARD_MAX_AUDIO_DEVFILES)	/* May be unnecessary check */
	      return OSS_EINVAL;

	    mdev->real_dev = r->map[i];
	  }
      }
      return 0;
      break;

    case OSSCTL_RENUM_MIDIDEVS:
      {
	oss_renumber_t *r = (oss_renumber_t *) arg;
	int i;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif

	if (r->n != num_mididevs)	/* Wrong map size? */
	  return OSS_EINVAL;

	for (i = 0; i < r->n; i++)
	  {
	    mididev_p mdev = midi_devs[i];

	    if (r->map[i] >= HARD_MAX_AUDIO_DEVFILES)	/* May be unnecessary check */
	      return OSS_EINVAL;

	    mdev->real_dev = r->map[i];
	  }
      }
      return 0;
      break;

#ifdef CONFIG_OSS_VMIX
    case VMIXCTL_ATTACH:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
      return vmixctl_attach((vmixctl_attach_t*)arg);
      break;

    case VMIXCTL_DETACH:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
      return vmixctl_detach((vmixctl_attach_t*)arg);
      break;

    case VMIXCTL_RATE:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
      return vmixctl_rate((vmixctl_rate_t*)arg);
      break;

    case VMIXCTL_REMAP:
#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
      return vmixctl_map_channels((vmixctl_map_t *)arg);
      break;

#endif

#if 0
/*
 * These calls are obsolete and disabled in current OSS version.
 */
    case OSSCTL_GET_REROUTE:
      {
	oss_reroute_t *r = (oss_reroute_t *) arg;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  {
	    return OSS_EINVAL;
	  }
#endif

	switch (r->mode)
	  {
	  case OPEN_READ:
	    memcpy (&r->devlist, &dspinlist, sizeof (oss_devlist_t));
	    break;

	  case OPEN_WRITE:
#ifdef MANAGE_DEV_DSP
#ifdef VDEV_SUPPORT
	    oss_combine_write_lists ();
#endif
#endif
	    memcpy (&r->devlist, &dspoutlist, sizeof (oss_devlist_t));
	    break;

	  case OPEN_WRITE | OPEN_READ:
	    memcpy (&r->devlist, &dspinoutlist, sizeof (oss_devlist_t));
	    break;

	  default:
	    return OSS_EINVAL;
	  }
      }
      return 0;
      break;

#if 0
    case OSSCTL_SET_REROUTE:
      {
	oss_reroute_t *r = (oss_reroute_t *) arg;
	int i, d;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif

	for (i = 0; i < r->devlist.ndevs; i++)
	  {
	    if ((d = r->devlist.devices[i]) < 0 || d >= num_audio_devfiles)
	      return OSS_EINVAL;
	  }

	switch (r->mode)
	  {
	  case OPEN_READ:
	    /* Refuse if number of devices has changed */
	    if (!check_list (&r->devlist, &dspinlist))
	      {
		return OSS_EINVAL;
	      }
	    memcpy (&dspinlist, &r->devlist, sizeof (oss_devlist_t));
	    break;

	  case OPEN_WRITE:
#ifdef MANAGE_DEV_DSP
#ifdef VDEV_SUPPORT
	    oss_combine_write_lists ();
#endif
#endif
	    /* Refuse if number of devices has changed */
	    if (!check_list (&r->devlist, &dspoutlist))
	      {
		return OSS_EINVAL;
	      }
	    memcpy (&dspoutlist, &r->devlist, sizeof (oss_devlist_t));
	    dspoutlist2.ndevs = 0;
	    break;

	  case OPEN_WRITE | OPEN_READ:
	    /* Refuse if number of devices has changed */
	    if (!check_list (&r->devlist, &dspinoutlist))
	      {
		return OSS_EINVAL;
	      }
	    memcpy (&dspinoutlist, &r->devlist, sizeof (oss_devlist_t));
	    break;

	  default:
	    return OSS_EINVAL;
	  }
      }
      return 0;
      break;
#endif

#ifdef APPLIST_SUPPORT
    case OSSCTL_RESET_APPLIST:
#ifdef GET_PROCESS_UID
      if (GET_PROCESS_UID () != 0)	/* Not root */
	return OSS_EINVAL;
#endif

      oss_applist_size = 0;
      return 0;
      break;

    case OSSCTL_ADD_APPLIST:
      {
	app_routing_t *def, *parm = (app_routing_t *) arg;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif

	if (oss_applist_size >= APPLIST_SIZE)
	  return OSS_ENOSPC;

	if (parm->dev < -1 || parm->dev >= num_audio_devfiles)
	  return OSS_ENXIO;

	def = &oss_applist[oss_applist_size];

	memset (def, 0, sizeof (*def));
	strcpy (def->name, parm->name);
	def->mode = parm->mode & (OPEN_READ | OPEN_WRITE);
	def->dev = parm->dev;
	def->open_flags = parm->open_flags;
	oss_applist_size++;
	return 0;
      }
      break;

#endif
#endif
    default:
#if 0
      if (mixer_devs[orig_dev]->d->ioctl != NULL)
         return mixer_devs[orig_dev]->d->ioctl (orig_dev, -1, cmd, arg);
#endif

      return OSS_EINVAL;
    }
}

/*ARGSUSED*/
static int
oss_mixer_open (int dev, int dev_type, struct fileinfo *file, int recursive,
		int open_flags, int *newdev)
{
/*
 * Permit opening nonexistent mixer so that certain mixer ioctl calls
 * can be called. Other code must check that the devices really exist
 * before permitting the calls.
 */
  if (dev >= 0 && dev < num_mixers)
    return 0;

  if (mixer_devs == NULL)
    return 0;

  if (mixer_devs[dev]->unloaded)
    return OSS_ENODEV;

  if (!mixer_devs[dev]->enabled)
    return OSS_ENXIO;

  return 0;
}

/*ARGSUSED*/
static void
oss_mixer_release (int dev, struct fileinfo *file)
{
}

/*ARGSUSED*/
int
oss_mixer_ioctl (int dev, struct fileinfo *bogus,
		 unsigned int cmd, ioctl_arg arg)
{
  int ret;

  if (cmd == OSS_GETVERSION)
    return *arg = OSS_VERSION;

/*
 * Handle SNDCTL_SYSINFO/CARDINFO/etc even if there are no mixer devices in the
 * system.
 */
  switch (cmd)
  {
      case OSS_GETVERSION:
      case SNDCTL_SYSINFO:
      case SNDCTL_CARDINFO:
      case SNDCTL_MIXERINFO:
      case SNDCTL_MIDIINFO:
      case SNDCTL_AUDIOINFO:
      case SNDCTL_AUDIOINFO_EX:
      case SNDCTL_ENGINEINFO:
      case OSSCTL_RENUM_AUDIODEVS:
      case OSSCTL_RENUM_MIXERDEVS:
      case OSSCTL_RENUM_MIDIDEVS:
      case SNDCTL_MIX_EXTINFO:
      case SNDCTL_MIX_ENUMINFO:
      case SNDCTL_MIX_READ:
      case SNDCTL_MIX_WRITE:
      case SNDCTL_MIX_NRMIX:
      case SNDCTL_MIX_MATRIX_WRITE:
      case SNDCTL_MIX_MATRIX_READ:
      case VMIXCTL_ATTACH:
      case VMIXCTL_DETACH:
      case VMIXCTL_RATE:
         return oss_mixer_ext (dev, OSS_DEV_MIXER, cmd, arg);
         break;
  }

  if (dev < 0 || dev >= num_mixers)
    {
      return OSS_ENXIO;
    }

  if (mixer_devs == NULL)
    {
      return OSS_ENXIO;
    }

  if (!mixer_devs[dev]->enabled || mixer_devs[dev]->unloaded)
    {
      return OSS_ENODEV;
    }

  if ((ret = oss_legacy_mixer_ioctl (dev, -1, cmd, arg)) != OSS_EINVAL)
    {
      return ret;
    }

  return oss_mixer_ext (dev, OSS_DEV_MIXER, cmd, arg);
}

#ifdef DO_TIMINGS

static char timing_buf[256 * 1024] = { 0 }, *timing_ptr = timing_buf;
static int timing_prev_time = 0;
int timing_flags = 0x7fffffff;

#define TM_SCALE 256
static oss_timing_timer_func tmfunc = NULL;
static void *tmfunc_arg = NULL;

void
timing_install_timer (oss_timing_timer_func f, void *x)
{
  tmfunc = f;
  tmfunc_arg = x;
}

static void
oss_do_timing_ (char *txt)
{
  int l = strlen (txt) + 20;
  oss_native_word flags;
  oss_native_word this_time;

  if (!timing_is_active) /* Nobody is listening */
     return;

  MUTEX_ENTER_IRQDISABLE (oss_timing_mutex, flags);
  if ((long) (&timing_buf[sizeof (timing_buf)] - timing_ptr - 8) <= l)
    {
      MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags);
      return;
    }

  if (tmfunc != NULL)
    {
      this_time = tmfunc (tmfunc_arg);
    }
  else
    {
      this_time = 0;		/* TODO: Get the actual audio pointer */

      if (this_time == 0)
	this_time = GET_JIFFIES ();
    }

  sprintf (timing_ptr, "%ld/%d: %s\n", this_time,
	   this_time - timing_prev_time, txt);
  l = strlen (timing_ptr);
  timing_ptr += l;
  timing_prev_time = this_time;
  MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags);
}

typedef struct
{
  oss_native_word sum;
  oss_native_word open_time;
}
timing_entry;

static timing_entry timing_bins[DF_NRBINS];
static oss_native_word timing_start = 0;

static char *bin_names[DF_NRBINS] = {
  "Iddle",
  "Write",
  "Read",
  "Interrupt",
  "Write sleep",
  "Read sleep",
  "Write SRC",
  "Read SRC"
};

void
timing_open (void)
{
  int i;

  if (tmfunc == NULL)
    return;
  timing_start = tmfunc (tmfunc_arg) / TM_SCALE;

  for (i = 0; i < DF_NRBINS; i++)
    {
      timing_bins[i].sum = 0;
      timing_bins[i].open_time = 0xffffffff;
    }

}

void
timing_close (void)
{
  char tmp[64];
  int i;
  oss_native_word t, sum, pc;
  if (tmfunc == NULL)
    return;

  t = tmfunc (tmfunc_arg) / TM_SCALE - timing_start;

  sprintf (tmp, "Timing close, elapsed=%d", t);
  oss_do_timing2 (DFLAG_PROFILE, tmp);

  sum = 0;

  for (i = 0; i < DF_NRBINS; i++)
    {
      sum += timing_bins[i].sum;
      pc = (timing_bins[i].sum * 1000) / t;
      sprintf (tmp, "Bin %s: %d/%d (%d.%d%%)", bin_names[i],
	       timing_bins[i].sum, pc, pc / 10, pc % 10);
      oss_do_timing2 (DFLAG_PROFILE, tmp);
    }

  /* sum = sum-timing_bins[DF_SLEEPWRITE].sum-timing_bins[DF_SLEEPREAD].sum; */
  pc = (sum * 10000) / t;

  sprintf (tmp, "OSS Total: %d (%d.%d%%)", sum, pc / 100, pc % 100);
  oss_do_timing2 (DFLAG_PROFILE, tmp);
}

void
oss_timing_enter (int bin)
{
  if (tmfunc == NULL)
    return;

  timing_bins[bin].open_time = tmfunc (tmfunc_arg) / TM_SCALE;
}

void
oss_timing_leave (int bin)
{
  oss_native_word t;

  if (tmfunc == NULL)
    return;

  if (timing_bins[bin].open_time >= 0xfffffffe)
    return;

  t = tmfunc (tmfunc_arg) / TM_SCALE - timing_bins[bin].open_time;
  timing_bins[bin].sum += t;
  timing_bins[bin].open_time = 0xfffffffe;
}

void
oss_do_timing (char *txt)
{
  if (!timing_is_active) /* Nobody is listening */
     return;

  if (timing_flags & DFLAG_ALL)
    oss_do_timing_ (txt);
}

void
oss_do_timing2 (int mask, char *txt)
{
  if (!timing_is_active) /* Nobody is listening */
     return;

  if ((timing_flags & DFLAG_ALL) || (timing_flags & mask))
    oss_do_timing_ (txt);
}

void
oss_timing_printf (char *s, ...)
{
  char tmp[1024], *a[6];
  va_list ap;
  int i, n = 0;

  if (!timing_is_active) /* Nobody is listening */
     return;

  va_start (ap, s);

  for (i = 0; i < strlen (s); i++)
    if (s[i] == '%')
      n++;

  for (i = 0; i < n && i < 6; i++)
    a[i] = va_arg (ap, char *);

  for (i = n; i < 6; i++)
    a[i] = NULL;

      sprintf (tmp, s, a[0], a[1], a[2], a[3], a[4], a[5], NULL,
	       NULL, NULL, NULL);
      oss_do_timing(tmp);

  va_end (ap);
}

static int
timing_read (int dev, struct fileinfo *file, uio_t * buf, int count)
{
  /*
   * Return at most 'count' bytes from the status_buf.
   */
  int l;
  oss_native_word flags;

  timing_is_active = 1;

  MUTEX_ENTER_IRQDISABLE (oss_timing_mutex, flags);

  l = timing_ptr - timing_buf;
  if (l <= 0)
    {
      MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags);
      return 0;
    }

  if (l > count)
    l = count;

  timing_ptr = timing_buf;

  MUTEX_EXIT_IRQRESTORE (oss_timing_mutex, flags);

  if (uiomove (timing_buf, l, UIO_READ, buf) != 0)
    cmn_err (CE_WARN, "audio: uiomove(UIO_READ) failed\n");

  return l;
}
#else
/*
 * Dummy wrappers
 */

/*ARGSUSED*/
void
oss_timing_enter (int bin)
{
}

/*ARGSUSED*/
void
oss_timing_leave (int bin)
{
}

/*ARGSUSED*/
void
oss_do_timing (char *txt)
{
}

/*ARGSUSED*/
void
oss_do_timing2 (int mask, char *txt)
{
}

/*ARGSUSED*/
void
oss_timing_printf (char *s, ...)
{
}
#endif

static oss_cdev_drv_t mixer_cdev_drv = {
  oss_mixer_open,
  oss_mixer_release,
#ifdef DO_TIMINGS
  timing_read,
#else
  NULL,				/* read */
#endif
  NULL,				/* write */
  oss_mixer_ioctl
};

int
oss_install_mixer (int vers,
		   oss_device_t * osdev,
		   oss_device_t * master_osdev,
		   const char *name,
		   mixer_driver_t * driver, int driver_size, void *devc)
{
  mixer_operations_t *op = NULL;
  mixer_driver_t *d;

  int i, num;
  char handle[32];

  if (master_osdev == NULL)
    master_osdev = osdev;

  if (mixer_devs == NULL)
    {
      mixer_devs = PMALLOC (osdev, sizeof (mixdev_p) * MAX_MIXER_DEV);
      memset (mixer_devs, 0, sizeof (mixdev_p) * MAX_MIXER_DEV);
      mixer_devs_p = mixer_devs;
    }

  if (num_mixers >= MAX_MIXER_DEV - 1)
    {
      static int nnn = 0;
      cmn_err (CE_WARN, "Too many mixer devices %d/%d (%s)\n",
	       num_mixers, MAX_MIXER_DEV, name);
      /*
       * In some special situations a driver may keep trying to install a mixer
       * in infinite loop if the request fails. Stop this by panicking after
       * this has continued for more than 50 times. In this case we can get an
       * error message instead of having the system to lock up foreever.
       */
      if (nnn++ > 50)
	cmn_err (CE_PANIC, "Killing runaway system.\n");
      return OSS_EIO;
    }

  if (vers != OSS_MIXER_DRIVER_VERSION)
    {
      cmn_err (CE_WARN, "Incompatible mixer driver for %s\n", name);
      return OSS_EIO;
    }

  if (driver_size > sizeof (mixer_driver_t))
    driver_size = sizeof (mixer_driver_t);

/*
 * Check if this device was earlier unloaded and now returning back.
 */
  num = -1;
  for (i = 0; i < num_mixers; i++)
    {
      if (mixer_devs[i]->unloaded
	  && mixer_devs[i]->os_id == oss_get_osid (osdev))
	{
	  op = mixer_devs[i];
	  num = i;
	  break;
	}
    }

  if ((d = PMALLOC (osdev, sizeof (*d))) == NULL)
    {
      cmn_err (CE_WARN, "Can't allocate mixer driver for (%s)\n", name);
      return OSS_ENOSPC;
    }

  if (num == -1)
    {
      op = PMALLOC (osdev, sizeof (mixer_operations_t));
      if (op == NULL)
	{
	  cmn_err (CE_WARN, "Can't allocate mixer driver for (%s)\n", name);
	  return OSS_ENOSPC;
	}

      memset ((char *) op, 0, sizeof (mixer_operations_t));
      num = num_mixers++;
      sprintf (handle, "%s-mx%02d", osdev->handle, osdev->num_mixerdevs+1);
      op->port_number = osdev->num_mixerdevs++;
    }
  else
    {
      strcpy (handle, op->handle);	/* Preserve the previous handle */
    }

  memset ((char *) d, 0, sizeof (mixer_driver_t));
  memcpy ((char *) d, (char *) driver, driver_size);
  strcpy (op->handle, handle);
  strcpy (op->id, osdev->nick);
  op->d = d;

  strncpy (op->name, name, sizeof (op->name));
  op->name[sizeof (op->name) - 1] = 0;
  op->devc = devc;
  op->osdev = osdev;
  op->os_id = oss_get_osid (osdev);
  op->master_osdev = master_osdev;
  op->hw_devc = NULL;
  op->max_ext = op->nr_ext = 0;
  op->names_checked = 0;
  op->extensions = NULL;
  op->timestamp = GET_JIFFIES ();
  op->ignore_mask = 0;
  op->card_number = osdev->cardnum;
  op->enabled = 1;
  op->unloaded = 0;
  op->flags = 0;
  op->caps = 0;
  op->priority = 0;		/* Normal (low) priority */
  op->real_dev = num;

  if (osdev->first_mixer == -1) /* Not defined yet */
     osdev->first_mixer = num;

  mixer_devs[num] = op;
/*
 * Create the device node
 */

  {
    oss_devnode_t name;

#ifdef NEW_DEVICE_NAMING
# ifdef USE_DEVICE_SUBDIRS
    sprintf (name, "oss/%s/mix%d", osdev->nick, osdev->num_mixerdevs - 1);
# else
    sprintf (name, "%s_mix%d", osdev->nick, osdev->num_mixerdevs - 1);
# endif
#else
    sprintf (name, "mixer%d", num);
#endif
    oss_install_chrdev (osdev, name, OSS_DEV_MIXER, num, &mixer_cdev_drv, 0);
    sprintf (op->devnode, "/dev/%s", name);

#if 0
    /*
     * Moved to install_dev_mixer()
     */
    if (num == 0)
      {
	oss_install_chrdev (osdev, "mixer", OSS_DEV_MIXER, num,
			    &mixer_cdev_drv, 0);
      }
#endif
  }

  return num;
}

void
install_dev_mixer (oss_device_t * osdev)
{
/*
 * Install the default mixer node if necessary
 */
  oss_install_chrdev (osdev, "mixer", OSS_DEV_MIXER, 0, &mixer_cdev_drv, 0);
}
