audio_engine.c revision ea1b934f6e05788b68ee3d5f5d2c6c35683854db
/*
* 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
* 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/sysmacros.h>
#include "audio_impl.h"
/*
* Audio Engine functions.
*/
{
audio_dev_t *d;
/*
* For a card with multiple independent audio ports on it, we
* allow the driver to provide a different instance numbering
* scheme than the standard DDI instance number. (This is
* sort of like the PPA numbering scheme used by NIC drivers
* -- by default PPA == instance, but sometimes we need more
* flexibility.)
*/
if (instance == 0) {
}
/* generally this shouldn't occur */
if (instance > AUDIO_MN_INST_MASK) {
return (NULL);
}
return (NULL);
}
d->d_number = -1;
d->d_instance = instance;
d->d_pcmvol = 100;
return (d);
}
void
{
struct audio_infostr *isp;
}
if (d->d_pcmvol_ctrl != NULL) {
}
list_destroy(&d->d_hwinfo);
list_destroy(&d->d_engines);
list_destroy(&d->d_controls);
list_destroy(&d->d_clients);
rw_destroy(&d->d_ctrl_lock);
mutex_destroy(&d->d_lock);
cv_destroy(&d->d_cv);
kmem_free(d, sizeof (*d));
}
void
{
}
void
{
}
void
{
struct audio_infostr *isp;
/* failure to add information structure is not critical */
audio_dev_warn(d, "unable to allocate information structure");
} else {
}
}
void
{
mutex_enter(&e->e_lock);
/* want more data than we have, not much we can do */
e->e_errors++;
e->e_underruns++;
}
mutex_exit(&e->e_lock);
}
void
{
mutex_enter(&e->e_lock);
/* no room for engine data, not much we can do */
e->e_errors++;
e->e_overruns++;
}
mutex_exit(&e->e_lock);
}
void
{
char *buf;
char *ptr;
int nfr;
int tail;
/* engine not open, nothing to do */
return;
}
mutex_enter(&e->e_lock);
while (nfr) {
int cnt;
int nbytes;
}
}
if (e->e_flags & ENGINE_INPUT) {
/* record */
e->e_hidx = 0;
} else {
/* play */
e->e_tidx = 0;
}
/* relocate from scratch area to destination */
mutex_exit(&e->e_lock);
}
{
int i;
audio_engine_t *e;
return (NULL);
}
/* NB: The ops vector must be held in persistent storage! */
if (e == NULL) {
return (NULL);
}
for (i = 0; i < AUDIO_MAX_CHANNELS; i++) {
return (NULL);
}
}
return (e);
}
void
{
int i;
for (i = 0; i < AUDIO_MAX_CHANNELS; i++) {
sizeof (int32_t) * AUDIO_CHBUFS);
}
}
list_destroy(&e->e_streams);
mutex_destroy(&e->e_lock);
kmem_free(e, sizeof (*e));
}
static list_t auimpl_devs_by_index;
static list_t auimpl_devs_by_number;
static krwlock_t auimpl_dev_lock;
/*
* Not for public consumption: Private interfaces.
*/
void
{
/* bump the reference count */
mutex_enter(&d->d_lock);
d->d_refcnt++;
mutex_exit(&d->d_lock);
}
{
audio_dev_t *d;
int instance;
list_t *l = &auimpl_devs_by_index;
auimpl_dev_hold(d);
break;
}
}
return (d);
}
{
audio_dev_t *d;
list_t *l = &auimpl_devs_by_index;
auimpl_dev_hold(d);
break;
}
}
return (d);
}
void
{
mutex_enter(&d->d_lock);
d->d_refcnt--;
mutex_exit(&d->d_lock);
}
int
auimpl_choose_format(int fmts)
{
/*
* Choose the very best format we can. We choose 24 bit in
* preference to 32 bit because we mix in 24 bit. We do that
* to allow overflows to fit within 32-bits. (Very few humans
* can tell a difference between 24 and 32 bit audio anyway.)
*/
if (fmts & AUDIO_FORMAT_S24_NE)
return (AUDIO_FORMAT_S24_NE);
if (fmts & AUDIO_FORMAT_S32_NE)
return (AUDIO_FORMAT_S32_NE);
if (fmts & AUDIO_FORMAT_S24_OE)
return (AUDIO_FORMAT_S24_OE);
if (fmts & AUDIO_FORMAT_S32_OE)
return (AUDIO_FORMAT_S32_OE);
if (fmts & AUDIO_FORMAT_S16_NE)
return (AUDIO_FORMAT_S16_NE);
if (fmts & AUDIO_FORMAT_S16_OE)
return (AUDIO_FORMAT_S16_OE);
if (fmts & AUDIO_FORMAT_AC3)
return (AUDIO_FORMAT_AC3);
return (AUDIO_FORMAT_NONE);
}
int
{
audio_engine_t *e = NULL;
unsigned caps;
int priority = 0;
int sampsz;
int i;
/*
* Engine selection:
*
* We try hard to avoid consuming an engine that can be used
* for another purpose.
*
*/
/*
* Which direction are we opening. (We must open exactly
* one direction, otherwise the open is meaningless.)
*/
if (flags & ENGINE_OUTPUT)
else if (flags & ENGINE_INPUT)
else
return (EINVAL);
mutex_enter(&d->d_lock);
/*
* First we want to know if we already have "default" input
* and output engines.
*/
int mypri;
/* make sure the engine can do what we want it to */
mutex_enter(&t->e_lock);
((ENG_FORMAT(t) & fmts) == 0)) {
mutex_exit(&t->e_lock);
continue;
}
/* if engine is in exclusive use, can't do it */
if (t->e_flags & ENGINE_EXCLUSIVE) {
mutex_exit(&t->e_lock);
continue;
}
/* if engine is in incompatible use, can't do it */
mutex_exit(&t->e_lock);
continue;
}
/*
* In order to support as many different possible
* output streams (e.g. AC3 passthru or AC3 decode),
* or multiple exclusive outputs, we treat audio
* engines as *precious*.
*
* This means that we will try hard to reuse an
* existing allocated engine. This may not be the
* optimal performance configuration (especially if we
* wanted to avoid rate conversion, for example), but
* it should have fewer cases where the configuration
* results in denying service to any client.
*/
rv = 0;
mypri = 2000;
/* try not to pick on idle engines */
if (list_is_empty(&t->e_streams)) {
mypri -= 1000;
}
/* try not to pick on duplex engines first */
mypri -= 100;
}
/* try not to pick on engines that can do other formats */
mypri -= 10;
}
if (e != NULL) {
mutex_exit(&e->e_lock);
}
e = t;
} else {
mutex_exit(&t->e_lock);
}
}
mutex_exit(&d->d_lock);
return (EINTR);
}
goto again;
}
if (rv != 0) {
mutex_exit(&d->d_lock);
return (rv);
}
/*
* If the engine is already open, there is no need for further
* work. The first open will be relatively expensive, but
* subsequent opens should be as cheap as possible.
*/
if (!list_is_empty(&e->e_streams)) {
rv = 0;
goto ok;
}
e->e_format = ENG_FORMAT(e);
e->e_nchan = ENG_CHANNELS(e);
/* Find out the "best" sample format supported by the device */
switch (e->e_format) {
case AUDIO_FORMAT_S24_NE:
e->e_export = auimpl_export_24ne;
e->e_import = auimpl_import_24ne;
sampsz = 4;
break;
case AUDIO_FORMAT_S32_NE:
e->e_export = auimpl_export_32ne;
e->e_import = auimpl_import_32ne;
sampsz = 4;
break;
case AUDIO_FORMAT_S24_OE:
e->e_export = auimpl_export_24oe;
e->e_import = auimpl_import_24oe;
sampsz = 4;
break;
case AUDIO_FORMAT_S32_OE:
e->e_export = auimpl_export_32oe;
e->e_import = auimpl_import_32oe;
sampsz = 4;
break;
case AUDIO_FORMAT_S16_NE:
e->e_export = auimpl_export_16ne;
e->e_import = auimpl_import_16ne;
sampsz = 2;
break;
case AUDIO_FORMAT_S16_OE:
e->e_export = auimpl_export_16oe;
e->e_import = auimpl_import_16oe;
sampsz = 2;
break;
case AUDIO_FORMAT_AC3:
e->e_export = auimpl_export_24ne;
e->e_import = auimpl_import_24ne;
sampsz = 2;
break;
default:
audio_dev_warn(d, "bad format");
goto done;
}
/* sanity test a few values */
audio_dev_warn(d, "bad engine channels or rate");
goto done;
}
if (rv != 0) {
audio_dev_warn(d, "unable to open engine");
goto done;
}
audio_dev_warn(d, "improper engine configuration");
goto done;
}
audio_dev_warn(d, "invalid fragment configuration");
goto done;
}
e->e_head = 0;
e->e_tail = 0;
e->e_hidx = 0;
e->e_tidx = 0;
e->e_limiter_state = 0x10000;
} else {
e->e_playahead = ENG_PLAYAHEAD(e);
/*
* Need to have at least a fragment plus some extra to
* avoid underruns.
*/
}
/*
* Impossible to queue more frames than FIFO can hold.
*/
if (e->e_playahead > e->e_nframes) {
}
}
for (i = 0; i < e->e_nchan; i++) {
e->e_choffs[i] = i;
} else {
}
}
/*
* Start the output callback to populate the engine on
* startup. This avoids a false underrun when we're first
* starting up.
*/
if (flags & ENGINE_OUTPUT) {
}
/*
* Start the engine up now.
*
* AC3: Note that this will need to be modified for AC3, since
* for AC3 we can't start the device until we actually have
* some data for it from the application. Probably the best
* way to do this would be to add a flag, ENGINE_DEFERRED or
* somesuch.
*/
if (rv != 0) {
ENG_CLOSE(e);
goto done;
}
}
ok:
done:
mutex_exit(&e->e_lock);
mutex_exit(&d->d_lock);
return (rv);
}
void
{
audio_dev_t *d;
if (e == NULL)
return;
d = e->e_dev;
mutex_enter(&d->d_lock);
mutex_enter(&e->e_lock);
if (list_is_empty(&e->e_streams)) {
/* if last client holding engine open, close it all down */
ENG_STOP(e);
e->e_flags &= ENGINE_DRIVER_FLAGS;
ENG_CLOSE(e);
}
mutex_exit(&e->e_lock);
cv_broadcast(&d->d_cv);
mutex_exit(&d->d_lock);
}
int
{
list_t *l;
int start;
/*
* Make sure we don't automatically unload. This prevents
* loss of hardware settings when no audio clients are
* running.
*/
DDI_NO_AUTODETACH, 1);
/*
* This does an in-order insertion, finding the first available
* free index. "Special" devices (ones without any actual engines)
* are all numbered 0. There should only be one of them anyway.
* All others start at one.
*/
if (d->d_flags & DEV_SNDSTAT_CAP) {
start = 0;
} else {
start = 1;
}
l = &auimpl_devs_by_index;
/* skip over special nodes */
continue;
/* found a free spot! */
break;
}
d->d_index++;
}
/*
* NB: If srch is NULL, then list_insert_before puts
* it on the tail of the list. So if we didn't find a
* hole, then that's where we want it.
*/
list_insert_before(l, srch, d);
/* insert in order by number */
l = &auimpl_devs_by_number;
break;
}
}
list_insert_before(l, srch, d);
if (auimpl_create_minors(d) != 0) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
mutex_enter(&d->d_lock);
/* if we are still in use, we can't unregister */
if (d->d_refcnt) {
mutex_exit(&d->d_lock);
return (DDI_FAILURE);
}
mutex_exit(&d->d_lock);
return (DDI_SUCCESS);
}
static int
{
if (rw == KSTAT_WRITE) {
return (EACCES);
}
mutex_enter(&e->e_lock);
mutex_exit(&e->e_lock);
return (0);
}
static void
{
char name[32];
struct audio_stats *st;
sizeof (struct audio_stats) / sizeof (kstat_named_t), 0);
audio_dev_warn(d, "unable to initialize kstats");
return;
}
e->e_ksp->ks_private = e;
kstat_install(e->e_ksp);
}
void
{
mutex_enter(&d->d_lock);
auimpl_engine_ksinit(d, e);
/* check for duplex */
d->d_flags |= DEV_DUPLEX_CAP;
}
d->d_flags |= DEV_DUPLEX_CAP;
}
/* add in the direction caps -- must be done after duplex above */
if (e->e_flags & ENGINE_OUTPUT_CAP) {
d->d_flags |= DEV_OUTPUT_CAP;
}
if (e->e_flags & ENGINE_INPUT_CAP) {
d->d_flags |= DEV_INPUT_CAP;
}
list_insert_tail(&d->d_engines, e);
e->e_dev = d;
mutex_exit(&d->d_lock);
}
void
{
mutex_enter(&d->d_lock);
list_remove(&d->d_engines, e);
if (e->e_ksp)
kstat_delete(e->e_ksp);
mutex_exit(&d->d_lock);
}
/*
* Change the number.
*/
void
{
list_t *l = &auimpl_devs_by_number;
/* reorder our list */
list_remove(l, d);
break;
}
}
list_insert_before(l, srch, d);
}
void
{
audio_dev_t *d;
list_t *l;
l = &auimpl_devs_by_index;
mutex_enter(&d->d_lock);
mutex_exit(&d->d_lock);
if (cont == AUDIO_WALK_STOP)
break;
}
}
void
{
audio_dev_t *d;
list_t *l;
l = &auimpl_devs_by_number;
mutex_enter(&d->d_lock);
mutex_exit(&d->d_lock);
if (cont == AUDIO_WALK_STOP)
break;
}
}
void
int (*walker)(audio_engine_t *, void *),
void *arg)
{
audio_engine_t *e;
mutex_enter(&d->d_lock);
break;
}
}
mutex_exit(&d->d_lock);
}
int
{
return (ENG_FORMAT(e));
}
int
{
return (ENG_CHANNELS(e));
}
int
{
return (ENG_RATE(e));
}
unsigned
{
unsigned capab = 0;
if (e->e_flags & ENGINE_INPUT_CAP) {
}
if (e->e_flags & ENGINE_OUTPUT_CAP) {
}
return (capab);
}
static void
{
audio_dev_t *d;
audio_engine_t *e;
mutex_enter(&d->d_lock);
break;
}
}
mutex_exit(&d->d_lock);
if (done)
break;
}
}
/*
* This function suspends an engine. The intent is to pause the
* engine temporarily so that it does not underrun while user threads
* are suspended. The driver is still responsible for actually doing
* the driver suspend work -- all this does is put the engine in a
* paused state. It does not prevent, for example, threads from
* accessing the hardware.
*
* A properly implemented driver won't even be aware of the existence
* of this routine -- the driver will just handle the suspend &
* resume. At the point of suspend & resume, the driver will see that
* the engines are not running (as if all threads had "paused" it).
*
* Failure to execute either of the routines below is not critical,
* but will probably lead to underruns and overflows as the kernel
* driver gets resumed well in advance of the time when user threads
* are ready to start operation.
*/
static int
{
mutex_enter(&e->e_lock);
e->e_suspended = B_TRUE;
mutex_exit(&e->e_lock);
return (AUDIO_WALK_CONTINUE);
}
static int
{
mutex_enter(&e->e_lock);
e->e_suspended = B_FALSE;
mutex_exit(&e->e_lock);
return (AUDIO_WALK_CONTINUE);
}
{
switch (code) {
case CB_CODE_CPR_CHKPT:
return (B_TRUE);
case CB_CODE_CPR_RESUME:
return (B_TRUE);
default:
return (B_FALSE);
}
}
static callb_id_t auimpl_cpr_id = 0;
void
auimpl_dev_init(void)
{
/*
* We "borrow" the CB_CL_CPR_PM class, which gets executed at
* about the right time for us. It would be nice to have a
* new CB_CL_CPR_AUDIO class, but it isn't critical at this
* point.
*
* Note that we don't care about our thread id.
*/
}
void
auimpl_dev_fini(void)
{
(void) callb_delete(auimpl_cpr_id);
}
void
{
}
void *
{
}
void
{
char line[64];
char *s;
int i;
const int wrap = 16;
s = line;
line[0] = 0;
for (i = 0; i < dcount; i++) {
(void) sprintf(s, " %02x", *w);
s += strlen(s);
w++;
line[0] = 0;
s = line;
}
}
if ((i % wrap) != 0) {
}
}
void
{
char line[64];
char *s;
int i;
const int wrap = 8;
s = line;
line[0] = 0;
for (i = 0; i < dcount; i++) {
(void) sprintf(s, " %04x", *w);
s += strlen(s);
w++;
line[0] = 0;
s = line;
}
}
if ((i % wrap) != 0) {
}
}
void
{
char line[128];
char *s;
int i;
const int wrap = 4;
s = line;
line[0] = 0;
for (i = 0; i < dcount; i++) {
(void) sprintf(s, " %08x", *w);
s += strlen(s);
w++;
line[0] = 0;
s = line;
}
}
if ((i % wrap) != 0) {
}
}
/*
* The following two functions are a temporary workaround for CR6924018
* in usb audio, and are not to be used for anything else, they WILL be
* removed in the future.
*/
void
{
mutex_enter(&e->e_lock);
}
void
{
mutex_exit(&e->e_lock);
}