audio_client.c revision 68c47f65208790c466e5e484f2293d3baed71c6a
/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#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);
}
mutex_enter(&sp->s_lock);
parms = *sp->s_user_parms;
if (rate != parms.p_rate) {
parms.p_rate = rate;
rv = auimpl_format_setup(sp, &parms);
}
mutex_exit(&sp->s_lock);
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);
}
mutex_enter(&sp->s_lock);
parms = *sp->s_user_parms;
/*
* 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 != 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_format_setup(sp, &parms);
}
mutex_exit(&sp->s_lock);
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);
}
mutex_enter(&sp->s_lock);
parms = *sp->s_user_parms;
if (nchan != parms.p_nchan) {
parms.p_nchan = nchan;
rv = auimpl_format_setup(sp, &parms);
}
mutex_exit(&sp->s_lock);
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, uint_t fmts, int oflag)
{
audio_stream_t *sp;
audio_dev_t *d = c->c_dev;
int rv = 0;
int flags;
audio_parms_t parms;
flags = 0;
if (oflag & FNDELAY)
flags |= ENGINE_NDELAY;
if (oflag & FWRITE) {
sp = &c->c_ostream;
rv = auimpl_engine_open(d, fmts, flags | ENGINE_OUTPUT, sp);
if (rv != 0) {
goto done;
}
mutex_enter(&sp->s_lock);
parms = *sp->s_user_parms;
rv = auimpl_format_setup(sp, &parms);
mutex_exit(&sp->s_lock);
if (rv != 0) {
goto done;
}
}
if (oflag & FREAD) {
sp = &c->c_istream;
rv = auimpl_engine_open(d, fmts, flags | ENGINE_INPUT, sp);
if (rv != 0) {
goto done;
}
mutex_enter(&sp->s_lock);
parms = *sp->s_user_parms;
rv = auimpl_format_setup(sp, &parms);
mutex_exit(&sp->s_lock);
if (rv != 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);
}