root/usr/src/uts/common/io/audio/impl/audio_client.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/list.h>
#include <sys/file.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/atomic.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include "audio_impl.h"

/*
 * Audio Client implementation.
 */

/*
 * Attenuation table for dB->linear conversion. Indexed in steps of
 * 0.5 dB.  Table size is 25 dB (first entry is handled as mute).
 *
 * Notably, the last item in table is taken as 0 dB (i.e. maximum volume).
 *
 * Table contents can be calculated as follows (requires sunmath library):
 *
 * scale = AUDIO_VOL_SCALE;
 * for (i = -50; i <= 0; i++) {
 *     x = exp10(0.05 * i);
 *     printf("%d: %f %.0f\n", i,  x, trunc(x * scale));
 * }
 *
 */

static const uint16_t auimpl_db_table[AUDIO_DB_SIZE + 1] = {
        0,   0,   1,   1,   1,   1,   1,   1,   2,   2,
        2,   2,   3,   3,   4,   4,   5,   5,   6,   7,
        8,   9,   10,  11,  12,  14,  16,  18,  20,  22,
        25,  28,  32,  36,  40,  45,  51,  57,  64,  72,
        80,  90,  101, 114, 128, 143, 161, 181, 203, 228,
        256
};

static list_t                   auimpl_clients;
static krwlock_t                auimpl_client_lock;
static audio_client_ops_t       *audio_client_ops[AUDIO_MN_TYPE_MASK + 1];

void *
auclnt_get_private(audio_client_t *c)
{
        return (c->c_private);
}

void
auclnt_set_private(audio_client_t *c, void *private)
{
        c->c_private = private;
}

int
auclnt_set_rate(audio_stream_t *sp, int rate)
{
        audio_parms_t   parms;
        int             rv = 0;

        /* basic sanity checks! */
        if ((rate < 5000) || (rate > 192000)) {
                return (EINVAL);
        }
        if (rate != sp->s_user_parms->p_rate) {
                parms.p_rate = rate;
                rv = auimpl_engine_setup(sp, 0, &parms, FORMAT_MSK_RATE);
        }
        return (rv);
}

int
auclnt_get_rate(audio_stream_t *sp)
{
        return (sp->s_user_parms->p_rate);
}

uint_t
auclnt_get_fragsz(audio_stream_t *sp)
{
        return (sp->s_fragbytes);
}

uint_t
auclnt_get_framesz(audio_stream_t *sp)
{
        return (sp->s_framesz);
}

uint_t
auclnt_get_nfrags(audio_stream_t *sp)
{
        return (sp->s_nfrags);
}

uint_t
auclnt_get_nframes(audio_stream_t *sp)
{
        return (sp->s_nframes);
}

void
auclnt_set_latency(audio_stream_t *sp, uint_t frags, uint_t bytes)
{
        mutex_enter(&sp->s_lock);
        sp->s_hintfrags = (uint16_t)frags;
        sp->s_hintsz = bytes;
        mutex_exit(&sp->s_lock);
}

uint64_t
auclnt_get_head(audio_stream_t *sp)
{
        return (sp->s_head);
}

uint64_t
auclnt_get_tail(audio_stream_t *sp)
{
        return (sp->s_tail);
}

uint_t
auclnt_get_hidx(audio_stream_t *sp)
{
        return (sp->s_hidx);
}

uint_t
auclnt_get_tidx(audio_stream_t *sp)
{
        return (sp->s_tidx);
}

audio_stream_t *
auclnt_input_stream(audio_client_t *c)
{
        return (&c->c_istream);
}

audio_stream_t *
auclnt_output_stream(audio_client_t *c)
{
        return (&c->c_ostream);
}

uint_t
auclnt_get_count(audio_stream_t *sp)
{
        uint_t  count;

        mutex_enter(&sp->s_lock);
        ASSERT((sp->s_head - sp->s_tail) <= sp->s_nframes);
        count = (uint_t)(sp->s_head - sp->s_tail);
        mutex_exit(&sp->s_lock);

        return (count);
}

uint_t
auclnt_consume(audio_stream_t *sp, uint_t n)
{
        mutex_enter(&sp->s_lock);

        ASSERT(sp == &sp->s_client->c_istream);
        n = max(n, sp->s_head - sp->s_tail);
        sp->s_tail += n;
        sp->s_tidx += n;
        if (sp->s_tidx >= sp->s_nframes) {
                sp->s_tidx -= sp->s_nframes;
        }

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_hidx < sp->s_nframes);

        mutex_exit(&sp->s_lock);

        return (n);
}

uint_t
auclnt_consume_data(audio_stream_t *sp, caddr_t dst, uint_t n)
{
        uint_t nframes;
        uint_t framesz;
        uint_t cnt;
        caddr_t data;

        mutex_enter(&sp->s_lock);

        nframes = sp->s_nframes;
        framesz = sp->s_framesz;

        ASSERT(sp == &sp->s_client->c_istream);
        ASSERT(sp->s_head >= sp->s_tail);
        ASSERT(sp->s_tidx < nframes);
        ASSERT(sp->s_hidx < nframes);

        cnt = n = min(n, sp->s_head - sp->s_tail);
        data = sp->s_data + (sp->s_tidx * framesz);
        do {
                uint_t nf, nb;

                nf = min(nframes - sp->s_tidx, n);
                nb = nf * framesz;

                bcopy(data, dst, nb);
                dst += nb;
                data += nb;

                n -= nf;
                sp->s_tail += nf;
                sp->s_tidx += nf;
                if (sp->s_tidx == nframes) {
                        sp->s_tidx = 0;
                        data = sp->s_data;
                }
        } while (n);

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_tidx < nframes);

        mutex_exit(&sp->s_lock);

        return (cnt);
}

uint_t
auclnt_produce(audio_stream_t *sp, uint_t n)
{
        mutex_enter(&sp->s_lock);

        ASSERT(sp == &sp->s_client->c_ostream);
        n = max(n, sp->s_nframes - (sp->s_head - sp->s_tail));
        sp->s_head += n;
        sp->s_hidx += n;
        if (sp->s_hidx >= sp->s_nframes) {
                sp->s_hidx -= sp->s_nframes;
        }

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_hidx < sp->s_nframes);

        mutex_exit(&sp->s_lock);

        return (n);
}

uint_t
auclnt_produce_data(audio_stream_t *sp, caddr_t src, uint_t n)
{
        uint_t nframes;
        uint_t framesz;
        uint_t cnt;
        caddr_t data;

        mutex_enter(&sp->s_lock);

        nframes = sp->s_nframes;
        framesz = sp->s_framesz;

        ASSERT(sp == &sp->s_client->c_ostream);
        ASSERT(sp->s_head >= sp->s_tail);
        ASSERT(sp->s_tidx < nframes);
        ASSERT(sp->s_hidx < nframes);

        cnt = n = min(n, nframes - (sp->s_head - sp->s_tail));
        data = sp->s_data + (sp->s_hidx * framesz);
        do {
                uint_t nf, nb;

                nf = min(nframes - sp->s_hidx, n);
                nb = nf * framesz;

                bcopy(src, data, nb);

                src += nb;
                data += nb;

                n -= nf;
                sp->s_head += nf;
                sp->s_hidx += nf;
                if (sp->s_hidx == nframes) {
                        sp->s_hidx = 0;
                        data = sp->s_data;
                }
        } while (n);

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_hidx < nframes);

        mutex_exit(&sp->s_lock);

        return (cnt);
}

int
auclnt_read(audio_client_t *c, struct uio *uio)
{
        audio_stream_t  *sp = &c->c_istream;
        uint_t          cnt;
        int             rv = 0;
        offset_t        loff;
        int             eagain;
        uint_t          tidx;
        uint_t          framesz;

        loff = uio->uio_loffset;
        eagain = EAGAIN;

        mutex_enter(&sp->s_lock);

        if ((!sp->s_paused) && (!sp->s_running)) {
                mutex_exit(&sp->s_lock);
                auclnt_start(sp);
                mutex_enter(&sp->s_lock);
        }


        framesz = sp->s_framesz;

        ASSERT(sp->s_head >= sp->s_tail);
        ASSERT(sp->s_tidx < sp->s_nframes);

        while (uio->uio_resid >= framesz) {

                while ((cnt = (sp->s_head - sp->s_tail)) == 0) {
                        if (uio->uio_fmode & (FNONBLOCK|FNDELAY)) {
                                mutex_exit(&sp->s_lock);
                                return (eagain);
                        }
                        if (cv_wait_sig(&sp->s_cv, &sp->s_lock) == 0) {
                                mutex_exit(&sp->s_lock);
                                return (EINTR);
                        }
                }

                tidx = sp->s_tidx;
                cnt = min(cnt, sp->s_nframes - tidx);
                cnt = min(cnt, (uio->uio_resid / framesz));

                mutex_exit(&sp->s_lock);
                rv = uiomove(sp->s_data + (tidx * framesz),
                    cnt * framesz, UIO_READ, uio);

                uio->uio_loffset = loff;
                eagain = 0;

                if (rv != 0) {
                        return (rv);
                }

                mutex_enter(&sp->s_lock);
                sp->s_tail += cnt;
                sp->s_tidx += cnt;
                if (sp->s_tidx == sp->s_nframes) {
                        sp->s_tidx = 0;
                }
        }

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_tidx < sp->s_nframes);

        /* round off any remaining partial bits */
        uio->uio_resid = 0;

        mutex_exit(&sp->s_lock);

        return (rv);
}

int
auclnt_write(audio_client_t *c, struct uio *uio)
{
        audio_stream_t *sp = &c->c_ostream;
        uint_t          cnt;
        int             rv = 0;
        offset_t        loff;
        int             eagain;
        uint_t          framesz;
        uint_t          hidx;

        loff = uio->uio_loffset;
        eagain = EAGAIN;

        mutex_enter(&sp->s_lock);

        framesz = sp->s_framesz;

        ASSERT(sp->s_head >= sp->s_tail);
        ASSERT(sp->s_hidx < sp->s_nframes);

        while (uio->uio_resid >= framesz) {

                while ((cnt = sp->s_nframes - (sp->s_head - sp->s_tail)) == 0) {
                        if (uio->uio_fmode & (FNONBLOCK|FNDELAY)) {
                                mutex_exit(&sp->s_lock);
                                return (eagain);
                        }
                        if (cv_wait_sig(&sp->s_cv, &sp->s_lock) == 0) {
                                mutex_exit(&sp->s_lock);
                                return (EINTR);
                        }
                }

                hidx = sp->s_hidx;
                cnt = min(cnt, sp->s_nframes - hidx);
                cnt = min(cnt, (uio->uio_resid / framesz));

                /*
                 * We have to drop the stream lock, because the
                 * uiomove might require doing a page in, which could
                 * get blocked behind the PIL of the audio processing
                 * thread which also grabs the s_lock.  (Hence, there
                 * is a risk of deadlock due to priority inversion.)
                 */
                mutex_exit(&sp->s_lock);

                rv = uiomove(sp->s_data + (hidx * framesz),
                    cnt * framesz, UIO_WRITE, uio);

                uio->uio_loffset = loff;
                eagain = 0;

                if (rv != 0) {
                        return (rv);
                }

                mutex_enter(&sp->s_lock);

                sp->s_head += cnt;
                sp->s_hidx += cnt;
                if (sp->s_hidx == sp->s_nframes) {
                        sp->s_hidx = 0;
                }

                if ((!sp->s_paused) && (!sp->s_running) &&
                    ((sp->s_head - sp->s_tail) > sp->s_fragfr)) {
                        mutex_exit(&sp->s_lock);
                        auclnt_start(sp);
                        mutex_enter(&sp->s_lock);
                }
        }

        ASSERT(sp->s_tail <= sp->s_head);
        ASSERT(sp->s_hidx < sp->s_nframes);

        /* round off any remaining partial bits */
        uio->uio_resid = 0;

        mutex_exit(&sp->s_lock);

        return (rv);
}

int
auclnt_chpoll(audio_client_t *c, short events, int anyyet, short *reventsp,
    struct pollhead **phpp)
{
        audio_stream_t  *sp;
        short nev = 0;

        if (events & (POLLIN | POLLRDNORM)) {
                sp = &c->c_istream;
                mutex_enter(&sp->s_lock);
                if ((sp->s_head - sp->s_tail) > sp->s_fragfr) {
                        nev = POLLIN | POLLRDNORM;
                }
                mutex_exit(&sp->s_lock);
        }

        if (events & POLLOUT) {
                sp = &c->c_ostream;
                mutex_enter(&sp->s_lock);
                if ((sp->s_nframes - (sp->s_head - sp->s_tail)) >
                    sp->s_fragfr) {
                        nev = POLLOUT;
                }
                mutex_exit(&sp->s_lock);
        }

        if (nev) {
                *reventsp = nev & events;
        } else {
                *reventsp = 0;
                if (!anyyet) {
                        *phpp = &c->c_pollhead;
                }
        }
        return (0);
}

void
auclnt_pollwakeup(audio_client_t *c, short events)
{
        pollwakeup(&c->c_pollhead, events);
}

void
auclnt_get_output_qlen(audio_client_t *c, uint_t *slen, uint_t *flen)
{
        audio_stream_t  *sp = &c->c_ostream;
        audio_engine_t  *e = sp->s_engine;
        uint64_t        el, sl;
        uint_t          cnt, er, sr;

        if (e == NULL) {
                /* if no output engine, can't do it! */
                *slen = 0;
                *flen = 0;
                return;
        }

        mutex_enter(&e->e_lock);
        mutex_enter(&sp->s_lock);
        if (e->e_ops.audio_engine_qlen != NULL) {
                el = ENG_QLEN(e) + (e->e_head - e->e_tail);
        } else {
                el = (e->e_head - e->e_tail);
        }
        er = e->e_rate;
        sl = sp->s_cnv_cnt;
        sr = sp->s_user_parms->p_rate;
        cnt = (uint_t)(sp->s_head - sp->s_tail);
        mutex_exit(&sp->s_lock);
        mutex_exit(&e->e_lock);

        /* engine frames converted to stream rate, plus stream frames */
        *slen = cnt;
        *flen = ((uint_t)(((el * sr) / er) + sl));
}

int
auclnt_set_format(audio_stream_t *sp, int fmt)
{
        audio_parms_t   parms;
        int             rv = 0;

        /*
         * AC3: If we select an AC3 format, then we have to allocate
         * another engine.  Normally this will be an output only
         * engine.  However, for now we aren't supporting AC3
         * passthru.
         */

        switch (fmt) {
        case AUDIO_FORMAT_U8:
        case AUDIO_FORMAT_ULAW:
        case AUDIO_FORMAT_ALAW:
        case AUDIO_FORMAT_S8:
        case AUDIO_FORMAT_S16_LE:
        case AUDIO_FORMAT_S16_BE:
        case AUDIO_FORMAT_U16_LE:
        case AUDIO_FORMAT_U16_BE:
        case AUDIO_FORMAT_S24_LE:
        case AUDIO_FORMAT_S24_BE:
        case AUDIO_FORMAT_S32_LE:
        case AUDIO_FORMAT_S32_BE:
        case AUDIO_FORMAT_S24_PACKED:
                break;

        case AUDIO_FORMAT_AC3:          /* AC3: PASSTHRU */
        default:
                return (ENOTSUP);
        }


        /*
         * Optimization.  Some personalities send us the same format
         * over and over again.  (Sun personality does this
         * repeatedly.)  setup_src is potentially expensive, so we
         * avoid doing it unless we really need to.
         */
        if (fmt != sp->s_user_parms->p_format) {
                /*
                 * Note that setting the format doesn't check that the
                 * audio streams have been paused.  As a result, any
                 * data still playing or recording will probably get
                 * misinterpreted.  It would be smart if the client
                 * application paused/stopped playback before changing
                 * formats.
                 */
                parms.p_format = fmt;
                rv = auimpl_engine_setup(sp, 0, &parms, FORMAT_MSK_FMT);
        }

        return (rv);
}

int
auclnt_get_format(audio_stream_t *sp)
{
        return (sp->s_user_parms->p_format);
}

int
auclnt_get_output_format(audio_client_t *c)
{
        return (c->c_ostream.s_user_parms->p_format);
}

int
auclnt_get_input_format(audio_client_t *c)
{
        return (c->c_istream.s_user_parms->p_format);
}

int
auclnt_set_channels(audio_stream_t *sp, int nchan)
{
        audio_parms_t   parms;
        int             rv = 0;

        /* Validate setting */
        if ((nchan > AUDIO_MAX_CHANNELS) || (nchan < 1)) {
                return (EINVAL);
        }

        if (nchan != sp->s_user_parms->p_nchan) {
                parms.p_nchan = nchan;
                rv = auimpl_engine_setup(sp, 0, &parms, FORMAT_MSK_CHAN);
        }

        return (rv);
}

int
auclnt_get_channels(audio_stream_t *sp)
{
        return (sp->s_user_parms->p_nchan);
}


static void
auimpl_set_gain_master(audio_stream_t *sp, uint8_t gain)
{
        uint32_t        scaled;

        if (gain > 100) {
                gain = 0;
        }

        mutex_enter(&sp->s_lock);
        if (sp->s_gain_master == gain) {
                mutex_exit(&sp->s_lock);
                return;
        }

        /*
         * calculate the scaled values.  Done now to avoid calculations
         * later.
         */
        scaled = (gain * sp->s_gain_pct * AUDIO_DB_SIZE) / (100 * 100);

        sp->s_gain_master = gain;
        sp->s_gain_scaled = auimpl_db_table[scaled];

        if (!sp->s_muted) {
                sp->s_gain_eff = sp->s_gain_scaled;
        }
        mutex_exit(&sp->s_lock);
}

int
auimpl_set_pcmvol(void *arg, uint64_t val)
{
        audio_dev_t     *d = arg;
        list_t          *l = &d->d_clients;
        audio_client_t  *c;

        if (val > 100) {
                return (EINVAL);
        }
        rw_enter(&auimpl_client_lock, RW_WRITER);
        d->d_pcmvol = val & 0xff;
        rw_downgrade(&auimpl_client_lock);

        for (c = list_head(l); c; c = list_next(l, c)) {
                /* don't need to check is_active here, its safe */
                auimpl_set_gain_master(&c->c_ostream, (uint8_t)val);
        }
        rw_exit(&auimpl_client_lock);

        return (0);
}

int
auimpl_get_pcmvol(void *arg, uint64_t *val)
{
        audio_dev_t     *d = arg;

        *val = d->d_pcmvol;
        return (0);
}

void
auclnt_set_gain(audio_stream_t *sp, uint8_t gain)
{
        uint32_t        scaled;

        if (gain > 100) {
                gain = 0;
        }

        mutex_enter(&sp->s_lock);

        /* if no change, don't bother doing updates */
        if (sp->s_gain_pct == gain) {
                mutex_exit(&sp->s_lock);
                return;
        }

        /*
         * calculate the scaled values.  Done now to avoid calculations
         * later.
         */
        scaled = (gain * sp->s_gain_master * AUDIO_DB_SIZE) / (100 * 100);

        sp->s_gain_pct = gain;
        sp->s_gain_scaled = auimpl_db_table[scaled];

        if (!sp->s_muted) {
                sp->s_gain_eff = sp->s_gain_scaled;
        }
        mutex_exit(&sp->s_lock);

        atomic_inc_uint(&sp->s_client->c_dev->d_serial);
}

uint8_t
auclnt_get_gain(audio_stream_t *sp)
{
        return (sp->s_gain_pct);
}

void
auclnt_set_muted(audio_stream_t *sp, boolean_t muted)
{
        mutex_enter(&sp->s_lock);

        /* if no work change, don't bother doing updates */
        if (sp->s_muted == muted) {
                mutex_exit(&sp->s_lock);
                return;
        }

        sp->s_muted = muted;
        if (muted) {
                sp->s_gain_eff = 0;
        } else {
                sp->s_gain_eff = sp->s_gain_scaled;
        }
        mutex_exit(&sp->s_lock);

        atomic_inc_uint(&sp->s_client->c_dev->d_serial);
}

boolean_t
auclnt_get_muted(audio_stream_t *sp)
{
        return (sp->s_muted);
}

boolean_t
auclnt_is_running(audio_stream_t *sp)
{
        return (sp->s_running);
}

void
auclnt_start(audio_stream_t *sp)
{
        mutex_enter(&sp->s_lock);
        sp->s_running = B_TRUE;
        mutex_exit(&sp->s_lock);
}

void
auclnt_stop(audio_stream_t *sp)
{
        mutex_enter(&sp->s_lock);
        /* if running, then stop it */
        if (sp->s_running) {
                sp->s_running = B_FALSE;
                /*
                 * if we stopped the engine, we might need to wake up
                 * a thread that is waiting for drain to complete.
                 */
                cv_broadcast(&sp->s_cv);
        }
        mutex_exit(&sp->s_lock);
}

/*
 * When pausing, no new data will be played after the most recently
 * mixed samples have played.  However, the audio engine will continue
 * to play (possibly just silence).
 *
 * Note that we don't reference count the device, or release/close the
 * engine here.  Once fired up, the engine continues running unil it
 * is closed.
 */
void
auclnt_set_paused(audio_stream_t *sp)
{
        mutex_enter(&sp->s_lock);
        if (sp->s_paused) {
                mutex_exit(&sp->s_lock);
                return;
        }
        sp->s_paused = B_TRUE;
        mutex_exit(&sp->s_lock);

        auclnt_stop(sp);

        atomic_inc_uint(&sp->s_client->c_dev->d_serial);
}

void
auclnt_clear_paused(audio_stream_t *sp)
{
        mutex_enter(&sp->s_lock);
        if (!sp->s_paused) {
                mutex_exit(&sp->s_lock);
                return;
        }
        sp->s_paused = B_FALSE;
        mutex_exit(&sp->s_lock);
}

boolean_t
auclnt_is_paused(audio_stream_t *sp)
{
        return (sp->s_paused);
}

void
auclnt_flush(audio_stream_t *sp)
{
        mutex_enter(&sp->s_lock);
        if (sp == &sp->s_client->c_ostream) {
                sp->s_tail = sp->s_head;
                sp->s_tidx = sp->s_hidx;
        } else {
                sp->s_head = sp->s_tail;
                sp->s_hidx = sp->s_tidx;
        }
        sp->s_cnv_cnt = 0;
        mutex_exit(&sp->s_lock);
}

int
auclnt_get_oflag(audio_client_t *c)
{
        return (c->c_omode);
}

/*
 * These routines should not be accessed by client "personality"
 * implementations, but are for private framework use only.
 */

void
auimpl_client_init(void)
{
        rw_init(&auimpl_client_lock, NULL, RW_DRIVER, NULL);
        list_create(&auimpl_clients, sizeof (struct audio_client),
            offsetof(struct audio_client, c_global_linkage));
}

void
auimpl_client_fini(void)
{
        rw_destroy(&auimpl_client_lock);
        list_destroy(&auimpl_clients);
}

static int
auimpl_stream_init(audio_stream_t *sp, audio_client_t *c)
{
        mutex_init(&sp->s_lock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&sp->s_cv, NULL, CV_DRIVER, NULL);
        sp->s_client = c;

        if (sp == &c->c_ostream) {
                sp->s_user_parms = &sp->s_cnv_src_parms;
                sp->s_phys_parms = &sp->s_cnv_dst_parms;
                sp->s_engcap = ENGINE_OUTPUT_CAP;
        } else {
                ASSERT(sp == &c->c_istream);
                sp->s_user_parms = &sp->s_cnv_dst_parms;
                sp->s_phys_parms = &sp->s_cnv_src_parms;
                sp->s_engcap = ENGINE_INPUT_CAP;
        }

        /* for now initialize conversion parameters */
        sp->s_src_quality = 3;  /* reasonable compromise for now */
        sp->s_cnv_dst_nchan = 2;
        sp->s_cnv_dst_format = AUDIO_FORMAT_S24_NE;
        sp->s_cnv_dst_rate = 48000;
        sp->s_cnv_src_nchan = 2;
        sp->s_cnv_src_format = AUDIO_FORMAT_S24_NE;
        sp->s_cnv_src_rate = 48000;

        /* set volume/gain all the way up */
        sp->s_muted = B_FALSE;
        sp->s_gain_pct = 0;
        sp->s_gain_scaled = AUDIO_VOL_SCALE;
        sp->s_gain_eff = AUDIO_VOL_SCALE;

        /*
         * We have to start off with a reasonable buffer and
         * interrupt configuration.
         */
        sp->s_allocsz = 65536;
        sp->s_data = ddi_umem_alloc(sp->s_allocsz, DDI_UMEM_NOSLEEP,
            &sp->s_cookie);
        if (sp->s_data == NULL) {
                sp->s_allocsz = 0;
                audio_dev_warn(c->c_dev, "ddi_umem_alloc failed");
                return (ENOMEM);
        }
        /* make sure no stale data left in stream */
        bzero(sp->s_data, sp->s_allocsz);

        /*
         * Allocate SRC and data conversion state.
         */
        mutex_enter(&sp->s_lock);
        if (auimpl_format_alloc(sp) != 0) {
                mutex_exit(&sp->s_lock);
                return (ENOMEM);
        }

        mutex_exit(&sp->s_lock);

        return (0);
}


static void
audio_stream_fini(audio_stream_t *sp)
{
        auimpl_format_free(sp);
        if (sp->s_cnv_buf0)
                kmem_free(sp->s_cnv_buf0, sp->s_cnv_max);
        if (sp->s_cnv_buf1)
                kmem_free(sp->s_cnv_buf1, sp->s_cnv_max);
        mutex_destroy(&sp->s_lock);
        cv_destroy(&sp->s_cv);
        if (sp->s_data != NULL) {
                ddi_umem_free(sp->s_cookie);
                sp->s_data = NULL;
        }
}

int
auclnt_start_drain(audio_client_t *c)
{
        audio_stream_t  *sp;
        int             rv;

        sp = &c->c_ostream;

        /* start an asynchronous drain operation. */
        mutex_enter(&sp->s_lock);
        if (sp->s_paused || !sp->s_running) {
                rv = EALREADY;
        } else {
                sp->s_draining = B_TRUE;
                rv = 0;
        }
        mutex_exit(&sp->s_lock);
        return (rv);
}

int
auclnt_drain(audio_client_t *c)
{
        audio_stream_t  *sp;

        sp = &c->c_ostream;

        /*
         * Note: Drain logic will automatically "stop" the stream when
         * the drain threshold has been reached.  So all we have to do
         * is wait for the stream to stop.
         */
        mutex_enter(&sp->s_lock);
        sp->s_draining = B_TRUE;
        while (sp->s_draining && sp->s_running && !sp->s_paused) {
                if (cv_wait_sig(&sp->s_cv, &sp->s_lock) == 0) {
                        mutex_exit(&sp->s_lock);
                        return (EINTR);
                }
        }
        mutex_exit(&sp->s_lock);
        return (0);
}

audio_client_t *
auimpl_client_create(dev_t dev)
{
        audio_client_ops_t      *ops;
        audio_client_t          *c;
        audio_client_t          *next;
        list_t                  *list = &auimpl_clients;
        minor_t                 minor;
        audio_dev_t             *d;

        /* validate minor number */
        minor = getminor(dev) & AUDIO_MN_TYPE_MASK;
        if ((ops = audio_client_ops[minor]) == NULL) {
                return (NULL);
        }

        /* lookup device instance */
        if ((d = auimpl_dev_hold_by_devt(dev)) == NULL) {
                audio_dev_warn(NULL, "no audio_dev for dev_t %d,%d",
                    getmajor(dev), getminor(dev));
                return (NULL);
        }

        if ((c = kmem_zalloc(sizeof (*c), KM_NOSLEEP)) == NULL) {
                audio_dev_warn(d, "unable to allocate client structure");
                auimpl_dev_release(d);
                return (NULL);
        }
        c->c_dev = d;

        mutex_init(&c->c_lock, NULL, MUTEX_DRIVER, NULL);
        cv_init(&c->c_cv, NULL, CV_DRIVER, NULL);

        if ((auimpl_stream_init(&c->c_ostream, c) != 0) ||
            (auimpl_stream_init(&c->c_istream, c) != 0)) {
                goto failed;
        }

        c->c_major =            getmajor(dev);
        c->c_origminor =        getminor(dev);
        c->c_ops =              *ops;

        /*
         * We hold the client lock here.
         */
        rw_enter(&auimpl_client_lock, RW_WRITER);

        minor = AUDIO_MN_CLONE_MASK;
        for (next = list_head(list); next; next = list_next(list, next)) {
                if (next->c_minor > minor) {
                        break;
                }
                minor++;
        }
        if (minor >= MAXMIN32) {
                rw_exit(&auimpl_client_lock);
                goto failed;
        }
        c->c_minor = minor;
        list_insert_before(list, next, c);

        rw_exit(&auimpl_client_lock);


        return (c);

failed:
        auimpl_dev_release(d);
        audio_stream_fini(&c->c_ostream);
        audio_stream_fini(&c->c_istream);
        mutex_destroy(&c->c_lock);
        cv_destroy(&c->c_cv);
        kmem_free(c, sizeof (*c));
        return (NULL);
}

void
auimpl_client_destroy(audio_client_t *c)
{
        /* remove us from the global list */
        rw_enter(&auimpl_client_lock, RW_WRITER);
        list_remove(&auimpl_clients, c);
        rw_exit(&auimpl_client_lock);

        ASSERT(!c->c_istream.s_running);
        ASSERT(!c->c_ostream.s_running);

        /* release the device reference count */
        auimpl_dev_release(c->c_dev);
        c->c_dev = NULL;

        mutex_destroy(&c->c_lock);
        cv_destroy(&c->c_cv);

        audio_stream_fini(&c->c_istream);
        audio_stream_fini(&c->c_ostream);
        kmem_free(c, sizeof (*c));
}

void
auimpl_client_activate(audio_client_t *c)
{
        rw_enter(&auimpl_client_lock, RW_WRITER);
        c->c_is_active = B_TRUE;
        rw_exit(&auimpl_client_lock);
}

void
auimpl_client_deactivate(audio_client_t *c)
{
        rw_enter(&auimpl_client_lock, RW_WRITER);
        c->c_is_active = B_FALSE;
        rw_exit(&auimpl_client_lock);
}

void
auclnt_close(audio_client_t *c)
{
        audio_dev_t     *d = c->c_dev;

        /* stop the engines if they are running */
        auclnt_stop(&c->c_istream);
        auclnt_stop(&c->c_ostream);

        rw_enter(&auimpl_client_lock, RW_WRITER);
        list_remove(&d->d_clients, c);
        rw_exit(&auimpl_client_lock);

        mutex_enter(&c->c_lock);
        /* if in transition need to wait for other thread to release */
        while (c->c_refcnt) {
                cv_wait(&c->c_cv, &c->c_lock);
        }
        mutex_exit(&c->c_lock);

        /* release any engines that we were holding */
        auimpl_engine_close(&c->c_ostream);
        auimpl_engine_close(&c->c_istream);
}

audio_dev_t *
auclnt_hold_dev_by_index(int index)
{
        return (auimpl_dev_hold_by_index(index));
}

void
auclnt_release_dev(audio_dev_t *dev)
{
        auimpl_dev_release(dev);
}

audio_client_t *
auclnt_hold_by_devt(dev_t dev)
{
        minor_t mn = getminor(dev);
        major_t mj = getmajor(dev);
        list_t *list;
        audio_client_t *c;

        list = &auimpl_clients;
        /* linked list search is kind of inefficient, but it works */
        rw_enter(&auimpl_client_lock, RW_READER);
        for (c = list_head(list); c != NULL; c = list_next(list, c)) {
                if ((c->c_major == mj) && (c->c_minor == mn)) {
                        mutex_enter(&c->c_lock);
                        if (c->c_is_active) {
                                c->c_refcnt++;
                                mutex_exit(&c->c_lock);
                        } else {
                                mutex_exit(&c->c_lock);
                                c = NULL;
                        }
                        break;
                }
        }
        rw_exit(&auimpl_client_lock);
        return (c);
}

int
auclnt_serialize(audio_client_t *c)
{
        mutex_enter(&c->c_lock);
        while (c->c_serialize) {
                if (cv_wait_sig(&c->c_cv, &c->c_lock) == 0) {
                        mutex_exit(&c->c_lock);
                        return (EINTR);
                }
        }
        c->c_serialize = B_TRUE;
        mutex_exit(&c->c_lock);
        return (0);
}

void
auclnt_unserialize(audio_client_t *c)
{
        mutex_enter(&c->c_lock);
        ASSERT(c->c_serialize);
        c->c_serialize = B_FALSE;
        cv_broadcast(&c->c_cv);
        mutex_exit(&c->c_lock);
}

void
auclnt_hold(audio_client_t *c)
{
        mutex_enter(&c->c_lock);
        c->c_refcnt++;
        mutex_exit(&c->c_lock);
}

void
auclnt_release(audio_client_t *c)
{
        mutex_enter(&c->c_lock);
        ASSERT(c->c_refcnt > 0);
        c->c_refcnt--;
        if (c->c_refcnt == 0)
                cv_broadcast(&c->c_cv);
        mutex_exit(&c->c_lock);
}

uint_t
auclnt_dev_get_serial(audio_dev_t *d)
{
        return (d->d_serial);
}

void
auclnt_dev_walk_clients(audio_dev_t *d,
    int (*walker)(audio_client_t *, void *),
    void *arg)
{
        list_t          *l = &d->d_clients;
        audio_client_t  *c;
        int             rv;

        rw_enter(&auimpl_client_lock, RW_READER);
restart:
        for (c = list_head(l); c != NULL; c = list_next(l, c)) {
                if (!c->c_is_active)
                        continue;
                rv = (walker(c, arg));
                if (rv == AUDIO_WALK_STOP) {
                        break;
                } else if (rv == AUDIO_WALK_RESTART) {
                        goto restart;
                }
        }
        rw_exit(&auimpl_client_lock);
}


int
auclnt_open(audio_client_t *c, int oflag)
{
        audio_stream_t  *sp;
        audio_dev_t     *d = c->c_dev;
        int             rv = 0;
        int             flags;

        flags = 0;
        if (oflag & FNDELAY)
                flags |= ENGINE_NDELAY;

        if (oflag & FWRITE) {
                sp = &c->c_ostream;
                if ((rv = auimpl_engine_open(sp, flags | ENGINE_OUTPUT)) != 0)
                        goto done;
        }

        if (oflag & FREAD) {
                sp = &c->c_istream;
                if ((rv = auimpl_engine_open(sp, flags | ENGINE_INPUT)) != 0)
                        goto done;
        }

done:
        if (rv != 0) {
                /* close any engines that we opened */
                auimpl_engine_close(&c->c_ostream);
                auimpl_engine_close(&c->c_istream);
        } else {
                rw_enter(&auimpl_client_lock, RW_WRITER);
                list_insert_tail(&d->d_clients, c);
                c->c_ostream.s_gain_master = d->d_pcmvol;
                c->c_istream.s_gain_master = 100;
                rw_exit(&auimpl_client_lock);
                auclnt_set_gain(&c->c_ostream, 100);
                auclnt_set_gain(&c->c_istream, 100);
        }

        return (rv);
}

minor_t
auclnt_get_minor(audio_client_t *c)
{
        return (c->c_minor);
}

minor_t
auclnt_get_original_minor(audio_client_t *c)
{
        return (c->c_origminor);
}

minor_t
auclnt_get_minor_type(audio_client_t *c)
{
        return (c->c_origminor & AUDIO_MN_TYPE_MASK);
}

queue_t *
auclnt_get_rq(audio_client_t *c)
{
        return (c->c_rq);
}

queue_t *
auclnt_get_wq(audio_client_t *c)
{
        return (c->c_wq);
}

pid_t
auclnt_get_pid(audio_client_t *c)
{
        return (c->c_pid);
}

cred_t *
auclnt_get_cred(audio_client_t *c)
{
        return (c->c_cred);
}

audio_dev_t *
auclnt_get_dev(audio_client_t *c)
{
        return (c->c_dev);
}

int
auclnt_get_dev_number(audio_dev_t *dev)
{
        return (dev->d_number);
}

int
auclnt_get_dev_index(audio_dev_t *dev)
{
        return (dev->d_index);
}

const char *
auclnt_get_dev_name(audio_dev_t *dev)
{
        return (dev->d_name);
}

const char *
auclnt_get_dev_driver(audio_dev_t *dev)
{
        return (ddi_driver_name(dev->d_dip));
}

dev_info_t *
auclnt_get_dev_devinfo(audio_dev_t *dev)
{
        return (dev->d_dip);
}

const char *
auclnt_get_dev_hw_info(audio_dev_t *dev, void **iter)
{
        struct audio_infostr *isp = *iter;
        if (isp == NULL) {
                isp = list_head(&dev->d_hwinfo);
        } else {
                isp = list_next(&dev->d_hwinfo, isp);
        }

        *iter = isp;
        return (isp ? isp->i_line : NULL);
}

int
auclnt_get_dev_instance(audio_dev_t *dev)
{
        return (dev->d_instance);
}

const char *
auclnt_get_dev_description(audio_dev_t *dev)
{
        return (dev->d_desc);
}

const char *
auclnt_get_dev_version(audio_dev_t *dev)
{
        return (dev->d_vers);
}

uint_t
auclnt_get_dev_capab(audio_dev_t *dev)
{
        uint32_t        flags;
        uint_t          caps = 0;

        flags = dev->d_flags;

        if (flags & DEV_OUTPUT_CAP)
                caps |= AUDIO_CLIENT_CAP_PLAY;
        if (flags & DEV_INPUT_CAP)
                caps |= AUDIO_CLIENT_CAP_RECORD;
        if (flags & DEV_DUPLEX_CAP)
                caps |= AUDIO_CLIENT_CAP_DUPLEX;

        /* AC3: deal with formats that don't support mixing */

        return (caps);
}

uint64_t
auclnt_get_samples(audio_stream_t *sp)
{
        uint64_t        n;

        mutex_enter(&sp->s_lock);
        n = sp->s_samples;
        mutex_exit(&sp->s_lock);
        return (n);
}

void
auclnt_set_samples(audio_stream_t *sp, uint64_t n)
{
        mutex_enter(&sp->s_lock);
        sp->s_samples = n;
        mutex_exit(&sp->s_lock);
}

uint64_t
auclnt_get_errors(audio_stream_t *sp)
{
        uint64_t        n;
        mutex_enter(&sp->s_lock);
        n = sp->s_errors;
        mutex_exit(&sp->s_lock);
        return (n);
}

void
auclnt_set_errors(audio_stream_t *sp, uint64_t n)
{
        mutex_enter(&sp->s_lock);
        sp->s_errors = n;
        mutex_exit(&sp->s_lock);
}

void
auclnt_register_ops(minor_t minor, audio_client_ops_t *ops)
{
        /* we control minor number allocations, no need for runtime checks */
        ASSERT(minor <= AUDIO_MN_TYPE_MASK);

        audio_client_ops[minor] = ops;
}

int
auimpl_create_minors(audio_dev_t *d)
{
        char                    path[MAXPATHLEN];
        int                     rv = 0;
        minor_t                 minor;
        audio_client_ops_t      *ops;
        char                    *nt;

        for (int i = 0; i <= AUDIO_MN_TYPE_MASK; i++) {

                if ((ops = audio_client_ops[i]) == NULL)
                        continue;

                if (ops->aco_dev_init != NULL)
                        d->d_minor_data[i] = ops->aco_dev_init(d);

                switch (i) {
                case AUDIO_MINOR_SNDSTAT:
                        if (!(d->d_flags & DEV_SNDSTAT_CAP)) {
                                continue;
                        }
                        nt = DDI_PSEUDO;
                        break;

                default:
                        if (!(d->d_flags & (DEV_INPUT_CAP| DEV_OUTPUT_CAP))) {
                                continue;
                        }
                        nt = DDI_NT_AUDIO;
                        break;
                }

                if (ops->aco_minor_prefix != NULL) {

                        minor = AUDIO_MKMN(d->d_instance, i);
                        (void) snprintf(path, sizeof (path),
                            "%s%d", ops->aco_minor_prefix, d->d_instance);

                        rv = ddi_create_minor_node(d->d_dip, path, S_IFCHR,
                            minor, nt, 0);

                        if (rv != 0)
                                break;
                }
        }
        return (rv);
}

void
auimpl_remove_minors(audio_dev_t *d)
{
        char                    path[MAXPATHLEN];
        audio_client_ops_t      *ops;

        for (int i = 0; i <= AUDIO_MN_TYPE_MASK; i++) {
                if ((ops = audio_client_ops[i]) == NULL)
                        continue;
                if (ops->aco_minor_prefix != NULL) {
                        (void) snprintf(path, sizeof (path), "%s%d",
                            ops->aco_minor_prefix, d->d_instance);
                        (void) ddi_remove_minor_node(d->d_dip, path);
                }

                if (ops->aco_dev_fini != NULL)
                        ops->aco_dev_fini(d->d_minor_data[i]);
        }
}

void *
auclnt_get_dev_minor_data(audio_dev_t *d, minor_t mn)
{
        ASSERT(mn < (1U << AUDIO_MN_TYPE_NBITS));
        return (d->d_minor_data[mn]);
}

void *
auclnt_get_minor_data(audio_client_t *c, minor_t mn)
{
        ASSERT(mn < (1U << AUDIO_MN_TYPE_NBITS));
        return (c->c_dev->d_minor_data[mn]);
}

/*
 * This will walk all controls registered to a clients device and callback
 * to walker for each one with its audio_ctrl. Note this data
 * must be considered read only by walker.
 *
 * Note that walk_func may return values to continue (AUDIO_WALK_CONTINUE)
 * or stop walk (AUDIO_WALK_STOP).
 *
 */
void
auclnt_walk_controls(audio_dev_t *d,
    int (*walker)(audio_ctrl_t *, void *),
    void *arg)
{
        audio_ctrl_t *ctrl;

        mutex_enter(&d->d_ctrl_lock);
        for (ctrl = list_head(&d->d_controls); ctrl;
            ctrl = list_next(&d->d_controls, ctrl)) {
                if (walker(ctrl, arg) == AUDIO_WALK_STOP)
                        break;
        }
        mutex_exit(&d->d_ctrl_lock);
}

/*
 * This will search all controls attached to an
 * audio device for a control with the desired name.
 *
 * d    - the audio device to look on
 * name - name of the control being looked for.
 *
 * On successful return a ctrl handle will be returned. On
 * failure NULL is returned.
 */
audio_ctrl_t *
auclnt_find_control(audio_dev_t *d, const char *name)
{
        audio_ctrl_t *ctrl;

        /* Verify argument */
        ASSERT(d);

        mutex_enter(&d->d_ctrl_lock);
        for (ctrl = list_head(&d->d_controls); ctrl;
            ctrl = list_next(&d->d_controls, ctrl)) {
                if (strcmp(ctrl->ctrl_name, name) == 0) {
                        mutex_exit(&d->d_ctrl_lock);
                        return (ctrl);
                }
        }
        mutex_exit(&d->d_ctrl_lock);
        return (NULL);
}

/*
 * Given a known control, get its attributes.
 *
 * The caller must supply a audio_ctrl_desc_t structure.  Also the
 * values in the structure are ignored when making the call and filled
 * in by this function. All data pointed to by elements of desc should
 * be assumed read only.
 *
 * If an error occurs then a non-zero is returned.
 *
 */
int
auclnt_control_describe(audio_ctrl_t *ctrl, audio_ctrl_desc_t *desc)
{
        ASSERT(ctrl);
        ASSERT(desc);

        bcopy(&ctrl->ctrl_des, desc, sizeof (*desc));
        return (0);
}

int
auclnt_control_read(audio_ctrl_t *ctrl, uint64_t *value)
{
        return (audio_control_read(ctrl, value));
}

int
auclnt_control_write(audio_ctrl_t *ctrl, uint64_t value)
{
        return (audio_control_write(ctrl, value));
}

void
auclnt_warn(audio_client_t *c, const char *fmt, ...)
{
        va_list va;

        va_start(va, fmt);
        auimpl_dev_vwarn(c ? c->c_dev : NULL, fmt, va);
        va_end(va);
}