/*
 * Purpose: Operating system abstraction functions for Solaris
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#include "oss_config.h"
#include "midi_core.h"
#include <oss_pci.h>
#if !defined(SOL9) && !defined(SOL8)
#include <sys/sunldi.h>
#endif
#include <sys/mman.h>

#if 0
/* TODO: Disable this debugging stuff */
unsigned char tmp_status = 0;
# define UP_STATUS(v) OUTB(NULL, (tmp_status=tmp_status|(v)), 0x378)
# define DOWN_STATUS(v) OUTB(NULL, (tmp_status=tmp_status&~(v)), 0x378)
#else
# define UP_STATUS(v)
# define DOWN_STATUS(v)
#endif

static int oss_expired = 0;

static oss_mutex_t osscore_mutex;

/*
 * TODO: These dummy variables are collected here while MIDI/sequencer stuff
 * is not included. Remove them when enabling this stuff again.
 */
struct synth_operations *synth_devs[MAX_SYNTH_DEV + MAX_MIDI_DEV];
void
sequencer_timer (oss_uint64_t dummy)
{
}

oss_native_word
compute_finetune (oss_native_word base_freq, int bend, int range,
		  int vibrato_cents)
{
  return 0;
}

int (*softsynthp) (int cmd, int parm1, int parm2, oss_native_word parm3) =
  NULL;
int
note_to_freq (int note_num)
{
  return 0;
}


#define TRC(x)
#define TRC2(x)

#define FIX_RET_VALUE(ret) (((ret)<0) ? -(ret) : 0)

static struct fileinfo files[OSS_MAX_CDEVS];
static volatile int open_count[OSS_MAX_CDEVS] = { 0 };
static volatile int open_devices = 0;
static volatile int do_forceload = 0;
static volatile int forceload_in_progress = 0;

/*
 * MAX_CARDS must be larger than life. The system will panic if there are
 * more sound devices (cards os sound chips) than MAX_CARDS.
 */
#define MAX_CARDS 32

static oss_device_t *cards[MAX_CARDS];
int oss_num_cards = 0;

int oss_detach_enabled = 0;

/*
 * Table for permanently allocated memory (to be freed by _fini)
 */
#define OSS_MAX_MEM 	1024
void *oss_mem_blocks[OSS_MAX_MEM];
int oss_nblocks = 0;

#ifdef MUTEX_CHECKS
static volatile int inside_intr = 0;	/* For mutex debugging only */
#endif

/*
 * These are the entry points into our driver that are called when the
 * driver is loaded, during a system call, or in response to an interrupt.
 */
static int oss_getinfo (dev_info_t * dip, ddi_info_cmd_t infocmd, void *arg,
			void **result);
static int oss_identify (dev_info_t * dip);
static int oss_attach (dev_info_t * dip, ddi_attach_cmd_t cmd);
static int oss_detach (dev_info_t * dip, ddi_detach_cmd_t cmd);
static void free_all_contig_memory (void);

/*
 * DMA memory management
 */

typedef struct contig_desc
{
  int is_special;
  struct contig_desc *next;
  unsigned char *first_addr, *last_addr;
  unsigned long physaddr;
  void *orig_buf;

  oss_device_t *osdev;
  ddi_dma_handle_t handle;
  ddi_dma_handle_t dhandle;
  ddi_acc_handle_t dma_acc_handle;
  ddi_dma_cookie_t cookie;
  ddi_dma_win_t win;
  ddi_dma_seg_t seg;
  size_t size;
}
contig_desc;

static contig_desc *contig_list = NULL;

#ifdef MEMDEBUG
typedef struct
{
  void *addr;
  int size;
  char file[40];
  int line;
} mem_block_t;

#define MAX_MEMBLOCKS 1024

static mem_block_t memblocks[MAX_MEMBLOCKS];
static int num_memblocks = 0;
#endif

#if 1
/*
 * Unfortunately this handy function is not safe (incompatibilities
 * between kenel versions/builds). So let's hope it gets moved to
 * the kernel.
 */
char *
oss_get_procname (void)		/* Return the command name of the current thread */
{
  return ttoproc (curthread)->p_user.u_comm;
}
#endif

#ifdef MEMDEBUG
void *
oss_kmem_alloc (size_t size, int flags, char *file, int line)
#else
void *
oss_kmem_alloc (size_t size, int flags)
#endif
{
/*
 * This routine allocates a memory block and stores length of it in
 * the beginning. This length information can be used when later unallocating
 * the memory.
 */

  char *ptr;
  uint64_t *len;

  ptr = kmem_zalloc (size + sizeof (uint64_t), flags);

  if (ptr == NULL)
    return NULL;

  len = (uint64_t *) ptr;

  ptr += sizeof (uint64_t);
  *len = size + sizeof (uint64_t);

#ifdef MEMDEBUG
#if 1
  {
    int i;
    for (i = 0; i < num_memblocks; i++)
      if (memblocks[i].addr == NULL)
	{
	  memblocks[i].addr = ptr;
	  memblocks[i].size = size;
	  strncpy (memblocks[i].file, file, 39);
	  memblocks[i].line = line;
	  return ptr;
	}
  }
#endif

  if (num_memblocks < MAX_MEMBLOCKS)
    {
      memblocks[num_memblocks].addr = ptr;
      memblocks[num_memblocks].size = size;
      strncpy (memblocks[num_memblocks].file, file, 39);
      memblocks[num_memblocks].line = line;
      num_memblocks++;
    }
#endif

  return ptr;
}

void
oss_kmem_free (void *addr)
{
  uint64_t *len;
  int i;

  char *ptr = addr;

  if (addr == NULL)
    return;

  ptr -= sizeof (uint64_t);

  len = (uint64_t *) ptr;

  kmem_free (ptr, *len);
#ifdef MEMDEBUG
  {
    int i;

    for (i = 0; i < num_memblocks; i++)
      if (addr == memblocks[i].addr)
	{
	  memblocks[i].addr = NULL;
	  break;
	}
  }
#endif
}

static struct os_dma_params dma_parms[8] = { {0} };

static const ddi_dma_attr_t dma_attr_pci = {
  DMA_ATTR_V0,
  0x00000000,
  0xfffffff0,
  0xffffff,
  1,
  0x7,
  0x1,
  0xffffffff,
  0x0fffffff,
  1,
  1,
  0
};

#if !defined(SOL9) && !defined(SOL8)
static void
forceload_drivers (dev_t dev, cred_t * credp)
{
/*
 * This routine will be called whenever the first application tries
 * to access OSS devices. It's purpose is to load all the drivers
 * just in case they have not been loaded yet. In this way it's possible to
 * guarantee that the audio device numbering doesn't change between 
 * reboots.
 */
  char path[20];

  int i, err;
  ldi_handle_t lh;
  ldi_ident_t li;
  DDB (cmn_err (CE_NOTE, "Forceloading OSS devices\n"));

  if (ldi_ident_from_dev (dev, &li) != 0)
    {
      cmn_err (CE_NOTE, "ldi_ident_from_dev failed\n");
      return;
    }

  for (i = 0; i < MAX_PCM_DEV; i++)
    {
      sprintf (path, "/dev/dsp%d", i);

      if ((err = ldi_open_by_name (path, FWRITE, credp, &lh, li)) != 0)
	{
	  if (err != ENODEV)
	    cmn_err (CE_NOTE, "Forceload error %d\n", err);
/*                      break; */
	}
      else
	ldi_close (lh, FWRITE, credp);
    }

  ldi_ident_release (li);
  DDB (cmn_err (CE_NOTE, "Forceloading OSS devices done\n"));
}

void
oss_forceload_drivers (int dev, cred_t * cred_p)
{

  if (do_forceload)
    {
      do_forceload = 0;
      forceload_in_progress = 1;
      forceload_drivers (dev, cred_p);
      forceload_in_progress = 0;
    }
}
#endif

int
oss_open (dev_t * dev_p, int open_flags, int otyp, cred_t * cred_p)
{
  int retval, dev_class;
  dev_t dev = *dev_p;
  oss_cdev_t *cdev;
  int tmpdev, maj;
  oss_native_word flags;

  struct fileinfo tmp_file;
#ifdef DO_TIMINGS
  char tmp[100];
  sprintf (tmp, "**** oss_open(%x) ****", getminor (dev));
  oss_do_timing (tmp);
#endif
//cmn_err(CE_CONT, "**** oss_open(%x) ****\n", getminor (dev));
//cmn_err(CE_CONT, "  PID %d, cmd %s\n", GET_PROCESS_PID(x), GET_PROCESS_NAME(x));
  maj = getmajor (dev);
  dev = getminor (dev);

#if !defined(SOL9) && !defined(SOL8)
/*
 * Handle driver forceload
 */

  if (forceload_in_progress)
    return ENODEV;

  oss_forceload_drivers (dev, cred_p);
#endif

  if (dev >= OSS_MAX_CDEVS)
    return ENXIO;

  if (dev >= oss_num_cdevs)
    {
      return ENODEV;
    }

  if ((cdev = oss_cdevs[dev]) == NULL || cdev->d == NULL)
    return ENODEV;

  if (cdev->d->open == NULL)
    {
      return ENODEV;
    }

  dev_class = cdev->dev_class;

  memset (&tmp_file, 0, sizeof (tmp_file));
  tmp_file.mode = 0;
  tmp_file.flags = open_flags;
  tmp_file.acc_flags = open_flags;

  if (open_flags & FREAD && open_flags & FWRITE)
    tmp_file.mode = OPEN_READWRITE;
  else if (open_flags & FREAD)
    tmp_file.mode = OPEN_READ;
  else if (open_flags & FWRITE)
    tmp_file.mode = OPEN_WRITE;

  tmpdev = dev;
  retval =
    cdev->d->open (cdev->instance, cdev->dev_class, &tmp_file, 0, 0, &tmpdev);
  *dev_p = makedevice (maj, tmpdev);
  dev = tmpdev;

  if (retval < 0)
    {
      return -retval;
    }

  memcpy ((char *) &files[dev], (char *) &tmp_file, sizeof (tmp_file));

  MUTEX_ENTER_IRQDISABLE (osscore_mutex, flags);
  open_devices++;
//cmn_err(CE_CONT, "Increment open_devices=%d\n", open_devices);
  open_count[dev]++;
  MUTEX_EXIT_IRQRESTORE (osscore_mutex, flags);

  return 0;
}

int
oss_close (dev_t dev, int flag, int otyp, cred_t * cred_p)
{
  oss_cdev_t *cdev;
  struct fileinfo tmp_file;
#ifdef DO_TIMINGS
  char tmp[100];
  sprintf (tmp, "***** oss_close(%x) ****", getminor (dev));
  oss_do_timing (tmp);
#endif
//cmn_err(CE_CONT, "***** oss_close(%x) ****\n", getminor (dev));
//cmn_err(CE_CONT, "    PID %d, cmd %s\n", GET_PROCESS_PID(x), GET_PROCESS_NAME(x));

  if (getminor (dev) >= OSS_MAX_CDEVS)
    {
      cmn_err (CE_NOTE, "Closing bad minor %d\n", getminor (dev));
      return ENXIO;
    }

  if (open_count[getminor (dev)] == 0)	/* Not opened */
    {
      cmn_err (CE_NOTE, "Closing minor %d that is not open\n",
	       getminor (dev));
      return 0;
    }

  dev = getminor (dev);
  if ((cdev = oss_cdevs[dev]) == NULL)
    {
      cmn_err (CE_NOTE, "Closing undefined minor %d\n", getminor (dev));
      return ENXIO;
    }

  cdev->d->close (cdev->instance, &files[dev]);

  MUTEX_ENTER_IRQDISABLE (osscore_mutex, flags);
  open_devices--;
//cmn_err(CE_CONT, "Decrement open_devices=%d\n", open_devices);
  open_count[dev]--;
  MUTEX_EXIT_IRQRESTORE (osscore_mutex, flags);
  return 0;
}

int
oss_ioctl (dev_t dev, int cmd, intptr_t arg, int mode, cred_t * cred_p,
	   int *rval_p)
{
  int retval;
  int len = 0;
  char buf[4096];
  oss_cdev_t *cdev;
  int alloced = 0;
#ifdef DO_TIMINGS
  char tmp[100];
  sprintf (tmp, "OSS ioctl(%x, %x, %x)", getminor (dev), cmd, arg);
  oss_do_timing (tmp);
#endif

  dev = getminor (dev);
  if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->ioctl == NULL)
    return *rval_p = ENXIO;

  if (oss_expired)
    return *rval_p = ENODEV;

  if (cmd & (SIOC_OUT | SIOC_IN))
    {

      len = (cmd >> 16) & SIOCPARM_MASK;
      if (len < 0)
	len = 0;
      if (len > sizeof (buf))
	{
	  cmn_err (CE_WARN, "Bad ioctl buffer size %d\n", len);
	  return *rval_p = EFAULT;
	}

      if ((cmd & SIOC_IN) && len > 0)
	{
	  if (copyin ((char *) arg, buf, len) == -1)
	    return *rval_p = EFAULT;
	}

    }

  retval = cdev->d->ioctl (cdev->instance, &files[dev], cmd, (ioctl_arg) buf);

  if ((cmd & SIOC_OUT) && len > 0)
    {
      if (copyout (buf, (char *) arg, len) == -1)
	return *rval_p = EFAULT;
    }

  *rval_p = FIX_RET_VALUE (retval);

  return *rval_p;
}

int
oss_read (dev_t dev, struct uio *uiop, cred_t * credp)
{
  int count = uiop->uio_resid;
  int retval;
  oss_cdev_t *cdev;

  dev = getminor (dev);
  if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->read == NULL)
    return ENXIO;
  files[dev].acc_flags = uiop->uio_fmode;

  retval = cdev->d->read (cdev->instance, &files[dev], uiop, count);

  return FIX_RET_VALUE (retval);
}

int
oss_write (dev_t dev, struct uio *uiop, cred_t * cred_p)
{
  int count = uiop->uio_resid;
  int retval;
  int dev_class;
  oss_cdev_t *cdev;

  dev = getminor (dev);
  if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->write == NULL)
    return ENXIO;
  files[dev].acc_flags = uiop->uio_fmode;

  retval = cdev->d->write (cdev->instance, &files[dev], uiop, count);
  return FIX_RET_VALUE (retval);
}

int
oss_chpoll (dev_t dev, short events, int anyyet, short *reventsp,
	    struct pollhead **phpp)
{
  oss_cdev_t *cdev;
  oss_poll_event_t ev;
  int ret;

#ifdef DO_TIMINGS
  char tmp[100];
  sprintf (tmp, "***** oss_chpoll(%x) ****", getminor (dev));
  oss_do_timing (tmp);
#endif

  if (getminor (dev) >= OSS_MAX_CDEVS)
    return ENXIO;

  if (open_count[getminor (dev)] == 0)	/* Not opened */
    return 0;

  dev = getminor (dev);
  if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->chpoll == NULL)
    return ENXIO;

  *reventsp = 0;
  ev.events = events;
  ev.anyyet = anyyet;
  ev.revents = 0;
  ev.php = NULL;

  ret = cdev->d->chpoll (cdev->instance, &files[dev], &ev);
  if (ret < 0)
    {
      return -ret;
    }

  *reventsp |= ev.revents;
  if (ev.php != NULL)
    *phpp = ev.php;

  return 0;
}

#ifdef ALLOW_BUFFER_MAPPING
int
oss_devmap (dev_t dev, devmap_cookie_t handle, offset_t off, size_t len,
	    size_t * maplen, uint_t model)
{
  oss_cdev_t *cdev;
  oss_poll_event_t ev;
  int ret;

#ifdef DO_TIMINGS
  char tmp[100];
  sprintf (tmp, "***** oss_devmap(%x) ****", getminor (dev));
  oss_do_timing (tmp);
#endif

  if (getminor (dev) >= OSS_MAX_CDEVS)
    {
      return ENXIO;
    }

  if (open_count[getminor (dev)] == 0)	/* Not opened */
    {
      return EPERM;
    }

  dev = getminor (dev);
  if ((cdev = oss_cdevs[dev]) == NULL)
    {
      return ENXIO;
    }

  if (cdev->dev_class != OSS_DEV_DSP && cdev->dev_class != OSS_DEV_DSP_ENGINE)	/* Only audio devices can be mapped */
    {
      return ENXIO;
    }

  dev = cdev->instance;
  if (dev < 0 || dev >= num_audio_engines)
    return ENXIO;

  return ENOTSUP;
}
#endif

/*
 * When our driver is loaded or unloaded, the system calls our _init or
 * _fini routine with a modlinkage structure.  The modlinkage structure
 * contains:
 *
 *	modlinkage->
 *		modldrv->
 *			dev_ops->
 * 				cb_ops
 *
 * cb_ops contains the normal driver entry points and is roughly equivalent
 * to the cdevsw & bdevsw structures in previous releases.
 *
 * dev_ops contains, in addition to the pointer to cb_ops, the routines
 * that support loading and unloading our driver.
 */
static struct cb_ops oss_cb_ops = {
  oss_open,
  oss_close,
  nodev,			/* not a block driver   */
  nodev,			/* no print routine     */
  nodev,			/* no dump routine      */
  oss_read,
  oss_write,
  oss_ioctl,
#ifdef ALLOW_BUFFER_MAPPING
  oss_devmap,
#else
  nodev,			/* no devmap routine    */
#endif
  nodev,
  nodev,			/* no segmap routine    */
  oss_chpoll,			/* no chpoll routine    */
  ddi_prop_op,
  0,				/* not a STREAMS driver */
  D_NEW | D_MP | D_64BIT,	/* safe for multi-thread/multi-processor */
  CB_REV
};

static struct dev_ops oss_ops = {
  DEVO_REV,			/* DEVO_REV indicated by manual */
  0,				/* device reference count       */
  oss_getinfo,
  nulldev,
  nulldev,
  oss_attach,
  oss_detach,
  nodev,			/* device reset routine         */
  &oss_cb_ops,
  NULL,				/* bus operations               */
  NULL				/* TODO: Power management */
};

extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
  &mod_driverops,
  "OSS/Solaris " OSS_VERSION_STRING,
  &oss_ops,
};

static struct modlinkage modlinkage = {
  MODREV_1,			/* MODREV_1 indicated by manual */
  {(void *) &modldrv,
   NULL}			/* termination of list of linkage structures */
};

static ddi_device_acc_attr_t acc_attr_neverswap = {
  DDI_DEVICE_ATTR_V0,
  DDI_NEVERSWAP_ACC,
  DDI_STRICTORDER_ACC
};

#ifdef OSS_BIG_ENDIAN
static ddi_device_acc_attr_t acc_attr_le_swap = {
  DDI_DEVICE_ATTR_V0,
  DDI_STRUCTURE_LE_ACC,
  DDI_STRICTORDER_ACC
};
#endif


static ddi_device_acc_attr_t *
get_acc_attr (oss_device_t * osdev)
{
#ifdef OSS_BIG_ENDIAN
  if (osdev->swap_mode == 1)
    {
      return &acc_attr_le_swap;
    }
  else
#endif
    {
      return &acc_attr_neverswap;
    }
}

void
oss_load_options (oss_device_t * osdev, oss_option_map_t map[])
{
  int i, val;

  for (i = 0; map[i].name != NULL; i++)
    {
      if ((val =
	   ddi_prop_get_int (DDI_DEV_T_ANY, osdev->dip,
			     DDI_PROP_NOTPROM,
			     map[i].name, -12345)) != -12345)
	{
	  *map[i].ptr = val;
	}
    }
}

static int
oss_attach (dev_info_t * dip, ddi_attach_cmd_t cmd)
{
  oss_device_t *osdev;

#ifdef LICENSED_VERSION
  timestruc_t ts;

  gethrestime (&ts);
  if (!oss_license_handle_time (ts.tv_sec))
    {
      cmn_err (CE_WARN, "This version of Open Sound System has expired\n");
      cmn_err (CE_CONT,
	       "Please download the latest version from www.opensound.com\n");
      oss_expired = 1;
    }
#endif

  if (cmd != DDI_ATTACH)
    {
      cmn_err (CE_WARN, "oss_attach: Command %x\n", cmd);
      return (DDI_FAILURE);
    }

  if ((osdev = osdev_create (dip, DRV_VIRTUAL, 0, "oss", NULL)) == NULL)
    {
      cmn_err (CE_WARN, "Creating osdev failed\n");
      return DDI_FAILURE;
    }

  MUTEX_INIT (osdev, osscore_mutex);

  oss_load_options (osdev, oss_global_options);

  oss_common_init (osdev);

  ddi_report_dev (dip);
  oss_register_device (osdev, "OSS core services");

  return (DDI_SUCCESS);
}

/*
 * This is a pretty generic getinfo routine as describe in the manual.
 */

 /*ARGSUSED*/ static int
oss_getinfo (dev_info_t * dip, ddi_info_cmd_t infocmd, void *arg,
	     void **result)
{
  dev_t dev;
  register int error;
  int minor_num, instance;

  dev = (dev_t) arg;
  minor_num = getminor (dev) & 0xF;
  instance = getminor (dev) >> 4;

  switch (infocmd)
    {
    case DDI_INFO_DEVT2DEVINFO:
      *result = dip;
      error = DDI_SUCCESS;
      break;
    case DDI_INFO_DEVT2INSTANCE:
      *result = (void *) instance;
      error = DDI_SUCCESS;
      break;
    default:
      *result = NULL;
      error = DDI_FAILURE;
    }

  DEB (cmn_err (CE_CONT,
		"oss_getinfo: returns %d, result=%x minor=%d instance=%d dev=%x\n",
		error, *result, minor_num, instance, dev));
  return (error);
}

/*
 * _init, _info, and _fini support loading and unloading the driver.
 */
int
_init (void)
{
  register int error;
  error = mod_install (&modlinkage);
  return error;
}

int
_info (struct modinfo *modinfop)
{
  return (mod_info (&modlinkage, modinfop));
}

int
_fini (void)
{
  int status;
  int i;

  if ((status = mod_remove (&modlinkage)) != 0)
    return (status);

  free_all_contig_memory ();

  if (oss_nblocks >= OSS_MAX_MEM)
    cmn_err (CE_WARN, "All memory cannod be freed\n");
  for (i = 0; i < oss_nblocks; i++)
    KERNEL_FREE (oss_mem_blocks[i]);

#ifdef MEMDEBUG
  if (num_memblocks >= MAX_MEMBLOCKS)
    cmn_err (CE_NOTE, "All memory allocations were not checked\n");

  for (i = 0; i < num_memblocks; i++)
    {
      if (memblocks[i].addr != NULL)
	{
	  cmn_err (CE_NOTE, "Memory leak in %s:%d\n",
		   memblocks[i].file, memblocks[i].line);
	}
    }
#endif

  return status;
}

/*
 * When our driver is unloaded, oss_detach cleans up and frees the resources
 * we allocated in oss_attach.
 */
static int
oss_detach (dev_info_t * dip, ddi_detach_cmd_t cmd)
{
  int instance;
  static int already_unloaded = 0;

  if (cmd != DDI_DETACH)
    return DDI_FAILURE;

  if (already_unloaded)
    return DDI_SUCCESS;
  already_unloaded = 1;

  instance = ddi_get_instance (dip);

  /*
   * Remove the minor nodes created in attach
   */
  ddi_remove_minor_node (dip, NULL);

  oss_unload_drivers ();

  MUTEX_CLEANUP (osscore_mutex);

  return (DDI_SUCCESS);
}

/*
 *	Some support routines
 */
void *
memset (void *t, int c, size_t l)
{
  int i;

  for (i = 0; i < l; i++)
    ((char *) t)[i] = c;

  return t;
}

#ifdef sparc
/*
 * I/O functions that do byte swapping (for Sparc)
 */
void
oss_put16 (ddi_acc_handle_t handle, unsigned short *addr, unsigned short val)
{
  val = ((val >> 8) & 0xff) | ((val & 0xff) << 8);
  ddi_put16 (handle, addr, val);
}

void
oss_put32 (ddi_acc_handle_t handle, unsigned int *addr, unsigned int val)
{
#ifdef OSS_BIG_ENDIAN
  val = ((val & 0x000000ff) << 24) |
    ((val & 0x0000ff00) << 8) |
    ((val & 0x00ff0000) >> 8) | ((val & 0xff000000) >> 24);
#endif
  ddi_put32 (handle, addr, val);
}

unsigned short
oss_get16 (ddi_acc_handle_t handle, unsigned short *addr)
{
  unsigned short val;

  val = ddi_get16 (handle, addr);
#ifdef OSS_BIG_ENDIAN
  val = ((val >> 8) & 0xff) | ((val & 0xff) << 8);
#endif
  return val;
}

unsigned int
oss_get32 (ddi_acc_handle_t handle, unsigned int *addr)
{
  unsigned int val;

  val = ddi_get32 (handle, addr);
#ifdef OSS_BIG_ENDIAN
  val = ((val & 0x000000ff) << 24) |
    ((val & 0x0000ff00) << 8) |
    ((val & 0x00ff0000) >> 8) | ((val & 0xff000000) >> 24);
#endif
  return val;
}

uint16_t
oss_mem_get16 (ddi_acc_handle_t handle, uint16_t * addr)
{
  uint16_t val;

  val = ddi_mem_get16 (handle, addr);
#ifdef OSS_BIG_ENDIAN
  val = ((val >> 8) & 0xff) | ((val & 0xff) << 8);
#endif
  return val;
}

uint32_t
oss_mem_get32 (ddi_acc_handle_t handle, uint32_t * addr)
{

  uint32_t val;

  val = ddi_mem_get32 (handle, addr);
#ifdef OSS_BIG_ENDIAN
  val = ((val & 0x000000ff) << 24) |
    ((val & 0x0000ff00) << 8) |
    ((val & 0x00ff0000) >> 8) | ((val & 0xff000000) >> 24);
#endif
  return val;
}

void
oss_mem_put16 (ddi_acc_handle_t handle, uint16_t * addr, uint16_t val)
{
#ifdef OSS_BIG_ENDIAN
  val = ((val >> 8) & 0xff) | ((val & 0xff) << 8);
#endif

  ddi_mem_put16 (handle, addr, val);
}

void
oss_mem_put32 (ddi_acc_handle_t handle, uint32_t * addr, uint32_t val)
{
#ifdef OSS_BIG_ENDIAN
  val = ((val & 0x000000ff) << 24) |
    ((val & 0x0000ff00) << 8) |
    ((val & 0x00ff0000) >> 8) | ((val & 0xff000000) >> 24);
#endif
  ddi_mem_put32 (handle, addr, val);
}
#endif

void
oss_pci_byteswap (oss_device_t * osdev, int mode)
{
  osdev->swap_mode = mode;
}

caddr_t
oss_map_pci_ioaddr (oss_device_t * osdev, int nr, int io)
{
  caddr_t addr;
  off_t region_size;
  int i, n, err;
  ddi_device_acc_attr_t *acc_attr;

  acc_attr = get_acc_attr (osdev);

  ddi_dev_regsize (osdev->dip, nr + 1, &region_size);

  if ((err = ddi_regs_map_setup
       (osdev->dip, nr + 1, &addr, 0, region_size, acc_attr,
	&osdev->acc_handle)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "Register setup failed (%d)\n", err);
      return 0;
    }

  return addr;
}

int
pci_read_config_byte (oss_device_t * osdev, offset_t where,
		      unsigned char *val)
{
  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
#if defined (sparc)
  if (where == PCI_INTERRUPT_LINE)
    *val = 7;			/* PC emulation hack */
  else
#endif
    *val = pci_config_get8 (osdev->pci_config_handle, where);
  return PCIBIOS_SUCCESSFUL;
}

int
pci_read_config_irq (oss_device_t * osdev, offset_t where, unsigned char *val)
{
  int ret;

  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
  ret = pci_read_config_byte (osdev, where, val);
  return ret;
}


int
pci_read_config_word (oss_device_t * osdev, offset_t where,
		      unsigned short *val)
{
  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
  *val = pci_config_get16 (osdev->pci_config_handle, where);
  return PCIBIOS_SUCCESSFUL;
}

int
pci_read_config_dword (oss_device_t * osdev, offset_t where,
		       unsigned int *val)
{
  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
  *val = pci_config_get32 (osdev->pci_config_handle, where);
  return PCIBIOS_SUCCESSFUL;
}

int
pci_write_config_byte (oss_device_t * osdev, offset_t where,
		       unsigned char val)
{
  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
  pci_config_put8 (osdev->pci_config_handle, where, val);
  return PCIBIOS_SUCCESSFUL;
}

int
pci_write_config_word (oss_device_t * osdev, offset_t where,
		       unsigned short val)
{
  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
  pci_config_put16 (osdev->pci_config_handle, where, val);
  return PCIBIOS_SUCCESSFUL;
}

int
pci_write_config_dword (oss_device_t * osdev, offset_t where,
			unsigned int val)
{
  if (osdev->dev_type != DRV_PCI)
    return PCIBIOS_FAILED;
  pci_config_put32 (osdev->pci_config_handle, where, val);
  return PCIBIOS_SUCCESSFUL;
}

void *
memcpy (void *t, const void *s, size_t l)
{
  bcopy (s, t, l);
  return t;
}

void *
oss_pmalloc (size_t size)
{
  void *mem_ptr;
  mem_ptr = (oss_mem_blocks[oss_nblocks] = KERNEL_MALLOC (size));

  if (oss_nblocks <= OSS_MAX_MEM)
    oss_nblocks++;
  return mem_ptr;
}

#ifdef MEMDEBUG
void *
oss_contig_malloc (oss_device_t * osdev, int size, oss_uint64_t memlimit,
		   oss_native_word * phaddr, char *file, int line)
#else
void *
oss_contig_malloc (oss_device_t * osdev, int size, oss_uint64_t memlimit,
		   oss_native_word * phaddr)
#endif
{
/*
 * Allocate physically contiguous memory (suitable for DMA).
 *
 * The memlimit parameter is equal to oss_alloc_dmabuf().
 */
  int err;
  size_t len;
  uint_t count;
  int flags;
  contig_desc *desc;
  ddi_dma_attr_t dma_attr;
  ddi_device_acc_attr_t *acc_attr;

  if (osdev == NULL)
    {
      cmn_err (CE_WARN, "oss_contig_malloc: osdev==NULL\n");
      return NULL;
    }

  acc_attr = get_acc_attr (osdev);

  memcpy (&dma_attr, &dma_attr_pci, sizeof (ddi_dma_attr_t));
  dma_attr.dma_attr_addr_hi = memlimit;

  flags = DDI_DMA_CONSISTENT;

  desc = KERNEL_MALLOC (sizeof (contig_desc));
  if (desc == NULL)
    {
      cmn_err (CE_WARN, "Failed to allocate contig buffer descriptor\n");
      return NULL;
    }

  desc->osdev = osdev;
  desc->next = NULL;
  desc->is_special = 0;

#if 1
  if ((err = ddi_dma_alloc_handle (osdev->dip,
				   &dma_attr,
				   DDI_DMA_SLEEP,
				   NULL, &desc->dhandle)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "Failed to allocate pci DMA handle (%d)\n", err);
      return NULL;
    }
#endif

  if ((err = ddi_dma_mem_alloc (desc->dhandle,
				size + 4096,
				acc_attr,
				flags,
				DDI_DMA_SLEEP,
				NULL,
				(caddr_t *) & desc->first_addr,
				&len, &desc->dma_acc_handle)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "Failed to allocate %d bytes of contig memory (%d)\n",
	       size, err);
      return NULL;

    }

  desc->size = len;
  desc->orig_buf = desc->first_addr;

  if (desc->first_addr == NULL)
    {
      cmn_err (CE_WARN, "Can't allocate a contig buffer\n");
      return NULL;
    }

  DEB (cmn_err
       (CE_CONT, "Allocated contig memory, addr=%x, len=%d\n",
	(int) desc->first_addr, desc->size));

  if ((err = ddi_dma_addr_bind_handle (desc->dhandle,
				       NULL,
				       (char *) desc->first_addr,
				       desc->size,
				       flags | DDI_DMA_STREAMING,
				       DDI_DMA_DONTWAIT,
				       NULL,
				       &desc->cookie, &count)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "Contig address setup failed (%d)\n", err);
      return NULL;
    }

  desc->physaddr = desc->cookie.dmac_address;

  desc->last_addr = desc->first_addr + desc->size - 1;
  desc->first_addr =
    (void *) (((unsigned long) desc->first_addr + 4095) & ~4095);
  desc->physaddr = (desc->physaddr + 4095) & ~4095;
  *phaddr = desc->physaddr;

  desc->next = contig_list;
  contig_list = desc;
/* HW_PRINTF(("Alloc contig: %x-%x, ph=%x\n", desc->first_addr, desc->last_addr, desc->physaddr)); */

  return desc->first_addr;
}

void
oss_contig_free (oss_device_t * osdev, void *p, int sz)
{
  int err;
  contig_desc *d, *desc = NULL, *prev = NULL;

  if (p == NULL)
    return;

  d = contig_list;

  while (d && desc == NULL)
    {
      if (d->is_special)
	{
	  prev = d;
	  d = d->next;
	  continue;
	}

      if (d->first_addr == p)
	{
	  desc = d;
	  break;
	}

      prev = d;
      d = d->next;
    }

  if (desc == NULL)
    {
      cmn_err (CE_WARN, "OSS: Can't free memory\n");
      return;
    }

#if 1
  if ((err = ddi_dma_unbind_handle (desc->dhandle)) != DDI_SUCCESS)
    cmn_err (CE_WARN, "Failed to free DMA handle (%d)\n", err);
#endif

  if (desc->dma_acc_handle == NULL)
    cmn_err (CE_WARN, "desc->dma_acc_handle==NULL\n");
  else
    ddi_dma_mem_free (&desc->dma_acc_handle);
#if 1
  ddi_dma_free_handle (&desc->dhandle);
#endif

  if (desc == contig_list)
    contig_list = desc->next;
  else
    {
      prev->next = desc->next;
    }
  KERNEL_FREE (desc);

}

oss_native_word
oss_virt_to_bus (void *addr)
{
  contig_desc *desc = contig_list;
  unsigned long p;

  if (addr == NULL)
    return 0;

  while (desc &&
	 ((oss_native_word) addr < (oss_native_word) desc->first_addr ||
	  (oss_native_word) addr >= (oss_native_word) desc->last_addr))
    desc = desc->next;

  if (desc == NULL)
    {
      cmn_err (CE_WARN, "Virtual address %x not mapped\n", (int) addr);
      return 0;
    }

  p = (oss_native_word) addr - (oss_native_word) desc->first_addr;	/* Offset from the beginning */


  return desc->physaddr + p;
}

static void
free_all_contig_memory (void)
{
  contig_desc *p, *desc = contig_list;

  while (desc != NULL)
    {
      p = desc;
      desc = desc->next;

      if (p->is_special)
	continue;

      oss_contig_free (p->osdev, p->orig_buf, 0);
    }

  contig_list = NULL;
}

oss_device_t *
osdev_create (dev_info_t * dip, int dev_type, int instance, const char *nick,
	      const char *handle)
{
  oss_device_t *osdev = NULL;
  int i, err;
  caddr_t addr;
  off_t region_size;

  if (handle == NULL)
    handle = nick;

  /*
   * Don't accept any more drivers if expired
   */
  if (oss_expired && oss_num_cards > 0)
    return NULL;

  for (i = 0; i < oss_num_cards; i++)
    {
      if (cards[i]->dip == dip)
	{
	  osdev = cards[i];
	  break;
	}
    }

  if (osdev == NULL)
    {
      if (oss_num_cards >= MAX_CARDS)
	cmn_err (CE_PANIC, "Too many OSS devices. At most %d permitted.\n",
		 MAX_CARDS);
      if ((osdev = PMALLOC (NULL, sizeof (*osdev))) == NULL)
	{
	  cmn_err (CE_WARN, "osdev_create: Out of memory\n");
	  return NULL;
	}
      memset (osdev, 0, sizeof (*osdev));

      osdev->cardnum = oss_num_cards;
      cards[oss_num_cards++] = osdev;
    }

  osdev->dip = dip;
  osdev->osid = dip;
  osdev->instance = instance;
  osdev->dev_type = dev_type;
  osdev->devc = NULL;
  sprintf (osdev->nick, "%s%d", nick, instance);
  strcpy (osdev->modname, nick);

  switch (dev_type)
    {
    case DRV_PCI:
      pci_config_setup (dip, &osdev->pci_config_handle);
      break;

    case DRV_VIRTUAL:
    case DRV_STREAMS:
      /* NOP */
      break;

    case DRV_USB:
      /* NOP */
      break;

    default:
      cmn_err (CE_WARN, "Bad device type\n");
      return NULL;
    }

/*
 * Create the device handle
 */
  switch (dev_type)
    {
    case DRV_PCI:
      {
	unsigned int subvendor;
	pci_read_config_dword (osdev, 0x2c, &subvendor);

	sprintf (osdev->handle, "PCI%08x-%d", subvendor, instance);
      }
      break;

    case DRV_USB:
      /* TODO: Get the vendor information */
      sprintf (osdev->handle, "USB-%s%d", handle, instance);
      break;

    default:
      sprintf (osdev->handle, "%s%d", handle, instance);
    }

  return osdev;
}

oss_device_t *
osdev_clone (oss_device_t * orig_osdev, int new_instance)
{
  oss_device_t *osdev;

  osdev = PMALLOC (NULL, sizeof (*osdev));
  if (osdev == NULL)
    {
      cmn_err (CE_WARN, "osdev_create: Out of memory\n");
      return NULL;
    }
  memcpy (osdev, orig_osdev, sizeof (*osdev));
  osdev->dev_type = DRV_CLONE;
  osdev->instance = new_instance;
  sprintf (osdev->nick, "%s%d", orig_osdev->modname, new_instance);
  sprintf (osdev->handle, "%s%d", orig_osdev->modname, new_instance);

  return osdev;
}

void
osdev_delete (oss_device_t * osdev)
{
  int i;

  if (osdev == NULL)
    return;

  switch (osdev->dev_type)
    {
    case DRV_PCI:
      pci_config_teardown (&osdev->pci_config_handle);
      osdev->pci_config_handle = NULL;
      break;
    }
  ddi_remove_minor_node (osdev->dip, NULL);

/*
 * Mark all minor nodes for this module as invalid.
 */
  for (i = 0; i < oss_num_cdevs; i++)
    if (oss_cdevs[i]->osdev == osdev)
      {
	oss_cdevs[i]->d = NULL;
	oss_cdevs[i]->osdev = NULL;
	strcpy (oss_cdevs[i]->name, "Removed device");
      }
}

int
oss_get_cardinfo (int cardnum, oss_card_info * ci)
{
/*
 * Print information about a 'card' in a format suitable for /dev/sndstat
 */

  if (cardnum < 0 || cardnum >= oss_num_cards)
    return -ENXIO;

  if (cards[cardnum]->name != NULL)
    strncpy (ci->longname, cards[cardnum]->name, 128);
  ci->shortname[127] = 0;

  if (cards[cardnum]->nick != NULL)
    strncpy (ci->shortname, cards[cardnum]->nick, 16);
  ci->shortname[15] = 0;

  return 0;
}

void
oss_install_chrdev (oss_device_t * osdev, char *name, int dev_class,
		    int instance, oss_cdev_drv_t * drv, int flags)
{
/*
 * oss_install_chrdev creates a character device (minor). However if
 * name==NULL the device will not be exported (made visible to userland
 * clients).
 */

  int i, num;
  oss_cdev_t *cdev = NULL;

  if (dev_class != OSS_DEV_STATUS)
    if (oss_expired && instance > 0)
      return;
/*
 * Find if this dev_class&instance already exists (after previous module
 * detach).
 */

  for (num = 0; num < oss_num_cdevs; num++)
    if (oss_cdevs[num]->d == NULL)	/* Unloaded driver */
      if (oss_cdevs[num]->dev_class == dev_class
	  && oss_cdevs[num]->instance == instance)
	{
	  cdev = oss_cdevs[num];
	  break;
	}

  if (cdev == NULL)
    {
      num = oss_num_cdevs++;
      if (oss_num_cdevs >= OSS_MAX_CDEVS)
	{
	  cmn_err (CE_WARN, "Out of minor numbers.\n");
	  return;
	}

      if ((cdev = PMALLOC (NULL, sizeof (*cdev))) == NULL)
	{
	  cmn_err (CE_WARN, "Cannot allocate character device desc.\n");
	  return;
	}
    }

  memset (cdev, 0, sizeof (*cdev));
  cdev->dev_class = dev_class;
  cdev->instance = instance;
  cdev->d = drv;
  cdev->osdev = osdev;
  if (name != NULL)
    strncpy (cdev->name, name, sizeof (cdev->name) - 1);
  else
    strcpy (cdev->name, "NONE");
  cdev->name[sizeof (cdev->name) - 1] = 0;
  oss_cdevs[num] = cdev;

/*
 * Export the device only if name != NULL
 */
  if (name != NULL)
    {
      char tmp[64], *s;
      char *dev_type = "oss_sysdev";

/*
 * Convert "oss/device/node" style names to the
 * "device,node" style naming required by Solaris.
 */
      if (name[0] == 'o' && name[3] == '/')	/* oss/ prefix */
	{
	  strcpy (tmp, name + 4);
	  name = tmp;

	  s = tmp;
	  while (*s)
	    {
	      if (*s == '/')
		*s = ',';
	      s++;
	    }
	  dev_type = "oss_audio";
	}

      if (ddi_create_minor_node (osdev->dip, name, S_IFCHR, num,
				 dev_type, 0) == DDI_FAILURE)
	{
	  cmn_err (CE_WARN, "ddi_create_minor_node failed\n");
	}
    }
}

int
oss_find_minor (int dev_class, int instance)
{
  int i;

  for (i = 0; i < oss_num_cdevs; i++)
    if (oss_cdevs[i]->d != NULL && oss_cdevs[i]->dev_class == dev_class
	&& oss_cdevs[i]->instance == instance)
      return i;

  return -1;
}

int
__oss_alloc_dmabuf (int dev, dmap_p dmap, unsigned int alloc_flags,
		    oss_uint64_t maxaddr, int direction)
{
  int err;
  uint_t len;
  int flags;
  contig_desc *desc;
  oss_device_t *osdev = dmap->osdev;
  ddi_dma_attr_t dma_attr;
  int size = 64 * 1024;
  extern int dma_buffsize;

  if (dma_buffsize > 16 && dma_buffsize <= 128)
    size = dma_buffsize * 1024;

/*
 * Some applications and virtual drivers need shorter buffer.
 */
  if (dmap->flags & DMAP_SMALLBUF)
    {
      size = SMALL_DMABUF_SIZE;
    }
  else if (dmap->flags & DMAP_MEDIUMBUF)
    {
      size = MEDIUM_DMABUF_SIZE;
    }

  if (alloc_flags & DMABUF_LARGE)
    size = 256 * 1024;

  if ((alloc_flags & DMABUF_SIZE_16BITS) && size > 32 * 1024)
    size = 32 * 1024;

  memcpy (&dma_attr, &dma_attr_pci, sizeof (ddi_dma_attr_t));
  dma_attr.dma_attr_addr_hi = maxaddr;

  flags = DDI_DMA_REDZONE | DDI_DMA_CONSISTENT |
    (direction == OPEN_READ) ? DDI_DMA_READ : DDI_DMA_WRITE;

  if (dmap->dmabuf != NULL)
    return 0;			/* Already done */

  dmap->dma_parms.state = 0;
  dmap->dma_parms.enabled = 1;
  dmap->dma_parms.ignore = 0;

  if (osdev == NULL)
    {
      cmn_err (CE_WARN, "oss_alloc_dmabuf: osdev==NULL\n");
      return -EIO;
    }

  if (osdev->dip == NULL)
    {
      cmn_err (CE_WARN, "oss_alloc_dmabuf: osdev->dip==NULL\n");
      return -EIO;
    }

  if (dmap == NULL)
    {
      cmn_err (CE_WARN, "oss_alloc_dmabuf: dmap==NULL\n");
      return -EIO;
    }

  if ((err = ddi_dma_alloc_handle (osdev->dip,
				   &dma_attr,
				   DDI_DMA_SLEEP,
				   NULL,
				   &dmap->dma_parms.dhandle)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "Failed to allocate DMA handle (error %d)\n", err);
      return -ENOMEM;
    }

  dmap->dmabuf = NULL;
  dmap->buffsize = size;

  err = -1;

  while (err != DDI_SUCCESS && dmap->dmabuf == NULL
	 && dmap->buffsize >= 4 * 1024)
    {
      if ((err = ddi_mem_alloc (osdev->dip,
				NULL,
				dmap->buffsize,
				0,
				(caddr_t *) & dmap->dmabuf,
				(uint_t *) & len)) != DDI_SUCCESS)
	{
	  if (!(alloc_flags & DMABUF_QUIET))
	    DDB (cmn_err (CE_WARN,
			  "failed to allocate %d bytes of DMA memory (%d)\n",
			  dmap->buffsize, err));
	  dmap->dmabuf = NULL;
	  dmap->buffsize /= 2;
	}
    }

  if (dmap->dmabuf == NULL)
    {
      if (!(alloc_flags & DMABUF_QUIET))
	cmn_err (CE_WARN, "Can't allocate a DMA buffer for device %d\n", dev);
      ddi_dma_free_handle (&dmap->dma_parms.dhandle);
      return -ENOMEM;
    }

  dmap->buffsize = len;
  DEB (cmn_err (CE_CONT, "Allocated DMA memory, addr=%x, len=%d\n",
		(int) dmap->dmabuf, (int) dmap->buffsize));

  dmap->dma_parms.orig_buf = (caddr_t) dmap->dmabuf;

  if ((err = ddi_dma_addr_bind_handle (dmap->dma_parms.dhandle,
				       NULL,
				       (char *) dmap->dmabuf,
				       dmap->buffsize,
				       flags | DDI_DMA_STREAMING,
				       DDI_DMA_DONTWAIT,
				       NULL,
				       &dmap->dma_parms.cookie,
				       &len)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "DMA address setup failed (%d)\n", err);

      return -EIO;
    }

  dmap->dmabuf =
    (unsigned char *) ((((unsigned long) dmap->dmabuf) + 4095) & ~4095);
  dmap->dmabuf_phys = (dmap->dma_parms.cookie.dmac_address + 4095) & ~4095;

  desc = PMALLOC (osdev, sizeof (contig_desc));
  if (desc == NULL)
    return -ENOMEM;

  desc->osdev = osdev;
  desc->next = NULL;
  desc->is_special = 1;
  desc->first_addr = dmap->dmabuf;
  desc->last_addr = dmap->dmabuf + dmap->buffsize - 1;
  desc->physaddr = dmap->dmabuf_phys;
  desc->orig_buf = dmap->dma_parms.orig_buf;

  if (contig_list != NULL)
    desc->next = contig_list;
/* HW_PRINTF(("Alloc DMA: %x-%x, ph=%x\n", desc->first_addr, desc->last_addr, desc->physaddr)); */
  contig_list = desc;

  return 0;
}

void
oss_free_dmabuf (int dev, dmap_p dmap)
{
  int err;

  if (dmap->dmabuf == NULL)
    return;

  dmap->dmabuf = NULL;
  dmap->dmabuf_phys = 0;

  if ((err = ddi_dma_unbind_handle (dmap->dma_parms.dhandle)) != DDI_SUCCESS)
    cmn_err (CE_WARN, "Failed to free DMA handle (%d)\n", err);

  ddi_mem_free (dmap->dma_parms.orig_buf);
  ddi_dma_free_handle (&dmap->dma_parms.dhandle);
}

/*
 * Interrupt management
 */
static u_int
oss_intr (caddr_t arg)		/* Global interrupt handler */
{
  oss_device_t *osdev = (oss_device_t *) arg;
  int serviced;
#ifdef MUTEX_CHECKS
  int x = inside_intr;
  inside_intr = 1;		/* For mutex debugging only */
#endif

  if (osdev == NULL)
    {
#ifdef MUTEX_CHECKS
      inside_intr = x;
#endif
      return DDI_INTR_UNCLAIMED;
    }

  UP_STATUS (0x01);
  serviced = osdev->tophalf_handler (osdev);
  DOWN_STATUS (0x01);

  if (serviced == 0)
    return DDI_INTR_UNCLAIMED;

  if (osdev->bottomhalf_handler == NULL)
    return DDI_INTR_CLAIMED;

  if (osdev->intr_is_hilevel)
    {
/* TODO: Schedule the bottom half handler */
    }
  else
    osdev->bottomhalf_handler (osdev);

#ifdef MUTEX_CHECKS
  inside_intr = x;
#endif
  return DDI_INTR_CLAIMED;
}

int
oss_register_interrupts (oss_device_t * osdev, int intrnum,
			 oss_tophalf_handler_t top,
			 oss_bottomhalf_handler_t bottom)
{
  int err;

  if (intrnum != 0)
    {
      cmn_err (CE_WARN, "Bad interrupt index (%d) for %s\n", intrnum,
	       osdev->name);
      return -EINVAL;
    }

  if (osdev == NULL)
    {
      cmn_err (CE_WARN, "oss_register_interrupts: Bad osdev\n");
      return -EINVAL;
    }

  if (osdev->tophalf_handler != NULL || osdev->bottomhalf_handler != NULL)
    {
      cmn_err (CE_WARN, "Interrupts already registered for %s\n",
	       osdev->name);
      return -EINVAL;
    }

  if (top == NULL)
    {
      cmn_err (CE_WARN, "Bad interrupt handler for %s\n", osdev->name);
      return -EINVAL;
    }

  osdev->tophalf_handler = top;
  osdev->bottomhalf_handler = bottom;
  osdev->intr_is_hilevel = ddi_intr_hilevel (osdev->dip, intrnum);
  if (osdev->intr_is_hilevel)
    {
      if (bottom == NULL)
	{
	  cmn_err (CE_WARN,
		   "The driver for %s doesn't support hilevel interrupts\n",
		   osdev->name);
	  return -EINVAL;
	}

      DDB (cmn_err (CE_NOTE, "Using hilevel intr for %s\n", osdev->name));

      /* TODO: Fix hilevel intr handling */
      cmn_err (CE_WARN, "Hilevel interrupts are not supported yet.\n");
      return -EINVAL;
    }
  else
    {
      DDB (cmn_err
	   (CE_NOTE, "Using normal interrupt level for %s\n", osdev->name));
    }

  if ((err = ddi_add_intr (osdev->dip, intrnum, NULL, NULL,
			   oss_intr, (caddr_t) osdev)) != DDI_SUCCESS)
    {
      cmn_err (CE_WARN, "ddi_add_intr() failed, error=%d\n", err);
      return -EIO;
    }

  return 0;
}

void
oss_unregister_interrupts (oss_device_t * osdev)
{
  ddi_remove_intr (osdev->dip, 0, osdev->iblock_cookie);
  osdev->tophalf_handler = NULL;
  osdev->bottomhalf_handler = NULL;
}

int
oss_register_device (oss_device_t * osdev, const char *name)
{
  static int dev_instance = 0;

  DDB (cmn_err (CE_CONT, "OSS device %d is %s\n", dev_instance++, name));

  if ((osdev->name = PMALLOC (NULL, strlen (name) + 1)) == NULL)
    {
      cmn_err (CE_WARN, "Cannot allocate memory for device name\n");
      osdev->name = "Unknown device";
    }
  strcpy (osdev->name, name);
  return 0;
}

int
oss_disable_device (oss_device_t * osdev)
{
  int i;
/*
 * This routine should check if the device is ready to be unloaded (no devices are in use).
 * If the device cannot be unloaded this routine must return -EBUSY.
 *
 * If the device can be unloaded then disable any timers or other features that may cause the
 * device to be called. Also mark the audio/midi/mixer/etc devices of this device to be disabled.
 * However the interrupt handler should still stay enabled. The low level driver will call
 * oss_unregister_interrupts() after it has cleared the interrupt enable register.
 */
  if (osdev->refcount > 0 || open_devices > 0)
    {
      cmn_err (CE_CONT, "Refcount %d, open_devices %d\n", osdev->refcount,
	       open_devices);
      return -EBUSY;
    }

/*
 * Now mark all devices unavailable (for the time being)
 */

  for (i = 0; i < num_mixers; i++)
    if (mixer_devs[i]->osdev == osdev)
      {
	mixer_devs[i]->unloaded = 1;
      }

  for (i = 0; i < num_mididevs; i++)
    {
      if (midi_devs[i]->osdev == osdev)
	{
	  midi_devs[i]->unloaded = 1;
	  if (midi_devs[i]->converter != NULL)
	    midi_devs[i]->converter->enabled = 0;
	}
    }

  for (i = 0; i < num_audio_engines; i++)
    if (audio_engines[i]->osdev == osdev)
      {
	audio_uninit_device (i);
      }

  return 0;
}

void
oss_unregister_device (oss_device_t * osdev)
{
/*
 * Notice! The driver calling this routine (the owner of the osdev parameter)
 * has already uninitialized itself. Do not do any actions that may call this
 * driver directly or indirectly.
 */

/*
 * Force reload of all drivers if any application tries to open any
 * of the devices.
 */
  do_forceload = 1;
}

void
oss_reserve_device (oss_device_t * osdev)
{
  osdev->refcount++;
}

void
oss_unreserve_device (oss_device_t * osdev)
{
  osdev->refcount--;
  if (osdev->refcount < 0)
    osdev->refcount = 0;
}

/*
 * Wait queue support
 */
oss_wait_queue_t *
oss_create_wait_queue (oss_device_t * osdev, const char *name)
{
  oss_wait_queue_t *wq;

  if ((wq = PMALLOC (NULL, sizeof (*wq))) == NULL)
    return NULL;

  cv_init (&wq->cv, NULL, CV_DRIVER, NULL);

  return wq;
}

void *
oss_get_osid (oss_device_t * osdev)
{
  return osdev->osid;
}

void
oss_reset_wait_queue (oss_wait_queue_t * wq)
{
  /* NOP */
}

void
oss_remove_wait_queue (oss_wait_queue_t * wq)
{
  cv_destroy (&wq->cv);
}

int
oss_sleep (oss_wait_queue_t * wq, oss_mutex_t * mutex, int ticks,
	   oss_native_word * flags, unsigned int *status)
{
/*
 * oss_sleep will return 0 if timeout occurred and 1 otherwise. The WK_SIGNAL bit will be reported on
 * status if a signal was caught.
 */
  *status = 0;

  if (ticks > 0)
    {
      int res;

      if ((res =
	   cv_timedwait_sig (&wq->cv, mutex, ddi_get_lbolt () + ticks)) == 0)
	*status |= WK_SIGNAL;	/* Got signal */
      if (res < 0)		/* Timeout */
	return 0;
    }
  else
    {
      if (cv_wait_sig (&wq->cv, mutex) == 0)
	*status |= WK_SIGNAL;	/* Got signal */
    }
  return 1;
}

int
oss_register_poll (oss_wait_queue_t * wq, oss_mutex_t * mutex,
		   oss_native_word * flags, oss_poll_event_t * ev)
{
  ev->php = &wq->ph;
  wq->pollevents |= ev->events;
  return 0;
}

void
oss_wakeup (oss_wait_queue_t * wq, oss_mutex_t * mutex,
	    oss_native_word * flags, short events)
{
  cv_broadcast (&wq->cv);

  if (wq->pollevents & events)
    {
      wq->pollevents &= ~events;
      MUTEX_EXIT_IRQRESTORE (*mutex, *flags);
      pollwakeup (&wq->ph, events);
      MUTEX_ENTER_IRQDISABLE (*mutex, *flags);
    }
}

#ifdef MUTEX_CHECKS
void
debug_mutex_init (oss_mutex_t * mutex, void *dummy, int typ, void *arg,
		  char *file, int line)
{
  memset (mutex, 0, sizeof (mutex));
  mutex_init (&mutex->mu, dummy, typ, arg);
}

void
debug_mutex_destroy (oss_mutex_t * mutex, char *file, int line)
{
  mutex_destroy (&mutex->mu);
}

void
debug_mutex_enter (oss_mutex_t * mutex, char *file, int line)
{
  if (mutex_owned (&mutex->mu))
    {
      cmn_err (CE_NOTE, "%s:%d: Re-entrant mutex (%s:%d %d)\n", file, line,
	       mutex->file, mutex->line, mutex->busy_flags);
      return;
    }

  mutex->file = file;
  mutex->line = line;
  mutex_enter (&mutex->mu);
}

void
debug_mutex_exit (oss_mutex_t * mutex, char *file, int line)
{
  if (!mutex_owned (&mutex->mu))
    {
      cmn_err (CE_NOTE, "Mutex not owned %s:%d\n", file, line);
    }
  else
    mutex_exit (&mutex->mu);

  mutex->file = NULL;
  mutex->line = 0;
}
#endif
