/*
* 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
*/
/*
*/
/*
* Sun audio(7I) and mixer(7I) personality.
*
* There are some "undocumented" details of how legacy Sun audio
* interfaces work. The following "rules" were derived from reading the
* legacy Sun mixer code, and to the best of our knowledge are not
* documented elsewhere.
*
* - We create a "fake" audio device, which behaves like a classic
* exclusive audio device, for each PID, as determined during open(2).
*
* - Different processes don't interfere with each other. Even though
* they are running concurrently, they each think they have exclusive
* control over the audio device.
*
* - Read and write directions operate independent of each other. That
* is, a device open for reading won't intefere with a future open for
* writing, and vice versa. This is true even within the same process.
*
* - Because the virtualization is by PID, strange behavior may occur
* if a process tries to open an audio device at the same time it
* has already received a file descriptor from another process (such
* through inheritence via fork()).
*
* - The "fake" audio device has no control over physical settings.
* It sees only the software attenuation-based volumes for play and
* record, and has no support for alternate input or output ports or
* access to the monitoring features of the hardware.
*
* - Explicit notificaton signals (SIGPOLL) are only ever sent up the
* audioctl node -- never up a regular audio node. (The stream head
* may still issue SIGPOLL based on readability/writability of
* course.)
*
* - Corollary: processes that want asynch. notifications will open
*
* - We don't support the MIXER mode at all.
*
* (in each direction.)
*
* if the device cannot support duplex operation.
*
* - Attempts to open a device with FREAD set fail if the device is not
* capable of recording. (Likewise for FWRITE and playback.)
*
* - No data transfer is permitted for audioctl nodes. (No actual
* record or play.)
*
* - Sun audio does not support any formats other than linear and
* streams which require special handling.
*
* - Sun audio only supports stereo or monophonic data streams.
*/
#include <sys/sysmacros.h>
#include "audio_client.h"
/* common structure shared between both audioctl and audio nodes */
struct daclient {
unsigned dc_eof;
};
struct eofcnt {
};
struct dadev {
};
struct daproc {
int p_refcnt;
int p_oflag;
};
int devaudio_proc_hold(audio_client_t *, int);
void devaudio_proc_release(audio_client_t *);
static void devaudio_proc_update(daproc_t *);
static int
{
case 8:
case AUDIO_ENCODING_ULAW:
return (AUDIO_FORMAT_ULAW);
case AUDIO_ENCODING_ALAW:
return (AUDIO_FORMAT_ALAW);
case AUDIO_ENCODING_LINEAR8:
return (AUDIO_FORMAT_U8);
case AUDIO_ENCODING_LINEAR:
return (AUDIO_FORMAT_S8);
}
break;
case 16:
return (AUDIO_FORMAT_S16_NE);
break;
case 32:
return (AUDIO_FORMAT_S32_NE);
break;
}
return (AUDIO_FORMAT_NONE);
}
static void
{
int e, p;
/*
* N.B.: Even though some of the formats below can't be set by
* this personality, reporting them (using the closest match)
* allows this personality to roughly approximate settings for
* other streams. It would be incredibly poor form for any
* personality to modify the format settings for a different
* personality, so we don't worry about that case.
*/
switch (afmt) {
case AUDIO_FORMAT_ULAW:
e = AUDIO_ENCODING_ULAW;
p = 8;
break;
case AUDIO_FORMAT_ALAW:
e = AUDIO_ENCODING_ALAW;
p = 8;
break;
case AUDIO_FORMAT_U8:
p = 8;
break;
case AUDIO_FORMAT_S8:
p = 8;
break;
case AUDIO_FORMAT_S16_NE:
case AUDIO_FORMAT_S16_OE:
case AUDIO_FORMAT_U16_NE:
case AUDIO_FORMAT_U16_OE:
p = 16;
break;
case AUDIO_FORMAT_S24_NE:
case AUDIO_FORMAT_S24_OE:
case AUDIO_FORMAT_S24_PACKED:
p = 24;
break;
case AUDIO_FORMAT_S32_NE:
case AUDIO_FORMAT_S32_OE:
p = 32;
break;
default:
/* all other formats (e.g. AC3) are uninterpreted */
e = AUDIO_ENCODING_NONE;
p = 32;
break;
}
}
static daproc_t *
{
return (NULL);
}
/*
* audio(7I) says: Upon the initial open() of the audio
* device, the driver resets the data format of the device to
* the default state of 8-bit, 8Khz, mono u-Law data.
*/
/* pretend we don't have a software mixer - we don't support the API */
info->hw_features = 0;
info->sw_features = 0;
info->sw_features_enabled = 0;
if (caps & AUDIO_CLIENT_CAP_PLAY)
if (caps & AUDIO_CLIENT_CAP_RECORD)
if (caps & AUDIO_CLIENT_CAP_DUPLEX)
return (proc);
}
static void
{
}
int
{
list_t *l;
int rv;
adev = auclnt_get_dev(c);
/* first allocate and initialize the daclient private data */
return (ENOMEM);
}
auclnt_set_private(c, dc);
pid = auclnt_get_pid(c);
/* set a couple of common fields */
break;
}
}
goto failed;
}
list_insert_tail(l, proc);
}
goto failed;
}
/* interrupted! */
goto failed;
}
}
goto failed;
}
auclnt_set_samples(sp, 0);
auclnt_set_errors(sp, 0);
}
goto failed;
}
auclnt_set_samples(sp, 0);
auclnt_set_errors(sp, 0);
}
/* we update the s_proc last to avoid a race */
return (0);
return (rv);
}
static void
{
dc = auclnt_get_private(c);
}
}
void
{
dc = auclnt_get_private(c);
}
}
}
}
}
}
static void
{
/*
* Potentially send a message upstream with the record data.
* We collect this up in chunks of the buffer size requested
* by the client.
*/
/*
* This will apply back pressure to the
* buffer. We haven't yet lost any data, we
* just can't send it up. The point at which
* we have an unrecoverable overrun is in the
* buffer, not in the streams queue. So, no
* need to do anything right now.
*
* Note that since recording is enabled, we
* expect that the callback routine will be
* called repeatedly & regularly, so we don't
* have to worry about leaving data orphaned
* in the queue.
*/
break;
}
}
}
static void
{
audio_client_t *c;
sp = auclnt_output_stream(c);
} else {
}
sp = auclnt_input_stream(c);
} else {
}
}
static void
{
return;
}
}
static void
{
int rv;
/* the special value "1" indicates that this is a copyin */
return;
}
return;
}
dc = auclnt_get_private(c);
if (auclnt_get_minor_type(c) == AUDIO_MINOR_DEVAUDIOCTL) {
/* control node can do both read and write fields */
} else {
}
/*
* Start by validating settings.
*/
if ((isctl) ||
goto err;
}
}
goto err;
}
}
if (writer &&
goto err;
}
}
/* play fields that anyone can modify */
goto err;
}
}
if ((isctl) ||
goto err;
}
}
goto err;
}
}
if (reader &&
goto err;
}
}
if (isctl) {
goto err;
}
/* make sure we can support 16-bit stereo samples */
}
/* limit the maximum buffer size somewhat */
}
}
/* record fields that anyone can modify */
goto err;
}
}
/*
* Now apply the changes.
*/
goto err;
}
goto err;
}
if (pfmt != AUDIO_FORMAT_NONE) {
goto err;
}
}
/*
* This ugly special case code is required to
* prevent problems with realaudio.
*/
}
}
} else {
/* qenable to start up the playback */
}
}
}
}
}
}
/*
* No checks on the buffer size are performed
* for play side. The value of the buffer size
* is meaningless for play side anyway.
*/
}
} else {
}
}
}
goto err;
}
goto err;
}
if (rfmt != AUDIO_FORMAT_NONE) {
goto err;
}
}
}
} else {
}
}
}
}
}
}
} else {
}
}
return;
err:
}
static void
{
audio_dev_t *d = auclnt_get_dev(c);
audio_device_t *a;
return;
}
"SUNW,%s", auclnt_get_dev_name(d));
auclnt_get_dev_description(d), sizeof (a->config));
auclnt_get_dev_version(d), sizeof (a->version));
}
static int
{
if (auclnt_get_minor_type(c) == AUDIO_MINOR_DEVAUDIOCTL) {
/* we only need to notify peers in our own process */
if (auclnt_get_pid(c) == pid) {
}
}
return (AUDIO_WALK_CONTINUE);
}
static void
{
}
}
static void
{
int eofs = 0;
/* get more data! (do this early) */
qenable(auclnt_get_wq(c));
eofs++;
}
if (eofs) {
}
}
static void *
{
unsigned cap;
/* if not a play or record device, don't bother initializing it */
return (NULL);
}
return (dev);
}
static void
{
}
}
static int
{
int rv;
return (rv);
}
auclnt_close(c);
return (rv);
}
/* start up the input */
}
return (0);
}
static int
{
int rv;
if ((rv = auclnt_open(c, 0)) != 0) {
return (rv);
}
auclnt_close(c);
return (rv);
}
return (0);
}
static void
{
auclnt_close(c);
}
static void
{
auclnt_close(c);
}
static void
{
wq = auclnt_get_wq(c);
/*
* If a transfer error occurred, the framework already
* MIOCNAK'd it.
*/
return;
}
/*
* If no state, then this is a response to M_COPYOUT, and we
* are done. (Audio ioctls just copyout a single structure at
* completion of work.)
*/
return;
}
/* now, call the handler ioctl */
case AUDIO_SETINFO:
break;
default:
break;
}
}
static void
{
/* BSD legacy here: we only support transparent ioctls */
return;
}
case AUDIO_GETINFO:
break;
case AUDIO_SETINFO:
break;
case AUDIO_GETDEV:
break;
case AUDIO_DIAG_LOOPBACK:
/* we don't support this one */
break;
case AUDIO_MIXERCTL_GET_MODE:
case AUDIO_MIXERCTL_SET_MODE:
case AUDIO_MIXERCTL_GETINFO:
case AUDIO_MIXERCTL_SETINFO:
case AUDIO_GET_NUM_CHS:
case AUDIO_GET_CH_NUMBER:
case AUDIO_GET_CH_TYPE:
case AUDIO_MIXER_SINGLE_OPEN:
default:
break;
}
}
static void
{
case M_IOCTL:
/* Drain ioctl needs to be handled on the service queue */
devaudio_mioctl(c, mp);
break;
case M_IOCDATA:
devaudio_miocdata(c, mp);
break;
case M_FLUSH:
/*
* We don't flush the engine. The reason is that
* other streams might be using the engine. This is
* fundamentally no different from the case where the
* engine hardware has data buffered in an
* inaccessible FIFO.
*
* Clients that want to ensure no more data is coming
* should stop the stream before flushing.
*/
}
} else {
}
break;
case M_DATA:
/*
* No audio data on control nodes!
*/
default:
break;
}
}
static void
{
case M_IOCTL:
/* Drain ioctl needs to be handled on the service queue */
} else {
devaudio_mioctl(c, mp);
}
break;
case M_IOCDATA:
devaudio_miocdata(c, mp);
break;
case M_FLUSH:
/*
* We don't flush the engine. The reason is that
* other streams might be using the engine. This is
* fundamentally no different from the case where the
* engine hardware has data buffered in an
* inaccessible FIFO.
*
* Clients that want to ensure no more data is coming
* should stop the stream before flushing.
*/
}
} else {
}
break;
case M_DATA:
/*
* Defer processing to the queue. This keeps the data
* ordered, and allows the wsrv routine to gather
* multiple mblks at once.
*/
/*
* If we need to pullup, do it here to
* simplify the rest of the processing later.
* This should rarely (if ever) be necessary.
*/
} else {
}
} else {
}
break;
default:
break;
}
}
static void
{
/*
* Put it back in the queue so we can apply
* backpressure properly.
*/
return;
}
}
}
static void
{
unsigned framesz;
sp = auclnt_output_stream(c);
unsigned count;
/* got a message */
/* if its a drain ioctl, we need to process it here */
if (auclnt_start_drain(c) != 0) {
devaudio_drain(c);
}
continue;
}
/*
* Empty mblk require special handling, since they
* indicate EOF. We treat them separate from the main
* processing loop.
*/
}
continue;
}
break;
} else {
}
}
/* if the stream isn't running yet, start it up */
if (!auclnt_is_paused(sp))
}
"sound,audio",
NULL, /* read */
NULL, /* write */
NULL, /* ioctl */
NULL, /* chpoll */
NULL, /* mmap */
};
"sound,audioctl",
NULL, /* dev_init */
NULL, /* dev_fini */
NULL, /* read */
NULL, /* write */
NULL, /* ioctl */
NULL, /* chpoll */
NULL, /* mmap */
NULL, /* output */
NULL, /* input */
NULL, /* drain */
NULL,
};
void
auimpl_sun_init(void)
{
}