diff options
Diffstat (limited to 'kernel/OS/SunOS/os_solaris.c')
-rw-r--r-- | kernel/OS/SunOS/os_solaris.c | 2401 |
1 files changed, 2401 insertions, 0 deletions
diff --git a/kernel/OS/SunOS/os_solaris.c b/kernel/OS/SunOS/os_solaris.c new file mode 100644 index 0000000..b4ceac3 --- /dev/null +++ b/kernel/OS/SunOS/os_solaris.c @@ -0,0 +1,2401 @@ +/* + * Purpose: Operating system abstraction functions for Solaris + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * 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 <oss_pci.h> +#if !defined(SOL9) && !defined(SOL8) +#include <sys/sunldi.h> +#endif +#include <sys/mman.h> + +#if 1 +/* + * Some older DDI routines are used by OSS instead of the latest ones. + * In this way the same OSS binary can be run both under Sol10 and Sol11. + */ +uint16_t ddi_mem_get16 (ddi_acc_handle_t handle, uint16_t * host_addr); +uint32_t ddi_mem_get32 (ddi_acc_handle_t handle, uint32_t * host_addr); +void ddi_mem_put16 (ddi_acc_handle_t handle, uint16_t * dev_addr, + uint16_t value); +void ddi_mem_put32 (ddi_acc_handle_t handle, uint32_t * dev_addr, + uint32_t value); +int ddi_mem_alloc (dev_info_t * dip, ddi_dma_lim_t * limits, uint_t length, + uint_t flags, caddr_t * kaddrp, uint_t * real_length); +void ddi_mem_free (caddr_t kaddr); +#endif + +#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; + +#define TRC(x) +#define TRC2(x) + +#define FIX_RET_VALUE(ret) (((ret)<0) ? -(ret) : 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; + +#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_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 + +#define CDEV_NUMHASH 79 // Prime number +static oss_cdev_t *cdev_hash[CDEV_NUMHASH] = {NULL}; +#define compute_cdev_hash(dev_class, instance) (dev_class*13+instance) % CDEV_NUMHASH + +static void +unlink_cdev_hash(oss_cdev_t *this_cdev) +{ + oss_cdev_t *cdev = cdev_hash[compute_cdev_hash(this_cdev->dev_class, this_cdev->instance)]; + + if (cdev == this_cdev) // First in the hash chain + { + cdev_hash[compute_cdev_hash(this_cdev->dev_class, this_cdev->instance)] = cdev->hl; + return; + } + + while (cdev != NULL) + { + if (cdev->hl == this_cdev) + { + cdev->hl = this_cdev->hl; + return; + } + + cdev = cdev->hl; + } + + cmn_err(CE_WARN, "unlink_cdev_hash: Cannot find cdev %p\n", this_cdev); +} + +#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; + + 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 const ddi_dma_attr_t dma_attr_pci = { + DMA_ATTR_V0, // Version + 0x00000000ULL, // Address low + 0xfffffff0ULL, // Address high + 0xffffffffULL, // Counter max + 1ULL, // Default byte align + 0x7f, // Burst size + 0x1, // Minimum xfer size + 0xffffffffULL, // Maximum xfer size + 0xffffffffULL, // Max segment size + 1, // S/G list length + 1, // Granularity + 0 // Flag +}; + +#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; + } + + if ((err = ldi_open_by_name ("/dev/sndstat", FWRITE, credp, &lh, li)) != 0) + { + if (err != ENODEV) + cmn_err (CE_NOTE, "Forceload error %d (/dev/sndstat)\n", err); + } + else + err = ldi_close (lh, FWRITE, credp); + + for (i = 0; i < MAX_MIXER_DEV; i++) + { + sprintf (path, "/dev/mixer%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); + } + else + err = ldi_close (lh, FWRITE, credp); + } + + for (i = 0; i < MAX_AUDIO_DEVFILES; 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); + } + else + err = 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 + +/*ARGSUSED*/ +int +oss_open (dev_t * dev_p, int open_flags, int otyp, cred_t * cred_p) +{ + int retval; + dev_t dev = *dev_p; + oss_cdev_t *cdev; + int tmpdev, maj; + oss_native_word flags; + +#ifdef DO_TIMINGS + oss_timing_printf ("**** oss_open(%x) ****", getminor (dev)); +#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; + } + + memset (&cdev->file, 0, sizeof (cdev->file)); + cdev->file.mode = 0; + cdev->file.acc_flags = open_flags; + + if (open_flags & FREAD && open_flags & FWRITE) + cdev->file.mode = OPEN_READWRITE; + else if (open_flags & FREAD) + cdev->file.mode = OPEN_READ; + else if (open_flags & FWRITE) + cdev->file.mode = OPEN_WRITE; + + tmpdev = dev; + retval = + cdev->d->open (cdev->instance, cdev->dev_class, &cdev->file, 0, 0, &tmpdev); + *dev_p = makedevice (maj, tmpdev); + dev = tmpdev; + + if (retval < 0) + { + return -retval; + } + + MUTEX_ENTER_IRQDISABLE (osscore_mutex, flags); + open_devices++; + cdev = oss_cdevs[dev]; /* Switch to the cdev that was actually opened */ + + cdev->open_count++; + if (open_flags & FREAD && open_flags & FWRITE) + cdev->file.mode = OPEN_READWRITE; + else if (open_flags & FREAD) + cdev->file.mode = OPEN_READ; + else if (open_flags & FWRITE) + cdev->file.mode = OPEN_WRITE; + + oss_reserve_device (cdev->osdev); +//cmn_err(CE_CONT, "Increment open_devices=%d, refcount=%d\n", open_devices, cdev->osdev->refcount); + MUTEX_EXIT_IRQRESTORE (osscore_mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +int +oss_close (dev_t dev, int flag, int otyp, cred_t * cred_p) +{ + oss_cdev_t *cdev; + oss_native_word flags; +#ifdef DO_TIMINGS + oss_timing_printf ("***** oss_close(%x) ****", getminor (dev)); +#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; + } + + dev = getminor (dev); + if ((cdev = oss_cdevs[dev]) == NULL || cdev->d == NULL) + { + cmn_err (CE_NOTE, "Closing undefined minor %d\n", getminor (dev)); + return ENXIO; + } + + + if (cdev->open_count == 0) /* Not opened */ + { + cmn_err (CE_NOTE, "Closing minor %d that is not open\n", dev); + return 0; + } + + cdev->d->close (cdev->instance, &cdev->file); + + MUTEX_ENTER_IRQDISABLE (osscore_mutex, flags); + open_devices -= cdev->open_count; +//cmn_err(CE_CONT, "Decrement open_devices=%d\n", open_devices); + oss_unreserve_device (cdev->osdev, cdev->open_count); + cdev->open_count = 0; + MUTEX_EXIT_IRQRESTORE (osscore_mutex, flags); + return 0; +} + +/*ARGSUSED*/ +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 b[4096], *buf = b; + oss_cdev_t *cdev; +#ifdef DO_TIMINGS + oss_timing_printf ("OSS ioctl(%x, %x, %x)", getminor (dev), cmd, arg); +#endif + + *rval_p = 0; + + dev = getminor (dev); + if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->ioctl == NULL) + return ENXIO; + + if (cdev->open_count == 0) /* Not opened */ + { + cmn_err (CE_NOTE, "Call to ioctl on minor %d that is not open\n", dev); + return ENXIO; + } + + if (oss_expired) + return ENODEV; + + if (mode & FKIOCTL) /* Called from kernel space */ + buf = (char *) arg; + else if (cmd & (SIOC_OUT | SIOC_IN)) + { + + len = (cmd >> 16) & SIOCPARM_MASK; + if (len < 0) + len = 0; + if (len > sizeof (b)) + { + cmn_err (CE_WARN, "Bad ioctl buffer size %d\n", len); + return EFAULT; + } + + if ((cmd & SIOC_IN) && len > 0) + { + if (copyin ((char *) arg, buf, len) == -1) + return EFAULT; + } + + } + + retval = cdev->d->ioctl (cdev->instance, &cdev->file, cmd, (ioctl_arg) buf); + + if (!(mode & FKIOCTL)) /* Not called from kernel space */ + if ((cmd & SIOC_OUT) && len > 0) + { + if (copyout (buf, (char *) arg, len) == -1) + return EFAULT; + } + + return FIX_RET_VALUE (retval); +} + +/*ARGSUSED*/ +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; + + if (cdev->open_count == 0) /* Not opened */ + { + cmn_err (CE_NOTE, "Call to read on minor %d that is not open\n", dev); + return ENXIO; + } + + cdev->file.acc_flags = uiop->uio_fmode; + + retval = cdev->d->read (cdev->instance, &cdev->file, uiop, count); + + return FIX_RET_VALUE (retval); +} + +/*ARGSUSED*/ +int +oss_write (dev_t dev, struct uio *uiop, cred_t * cred_p) +{ + int count = uiop->uio_resid; + int retval; + oss_cdev_t *cdev; + + dev = getminor (dev); + if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->write == NULL) + return ENXIO; + + if (cdev->open_count == 0) /* Not opened */ + { + cmn_err (CE_NOTE, "Call to write on minor %d that is not open\n", dev); + return ENXIO; + } + cdev->file.acc_flags = uiop->uio_fmode; + + retval = cdev->d->write (cdev->instance, &cdev->file, 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 + oss_timing_printf ("***** oss_chpoll(%x) ****", getminor (dev)); +#endif + + if (getminor (dev) >= OSS_MAX_CDEVS) + return ENXIO; + + dev = getminor (dev); + if ((cdev = oss_cdevs[dev]) == NULL || cdev->d->chpoll == NULL) + return ENXIO; + + if (cdev->open_count == 0) /* Not opened */ + { + cmn_err (CE_NOTE, "Call to chpoll on minor %d that is not open\n", dev); + return ENXIO; + } + + *reventsp = 0; + ev.events = events; + ev.anyyet = anyyet; + ev.revents = 0; + ev.php = NULL; + + ret = cdev->d->chpoll (cdev->instance, &cdev->file, &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 + oss_timing_printf ("***** oss_devmap(%x) ****", getminor (dev)); +#endif + + if (getminor (dev) >= OSS_MAX_CDEVS) + { + return ENXIO; + } + + dev = getminor (dev); + if ((cdev = oss_cdevs[dev]) == NULL || cdev->d == NULL) + { + return ENXIO; + } + + if (cdev->open_count == 0) /* Not opened */ + { + cmn_err (CE_NOTE, "Call to devmap on minor %d that is not open\n", dev); + return EPERM; + } + + 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 + +#if 0 +// Not used in misc modules. +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 */ +}; +#endif + +extern struct mod_ops mod_miscops; +static struct modldrv modldrv = { + &mod_miscops, + "Open Sound System " OSS_VERSION_STRING " framework" + // &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 + + +/*ARGSUSED*/ +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; + + 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, "osscore", NULL)) == NULL) + { + cmn_err (CE_WARN, "Creating osdev failed\n"); + return DDI_FAILURE; + } + + MUTEX_INIT (osdev, osscore_mutex, 0); + + 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 instance; + + dev = (dev_t) arg; + instance = getminor (dev) >> 4; + + switch (infocmd) + { + case DDI_INFO_DEVT2DEVINFO: + *result = dip; + error = DDI_SUCCESS; + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *) (uintptr_t) instance; + error = DDI_SUCCESS; + break; + default: + *result = NULL; + error = DDI_FAILURE; + } + +#if 0 + DDB (cmn_err (CE_CONT, + "oss_getinfo: returns %d, result=%x minor=%d instance=%d dev=%x\n", + error, *result, minor_num, instance, dev)); +#endif + return (error); +} + +/* + * _init, _info, and _fini support loading and unloading the driver. + */ +int +_init (void) +{ + register int error = 0; + error = mod_install (&modlinkage); + return error; +} + +int +_info (struct modinfo *modinfop) +{ + return (mod_info (&modlinkage, modinfop)); +} + +int +_fini (void) +{ + int status; + +#ifdef MEMDEBUG + int i; +#endif + + if ((status = mod_remove (&modlinkage)) != 0) + return (status); + + free_all_contig_memory (); + +#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 the osscore module 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) +{ + static int already_unloaded = 0; + + if (cmd != DDI_DETACH) + return DDI_FAILURE; + + if (already_unloaded) + return DDI_SUCCESS; + already_unloaded = 1; +//cmn_err(CE_CONT, "Oss detach\n"); + + /* 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; +} + +void +oss_pcie_init (oss_device_t * osdev, int flags) +{ + /* TODO: Should we do something? */ +} + +/*ARGSUSED*/ +caddr_t +oss_map_pci_ioaddr (oss_device_t * osdev, int nr, int io) +{ + caddr_t addr; + off_t region_size; + int err; + ddi_device_acc_attr_t *acc_attr; + + if (nr >= OSS_MAX_ACC_HANDLE) + { + cmn_err(CE_WARN, "Too large I/O region number %d\n", nr); + + return 0; + } + + acc_attr = get_acc_attr (osdev); + + if ((err = + ddi_dev_regsize (osdev->dip, nr + 1, ®ion_size)) != DDI_SUCCESS) + { + cmn_err (CE_WARN, "Getting device regsize failed (%d)\n", err); + return 0; + } + + if ((err = ddi_regs_map_setup + (osdev->dip, nr + 1, &addr, 0, region_size, acc_attr, + &osdev->acc_handle[nr])) != DDI_SUCCESS) + { + cmn_err (CE_WARN, "Register setup failed (%d)\n", err); + return 0; + } + + return addr; +} + +void +oss_unmap_pci_ioaddr(oss_device_t * osdev, int nr) +{ + if (nr >= OSS_MAX_ACC_HANDLE) + { + cmn_err(CE_WARN, "Too large I/O region number %d\n", nr); + return; + } + + ddi_regs_map_free(&osdev->acc_handle[nr]); +} + +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; +} + +#ifdef MEMDEBUG +void * +oss_contig_malloc (oss_device_t * osdev, int size, oss_uint64_t memlimit, + oss_native_word * phaddr, oss_dma_handle_t *dma_handle, char *file, int line) +#else +void * +oss_contig_malloc (oss_device_t * osdev, int size, oss_uint64_t memlimit, + oss_native_word * phaddr, oss_dma_handle_t *dma_handle) +#endif +{ +/* + * Allocate physically contiguous memory (suitable for DMA). + * + * The memlimit parameter is equal to oss_alloc_dmabuf(). + */ + int err; +#if defined(sparc) + uint_t len; +#else + size_t len; +#endif + uint_t count; + contig_desc *desc; + ddi_dma_attr_t dma_attr; + ddi_device_acc_attr_t *acc_attr; + + int flags = + DDI_DMA_REDZONE | DDI_DMA_CONSISTENT | DDI_DMA_READ | DDI_DMA_WRITE; + + 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; + + 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 ((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; + } + + if (dma_handle != NULL) + *dma_handle = desc->dhandle; + +#ifndef IOMEM_DATA_UNCACHED +#define IOMEM_DATA_UNCACHED 0 // Fix for Solaris 10 +#endif + +#if defined(sparc) + if ((err = ddi_mem_alloc (osdev->dip, + NULL, + size + 4096, + 0, + (caddr_t *) & desc->first_addr, + (uint_t *) & len)) != DDI_SUCCESS) + + +#else + if ((err = ddi_dma_mem_alloc (desc->dhandle, + size + 4096, + acc_attr, + flags, + DDI_DMA_SLEEP, + NULL, + (caddr_t *) & desc->first_addr, + (size_t *) & len, + &desc->dma_acc_handle)) != DDI_SUCCESS) +#endif + { + 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; + } + + DDB (cmn_err + (CE_CONT, "Allocated contig memory, addr=%x, len=%d\n", + (unsigned 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; +} + +/*ARGSUSED*/ +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 ((err = ddi_dma_unbind_handle (desc->dhandle)) != DDI_SUCCESS) + cmn_err (CE_WARN, "Failed to free DMA handle (%d)\n", err); + +#if defined(sparc) + ddi_mem_free (desc->orig_buf); +#else + 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); +#endif + ddi_dma_free_handle (&desc->dhandle); + + + if (desc == contig_list) + contig_list = desc->next; + else + { + prev->next = desc->next; + } + KERNEL_FREE (desc); + +} + +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; +} + +#if !defined(SOL9) && !defined(DISABLE_FMA) +/*ARGSUSED*/ +static int +oss_fm_error_cb(dev_info_t *dip, ddi_fm_error_t *err, const void *osdev_) +{ + pci_ereport_post(dip, err, NULL); + return err->fme_status; +} +#endif + +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; + ddi_iblock_cookie_t iblk; + static int license_checked=0; + +#ifdef LICENSED_VERSION + if (!license_checked) + { + timestruc_t ts; + + license_checked = 1; + 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 (handle == NULL) + handle = nick; + + /* + * Don't accept any more drivers if expired + */ + if (oss_expired && oss_num_cards > 0) + return NULL; + + /* + * Check if the same device is being reinserted. + */ + for (i = 0; i < oss_num_cards; i++) + { + if (cards[i]->available) /* Not deleted */ + continue; + + if (cards[i]->dip == dip) + { + osdev = cards[i]; + break; + } + } + +#if 1 + if (osdev == NULL) + { + /* + * Check if there are some deleted devices. + */ + for (i = 0; i < oss_num_cards; i++) + { + if (cards[i]->available) /* Not deleted */ + continue; + + osdev = cards[i]; + break; + } + } +#endif + + if (osdev == NULL) + { + for (i=0;i<oss_num_cards;i++) + if (!cards[i]->available) + { + osdev = cards[i]; + break; + } + } + + if (osdev == NULL) + { + if (oss_num_cards >= MAX_CARDS) + { + cmn_err (CE_WARN, "Too many OSS devices. At most %d permitted.\n", + MAX_CARDS); + return NULL; + } + + 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->available = 1; + osdev->instance = instance; + osdev->dev_type = dev_type; + osdev->devc = NULL; + sprintf (osdev->nick, "%s%d", nick, instance); + strcpy (osdev->modname, nick); + osdev->num_audio_engines = 0; + osdev->num_audioplay = 0; + osdev->num_audiorec = 0; + osdev->num_audioduplex = 0; + osdev->num_mididevs = 0; + osdev->num_mixerdevs = 0; + osdev->first_mixer = -1; + + switch (dev_type) + { + case DRV_PCI: + if (pci_config_setup (dip, &osdev->pci_config_handle) != DDI_SUCCESS) + cmn_err (CE_NOTE, "pci_config_setup() failed\n"); +#if !defined(SOL9) && !defined(DISABLE_FMA) + osdev->fm_capabilities = DDI_FM_EREPORT_CAPABLE | DDI_FM_DMACHK_CAPABLE | + DDI_FM_ERRCB_CAPABLE; + ddi_fm_init(dip, &osdev->fm_capabilities, &iblk); + ddi_fm_handler_register(dip, oss_fm_error_cb, (void*)osdev); + pci_ereport_setup(dip); +#endif + break; + + case DRV_VIRTUAL: + case DRV_VMIX: + case DRV_STREAMS: +#if !defined(SOL9) && !defined(DISABLE_FMA) + osdev->fm_capabilities=DDI_FM_EREPORT_CAPABLE; + ddi_fm_init(dip, &osdev->fm_capabilities, &iblk); +#endif + 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; + + if (!osdev->available) /* Already deleted */ + return; + + osdev->available = 0; + + switch (osdev->dev_type) + { + case DRV_PCI: +#if !defined(SOL9) && !defined(DISABLE_FMA) + ddi_fm_handler_unregister(osdev->dip); + pci_ereport_teardown(osdev->dip); +#endif + pci_config_teardown (&osdev->pci_config_handle); +#if !defined(SOL9) && !defined(DISABLE_FMA) + ddi_fm_fini(osdev->dip); +#endif + osdev->pci_config_handle = NULL; + break; + + case DRV_VIRTUAL: + case DRV_VMIX: + case DRV_STREAMS: +#if !defined(SOL9) && !defined(DISABLE_FMA) + ddi_fm_fini(osdev->dip); +#endif + 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] != NULL) + if (oss_cdevs[i]->osdev == osdev) + { + unlink_cdev_hash(oss_cdevs[i]); + oss_cdevs[i]->d = NULL; + oss_cdevs[i]->osdev = NULL; + oss_cdevs[i]->active = 0; /* Device removed */ + } +} + +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 OSS_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; + + if (cards[cardnum]->hw_info != NULL) + strncpy (ci->hw_info, cards[cardnum]->hw_info, sizeof (ci->hw_info) - 1); + ci->hw_info[sizeof (ci->hw_info) - 1] = 0; + ci->intr_count = cards[cardnum]->intrcount; + ci->ack_count = cards[cardnum]->ackcount; + + return 0; +} + +static int +grow_array(oss_device_t *osdev, oss_cdev_t ***arr, int *size, int element_size, int increment) +{ + oss_cdev_t **old=*arr, **new = *arr; + int old_size = *size; + int new_size = *size; + + new_size += increment; + + if ((new=PMALLOC(osdev, new_size * element_size))==NULL) + return 0; + + memset(new, 0, new_size * element_size); + if (old != NULL) + memcpy(new, old, old_size * element_size); + + *size = new_size; + *arr = new; + + if (old != NULL) + PMFREE(osdev, old); + + return 1; +} + +/*ARGSUSED*/ +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 num; + int hash_link; + 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). + */ + + if (flags & CHDEV_REPLUG) + 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) + { + if (oss_num_cdevs >= OSS_MAX_CDEVS) + { + if (!grow_array(osdev, &oss_cdevs, &oss_max_cdevs, sizeof(oss_cdev_t*), 100)) + { + cmn_err (CE_WARN, "Cannot allocate new minor numbers.\n"); + return; + } + } + + if ((cdev = PMALLOC (NULL, sizeof (*cdev))) == NULL) + { + cmn_err (CE_WARN, "Cannot allocate character device desc.\n"); + return; + } + num = oss_num_cdevs++; + } + + memset (cdev, 0, sizeof (*cdev)); + cdev->dev_class = dev_class; + cdev->instance = instance; + cdev->d = drv; + cdev->active = 1; + 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; + + cdev->minor = num; + + /* + * Add to the cdev_hash list. + */ + hash_link = compute_cdev_hash (dev_class, instance); + cdev->hl = cdev_hash[hash_link]; + cdev_hash[hash_link] = 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) +{ + oss_cdev_t *cdev; + + cdev = cdev_hash[compute_cdev_hash(dev_class, instance)]; + + while (cdev != NULL) + { + if (cdev->d != NULL && cdev->dev_class == dev_class + && cdev->instance == instance) + return cdev->minor; + cdev = cdev->hl; /* Next in the hash chain */ + } + + return OSS_EIO; +} + +int +__oss_alloc_dmabuf (int dev, dmap_p dmap, unsigned int alloc_flags, + oss_uint64_t maxaddr, int direction) +{ + int err; +#if defined(sparc) + uint_t len; +#else + size_t len; +#endif + uint_t ncookies; + contig_desc *desc; + oss_device_t *osdev = dmap->osdev; + ddi_dma_attr_t dma_attr; + int size = 32 * 1024; + extern int dma_buffsize; + ddi_device_acc_attr_t *acc_attr; + + int flags = DDI_DMA_REDZONE | DDI_DMA_CONSISTENT | + (direction == OPEN_READ) ? DDI_DMA_READ : DDI_DMA_WRITE; + + if (osdev == NULL) + { + cmn_err (CE_WARN, "oss_alloc_dmabuf: osdev==NULL\n"); + return OSS_EIO; + } + + acc_attr = get_acc_attr (osdev); + + 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; + +#ifndef SOL9 + if (osdev->dev_type == DRV_PCI) + dma_attr.dma_attr_flags |= DDI_DMA_FLAGERR; +#endif + + 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->dip == NULL) + { + cmn_err (CE_WARN, "oss_alloc_dmabuf: osdev->dip==NULL\n"); + return OSS_EIO; + } + + if (dmap == NULL) + { + cmn_err (CE_WARN, "oss_alloc_dmabuf: dmap==NULL\n"); + return OSS_EIO; + } + + if ((err = ddi_dma_alloc_handle (osdev->dip, + &dma_attr, + DDI_DMA_SLEEP, + NULL, + &dmap->dmabuf_dma_handle)) != DDI_SUCCESS) + { + cmn_err (CE_WARN, "Failed to allocate DMA handle (error %d)\n", err); + return OSS_ENOMEM; + } + + + dmap->dmabuf = NULL; + dmap->buffsize = size; + + err = -1; + + while (err != DDI_SUCCESS && dmap->dmabuf == NULL && dmap->buffsize >= 4096) + { +#if defined(sparc) + if ((err = ddi_mem_alloc (osdev->dip, + NULL, + dmap->buffsize, + 0, + (caddr_t *) & dmap->dmabuf, + (uint_t *) & len)) != DDI_SUCCESS) +#else + if ((err = ddi_dma_mem_alloc (dmap->dmabuf_dma_handle, + dmap->buffsize, + acc_attr, + flags, + DDI_DMA_SLEEP, + NULL, + (caddr_t *) & dmap->dmabuf, + (size_t *) & len, + &dmap->dma_parms.dma_acc_handle)) != + DDI_SUCCESS) +#endif + { + 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) + { + cmn_err (CE_WARN, "Can't allocate a DMA buffer for device %d\n", dev); + ddi_dma_free_handle (&dmap->dmabuf_dma_handle); + return OSS_ENOMEM; + } + + DDB (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->dmabuf_dma_handle, + NULL, + (char *) dmap->dmabuf, + dmap->buffsize, + flags | DDI_DMA_STREAMING, + DDI_DMA_DONTWAIT, + NULL, + &dmap->dma_parms.cookie, + &ncookies)) != DDI_SUCCESS) + { + cmn_err (CE_WARN, "DMA address setup failed (%d)\n", err); + + return OSS_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 OSS_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; +} + +/*ARGSUSED*/ +void +oss_free_dmabuf (int dev, dmap_p dmap) +{ + int err; + + if (dmap->dmabuf == NULL) + return; + + if ((err = ddi_dma_unbind_handle (dmap->dmabuf_dma_handle)) != DDI_SUCCESS) + cmn_err (CE_WARN, "Failed to free DMA handle (%d)\n", err); +#if defined(sparc) + ddi_mem_free (dmap->dma_parms.orig_buf); +#else + ddi_dma_mem_free (&dmap->dma_parms.dma_acc_handle); +#endif + ddi_dma_free_handle (&dmap->dmabuf_dma_handle); + + dmap->dmabuf = NULL; + dmap->dmabuf_phys = 0; +} + +/* + * 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; + } + osdev->intrcount++; + + UP_STATUS (0x01); + serviced = osdev->tophalf_handler (osdev); + DOWN_STATUS (0x01); + + if (serviced == 0) + return DDI_INTR_UNCLAIMED; + osdev->ackcount++; + + if (osdev->bottomhalf_handler == NULL) + return DDI_INTR_CLAIMED; + +#if 0 + if (osdev->intr_is_hilevel) + { +/* TODO: Schedule the bottom half handler */ + } + else +#endif + 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; + ddi_idevice_cookie_t ic; + + + if (intrnum != 0) + { + cmn_err (CE_WARN, "Bad interrupt index (%d) for %s\n", intrnum, + osdev->name); + return OSS_EINVAL; + } + + if (osdev == NULL) + { + cmn_err (CE_WARN, "oss_register_interrupts: Bad osdev\n"); + return OSS_EINVAL; + } + + if (osdev->tophalf_handler != NULL || osdev->bottomhalf_handler != NULL) + { + cmn_err (CE_WARN, "Interrupts already registered for %s\n", + osdev->name); + return OSS_EINVAL; + } + + if (top == NULL) + { + cmn_err (CE_WARN, "Bad interrupt handler for %s\n", osdev->name); + return OSS_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 OSS_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 OSS_EINVAL; + } + + ddi_get_iblock_cookie (osdev->dip, intrnum, &osdev->iblock_cookie); + + if ((err = ddi_add_intr (osdev->dip, intrnum, NULL, &ic, + oss_intr, (caddr_t) osdev)) != DDI_SUCCESS) + { + cmn_err (CE_WARN, "ddi_add_intr() failed, error=%d\n", err); + return OSS_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; +} + +/*ARGSUSED*/ +int +oss_register_device (oss_device_t * osdev, const char *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 OSS_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 (%s) , open_devices %d\n", + osdev->refcount, osdev->nick, open_devices); + if (open_devices > 0) + { + int i; + + for (i = 0; i < oss_num_cdevs; i++) + if (oss_cdevs[i]->open_count) + { + cmn_err (CE_CONT, "%s is opened\n", oss_cdevs[i]->name); + } + } + return OSS_EBUSY; + } +//cmn_err(CE_CONT, "oss_disable_device %s\n", osdev->nick); + +/* + * 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; + } + } + + for (i = 0; i < num_audio_engines; i++) + if (audio_engines[i]->osdev == osdev) + { + audio_uninit_device (i); + } + + return 0; +} + +/*ARGSUSED*/ +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, int decrement) +{ + osdev->refcount -= decrement; +} + +/* + * Wait queue support + */ + /*ARGSUSED*/ + 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; +} + +/*ARGSUSED*/ +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); +} + +/*ARGSUSED*/ +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; +} + +/*ARGSUSED*/ +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; +} + +/*ARGSUSED*/ +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 + +int +oss_get_procinfo(int what) +{ + + switch (what) + { + case OSS_GET_PROCINFO_UID: + return ddi_get_cred()->cr_uid; + break; + + case OSS_GET_PROCINFO_GID: + return ddi_get_cred()->cr_gid; + break; + +#if 0 + case OSS_GET_PROCINFO_PGID: + return ddi_get_cred()->cr_pgid; + break; +#endif + + } + return OSS_EINVAL; +} |