/*
 * 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_istream.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);
}