audio_engine.c revision 2c30fa4582c5d6c659e059e719c5f6163f7ef1e3
/*
* 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.
*
*/
#include <sys/sysmacros.h>
#include "audio_impl.h"
/*
* Audio Engine functions.
*/
/*
* Globals
*/
/*
* We need to operate at fairly high interrupt priority to avoid
* underruns due to other less time sensitive processing.
*/
int audio_priority = DDI_IPL_8;
{
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);
mutex_destroy(&d->d_ctrl_lock);
mutex_destroy(&d->d_lock);
cv_destroy(&d->d_cv);
cv_destroy(&d->d_ctrl_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 {
}
}
static void
{
char *buf;
char *ptr;
int tidx;
cnt = 0;
int nbytes;
tidx = 0;
}
}
if (e->e_flags & ENGINE_INPUT) {
/* record */
e->e_hidx = 0;
} else {
/* play */
e->e_tidx = 0;
}
/* relocate from scratch area to destination */
}
static volatile uint_t auimpl_engno = 0;
{
int i;
audio_engine_t *e;
char tname[32];
int num;
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);
cv_destroy(&e->e_cv);
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
{
}
int
{
audio_engine_t *e = NULL;
int priority = 0;
int sampsz;
int i;
int fragfr;
int fmts;
mutex_enter(&d->d_lock);
if (mask & FORMAT_MSK_FMT)
if (mask & FORMAT_MSK_RATE)
if (mask & FORMAT_MSK_CHAN)
/*
* Which direction are we opening? (We must open exactly
* one direction, otherwise the open is meaningless.)
*/
flags |= ENGINE_OUTPUT;
} else {
flags |= ENGINE_INPUT;
}
} else {
}
/* If the device is suspended, wait for it to resume. */
while (d->d_suspended) {
}
int mypri;
int r;
/* Make sure the engine can do what we want it to. */
mutex_enter(&t->e_lock);
mutex_exit(&t->e_lock);
continue;
}
/*
* Open the engine early, as the inquiries to rate and format
* may not be accurate until this is done.
*/
if (list_is_empty(&t->e_streams)) {
mutex_exit(&t->e_lock);
continue;
}
}
if ((ENG_FORMAT(t) & fmts) == 0) {
if (list_is_empty(&t->e_streams))
ENG_CLOSE(t);
mutex_exit(&t->e_lock);
continue;
}
/* If it is in failed state, don't use this engine. */
if (t->e_failed) {
if (list_is_empty(&t->e_streams))
ENG_CLOSE(t);
mutex_exit(&t->e_lock);
continue;
}
/*
* If the engine is in exclusive use, we can't use it.
* This is intended for use with AC3 or digital
* streams that cannot tolerate mixing.
*/
if (list_is_empty(&t->e_streams))
ENG_CLOSE(t);
mutex_exit(&t->e_lock);
continue;
}
/*
* If the engine is in use incompatibly, we can't use
* it. This should only happen for half-duplex audio
* devices. I've not seen any of these that are
* recent enough to be supported by Solaris.
*/
if (list_is_empty(&t->e_streams))
ENG_CLOSE(t);
mutex_exit(&t->e_lock);
/* Only override the ENODEV or EIO. */
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.
*/
/*
* This engine *can* support us, so we should no longer
* have a failure mode.
*/
rv = 0;
mypri = (1U << 0);
/*
* Mixing is cheap, so try not to pick on idle
* engines. This avoids burning bus bandwidth (which
* may be precious for certain classes of traffic).
* Note that idleness is given a low priority compared
* to the other considerations.
*
* We also use this opportunity open the engine, if
* not already done so, so that our parameter
* inquiries will be valid.
*/
if (!list_is_empty(&t->e_streams))
/*
* Slight preference is given to reuse an engine that
* we might already be using.
*/
/*
* Sample rate conversion avoidance. Upsampling
* requires multiplications and is moderately
* expensive. Downsampling requires division and is
* quite expensive, and hence to be avoided if at all
* possible.
*/
r = ENG_RATE(t);
/*
* No conversion needed at all. This is ideal.
*/
} else {
if (flags & ENGINE_INPUT) {
src = r;
} else {
dst = r;
}
/*
* Pure upsampling only. This
* penalizes any engine which requires
* downsampling.
*/
}
}
/*
* Try not to pick on duplex engines. This way we
* leave engines that can be used for recording or
* playback available as such. All modern drivers
* use separate unidirectional engines for playback
* and record.
*/
}
/*
* Try not to pick on engines that can do other
* formats. This will generally be false, but if it
* happens we pretty strongly avoid using a limited
* resource.
*/
}
if (e != NULL) {
/*
* If we opened this for our own use
* and we are no longer using it, then
* close it back down.
*/
if (list_is_empty(&e->e_streams))
ENG_CLOSE(e);
mutex_exit(&e->e_lock);
}
e = t;
} else {
mutex_exit(&t->e_lock);
}
/*
* Locking: at this point, if we have an engine, "e", it is
* locked. No other engines should have a lock held.
*/
}
mutex_exit(&d->d_lock);
return (EINTR);
}
goto again;
}
if (rv != 0) {
mutex_exit(&d->d_lock);
return (rv);
}
/*
* If this represents a potential engine change, then
* we close off everything, and start anew. This turns
* out to be vastly simpler than trying to close all
* the races associated with a true hand off. This
* ought to be relatively uncommon (changing engines).
*/
/* Drop the new reference. */
if (list_is_empty(&e->e_streams))
ENG_CLOSE(e);
mutex_exit(&e->e_lock);
mutex_exit(&d->d_lock);
/* Try again. */
}
/*
* Add a reference to this engine if we don't already
* have one.
*/
if (!list_is_empty(&e->e_streams)) {
/*
* 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.
*/
goto ok;
}
} else {
/*
* No change in engine... hence don't reprogram the
* engine, and don't change references.
*/
goto ok;
}
e->e_format = ENG_FORMAT(e);
e->e_nchan = ENG_CHANNELS(e);
/* Select format converters for the engine. */
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;
}
audio_dev_warn(d, "invalid fragment configration");
goto done;
}
/* Sanity test a few values. */
audio_dev_warn(d, "bad engine channels or rate");
goto done;
}
audio_dev_warn(d, "improper engine 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 {
}
}
/*
* Arrange for the engine to be started. We defer this to the
* periodic callback, to ensure that the start happens near
* the edge of the periodic callback. This is necessary to
* ensure that the first fragment processed is about the same
* size as the usual fragment size. (Basically, the problem
* is that we have only 10 msec resolution with the periodic
* interface, whch is rather unfortunate.)
*/
e->e_need_start = B_TRUE;
if (flags & ENGINE_OUTPUT) {
/*
* Start the output callback to populate the engine on
* startup. This avoids a false underrun when we're
* first starting up.
*/
} else {
}
ok:
/* Configure the engine. */
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);
while (d->d_suspended) {
}
mutex_enter(&e->e_lock);
if (list_is_empty(&e->e_streams)) {
ENG_STOP(e);
e->e_periodic = 0;
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;
if (cont == AUDIO_WALK_STOP)
break;
}
}
void
{
audio_dev_t *d;
list_t *l;
l = &auimpl_devs_by_number;
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));
}
{
if (e->e_flags & ENGINE_INPUT_CAP) {
}
if (e->e_flags & ENGINE_OUTPUT_CAP) {
}
return (capab);
}
/*
* 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 void
{
if (e->e_failed || e->e_suspended) {
e->e_suspended = B_TRUE;
return;
}
e->e_suspended = B_TRUE;
if (e->e_flags & ENGINE_INPUT) {
ENG_STOP(e);
}
if (e->e_flags & ENGINE_OUTPUT) {
ENG_STOP(e);
}
}
static void
{
ASSERT(e->e_suspended);
if (e->e_failed) {
/* No longer suspended, but still failed! */
e->e_suspended = B_FALSE;
return;
}
if (e->e_flags & ENGINE_OUTPUT) {
}
e->e_need_start = B_TRUE;
}
e->e_suspended = B_FALSE;
cv_broadcast(&e->e_cv);
}
static int
{
list_t *l;
audio_engine_t *e;
mutex_enter(&d->d_lock);
mutex_enter(&d->d_ctrl_lock);
if (d->d_suspended) {
d->d_suspended++;
mutex_exit(&d->d_ctrl_lock);
mutex_exit(&d->d_lock);
return (AUDIO_WALK_CONTINUE);
}
d->d_suspended++;
(void) auimpl_save_controls(d);
mutex_exit(&d->d_ctrl_lock);
l = &d->d_engines;
mutex_enter(&e->e_lock);
mutex_exit(&e->e_lock);
}
mutex_exit(&d->d_lock);
return (AUDIO_WALK_CONTINUE);
}
static int
{
list_t *l;
audio_engine_t *e;
mutex_enter(&d->d_lock);
mutex_enter(&d->d_ctrl_lock);
ASSERT(d->d_suspended);
d->d_suspended--;
if (d->d_suspended) {
mutex_exit(&d->d_ctrl_lock);
mutex_exit(&d->d_lock);
return (AUDIO_WALK_CONTINUE);
}
(void) auimpl_restore_controls(d);
cv_broadcast(&d->d_ctrl_cv);
mutex_exit(&d->d_ctrl_lock);
l = &d->d_engines;
mutex_enter(&e->e_lock);
mutex_exit(&e->e_lock);
}
mutex_exit(&d->d_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);
}
}
void
{
(void) auimpl_dev_suspend(d, NULL);
}
void
{
(void) auimpl_dev_resume(d, NULL);
}
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) {
}
}