2021-10-11 18:37:13 -03:00

1857 lines
44 KiB
C

#ident "@(#) audio.c 1.1@(#) Copyright (c) 1991-92 Sun Microsystems, Inc."
/*
* Generic AUDIO driver
*
* This file contains the generic routines for handling a STREAMS-based
* audio device. The SPARCstation 1 audio chips and the SPARCstation 3
* DBRI chips are examples of such devices.
*/
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/file.h>
#include <sys/debug.h>
#include <sun/audioio.h>
#include <sbusdev/audiovar.h>
#include "audiodebug.h"
/* debugging printf's */
int Audio_debug = 0;
/* Local private routines */
static void audio_append_cmd();
static void audio_delete_cmds();
static void audio_ioctl();
void audio_flush_cmdlist();
static aud_return_t audio_do_setinfo();
void audio_pause_record();
void audio_pause_play();
void audio_resume_record();
void audio_resume_play();
int audio_xmit_garbage_collect();
static int audio_receive_collect();
/* define macros to add and remove aud_cmd structures from the free list */
#define audio_alloc_cmd(v, d) { \
(d) = (v)->free; \
if ((d) != NULL) \
(v)->free = (d)->next; \
} \
/* ATRACE(0, ATR_CMDALLOC, (d)); */
#define audio_free_cmds(v, f, l) { \
/* ATRACE(0, ATR_CMDFREE, (int)(f)); */ \
(l)->next = (v)->free; \
(v)->free = (f); \
}
/*
* Append a command block to a list of chained commands
*/
static void
audio_append_cmd(list, cmdp)
struct aud_cmdlist *list;
aud_cmd_t *cmdp;
{
ATRACE(audio_append_cmd, ATR_APPEND, (int)cmdp);
cmdp->next = NULL;
if (list->tail != NULL)
list->tail->next = cmdp;
else
list->head = cmdp;
list->tail = cmdp;
return;
}
/*
* audio_delete_cmds - Remove one or more cmds from anywhere on a command
* list. Deletes the commands from headp to lastp inclusive.
*/
static void
audio_delete_cmds(list, headp, lastp)
struct aud_cmdlist *list;
aud_cmd_t *headp;
aud_cmd_t *lastp;
{
aud_cmd_t *cmdp;
if (list->head == NULL)
return;
if (list->head == headp) { /* Delete from head of list */
list->head = lastp->next;
if (list->head == NULL)
list->tail = NULL;
} else { /* Delete from middle/end of list */
/* Find element directly preceeding headp of list to delete */
for (cmdp = list->head; cmdp->next != NULL; cmdp = cmdp->next) {
if (cmdp->next == headp)
break;
}
if (cmdp->next != headp) /* match not found */
panic("match not found when aborting cmdlist\n");
cmdp->next = lastp->next;
if (cmdp->next == NULL)
list->tail = cmdp;
}
audio_free_cmds(list, headp, lastp); /* queue cmds to free list */
} /* audio_delete_cmds */
/*
* Flush a command list.
* Audio i/o must be stopped on the list before flushing it.
*/
void
audio_flush_cmdlist(as)
aud_stream_t *as;
{
struct aud_cmdlist *list;
aud_cmd_t *cmdp;
int s, ss;
aud_state_t *v = as->v;
s = splstr();
list = &as->cmdlist;
AUD_FLUSHCMD(as);
/* Release STREAMS command block */
for (cmdp = list->head; cmdp != NULL; cmdp = cmdp->next) {
if (cmdp->dihandle)
freemsg((mblk_t *)cmdp->dihandle);
cmdp->dihandle = NULL;
cmdp->data = 0;
cmdp->enddata = 0;
cmdp->lastfragment = 0;
cmdp->processed = 0;
cmdp->skip = 0;
cmdp->iotype = 0;
}
/* remove entire command list */
if (v->audio_spl)
ss = splr(v->audio_spl);
audio_delete_cmds(list, list->head, list->tail);
if (v->audio_spl)
(void) splx(ss);
(void) splx(s);
return;
} /* audio_flush_cmdlist */
/*
* The ordinary audio device may only have a single reader and single
* writer. However, a special minor device is defined for which multiple
* opens are permitted. Reads and writes are prohibited for this
* 'control' device, but certain ioctls, such as AUDIO_SETINFO, are
* allowed.
*
* Note that this is NOT a generic STREAMS open routine. It must be
* called from a device-dependent open routine that sets 'as'
* appropriately.
*
*/
int
audio_open(as, q, flag, sflag)
aud_stream_t *as; /* must be the correct "as" */
queue_t *q;
int flag;
int sflag;
{
int wantwrite;
int wantread;
int cansleep;
ATRACEINIT(); /* init tracing, if not already done */
ATRACE(audio_open, 'flag', flag);
ASSERT(as != NULL);
if ((ISDATASTREAM(as) && ((flag & (FREAD|FWRITE)) == FREAD)
&& (as != as->record_as))) {
call_debug("audio_open: failure: flag==FREAD && as!=as->record_as");
return(OPENFAIL);
}
/*
* If this is a data device: allow only one reader and one writer.
* If the requested access is busy, return EBUSY or hang until
* close().
*
* If this device does not carry data, then it is not an
* exclusive open device. Open is very simple for such devices.
*/
if (!ISDATASTREAM(as)) {
/* If this is the first open, init the Streams queues */
if (as->info.open == FALSE) {
as->info.open = TRUE;
if (as->openflag) {
/* XXX - close should clear this */
as->openflag = 0;
}
as->readq = q;
as->writeq = WR(q);
WR(q)->q_ptr = (caddr_t)(as);
q->q_ptr = (caddr_t)(as);
}
as->openflag |= (flag & (FREAD|FWRITE));
ATRACE(audio_open, ATR_OPENED, (int)as->info.minordev);
return (0);
}
wantwrite = ((flag & FWRITE) != 0); /* write access requested */
wantread = ((flag & FREAD) != 0); /* read access requested */
if (!wantwrite && !wantread) {
u.u_error = EINVAL; /* must read and/or write data */
return (OPENFAIL);
}
/*
* XXX - Because of limitation in vnode/streams interaction,
* blocking open only works on the clone device.
*/
/* if O_NDELAY, return immediately */
cansleep = (sflag == CLONEOPEN) && ((flag & (FNBIO | FNDELAY)) == 0);
/* While desired access is busy... */
while ((wantwrite && as->play_as->info.open) ||
(wantread && as->record_as->info.open)) {
int notify;
ATRACE(audio_open, ATR_BUSY, flag);
if (!cansleep) {
ATRACE(audio_open, '!slp', as);
u.u_error = EBUSY;
return (OPENFAIL);
}
/*
* Otherwise, hang until device is closed or a signal
* interrupts the sleep.
*
* If this is the first process to request access, signal
* the control device so that it can detect the status
* change. Unfortunately, if the current process has
* opened and enabled signals on the control device, this
* signal will break the sleep we're about to do.
* However, the process should already be prepared for
* this by retrying the open() when EINTR is returned.
*/
if (wantwrite && !as->play_as->info.waiting) {
as->play_as->info.waiting = TRUE;
notify = TRUE;
} else {
notify = FALSE;
}
if (wantread && !as->record_as->info.waiting) {
as->record_as->info.waiting = TRUE;
notify = TRUE;
}
if (notify)
audio_sendsig(as->control_as);
if (sleep((caddr_t)(as->control_as), SLEEPPRI)) {
u.u_error = EINTR;
return (OPENFAIL);
}
ATRACE(audio_open, ATR_NOTBUSY, as);
}
/*
* Set up the streams pointers for the requested access modes.
* If opened read/write, set the streams q_ptr to &v->play and
* mark the stream accordingly.
*/
if (wantread) {
as->record_as->info.open = TRUE;
as->record_as->openflag = flag & (FREAD|FWRITE);
as->record_as->readq = q;
as->record_as->writeq = WR(q);
WR(q)->q_ptr = (caddr_t)(as->record_as);
q->q_ptr = (caddr_t)(as->record_as);
ATRACE(audio_open, ATR_OPENREC, as->record_as);
/*
* XXX - Device-dependent code needs to call
* process_record after it initializes everything it
* needs to.
*/
}
if (wantwrite) {
as->play_as->info.open = TRUE;
as->play_as->openflag = flag & (FREAD|FWRITE);
as->play_as->readq = q;
as->play_as->writeq = WR(q);
WR(q)->q_ptr = (caddr_t)(as->play_as);
q->q_ptr = (caddr_t)(as->play_as);
ATRACE(audio_open, ATR_OPENPLAY, as->play_as);
}
/* Signal a state change */
audio_sendsig(as->control_as);
if (wantwrite) {
ASSERT(as->play_as->info.minordev != 0);
} else {
ASSERT(as->record_as->info.minordev != 0);
}
return (0);
}
/*
* Close the device. Be careful to only dismantle the open parts.
*
* If the device is open for both play and record, q_ptr will have been
* set to as->play_as and as->openflag to (FREAD|FWRITE).
*/
void
audio_close(q, flag)
queue_t *q;
int flag;
{
int s;
aud_stream_t *as;
aud_stream_t *as_output;
aud_stream_t *as_input;
int isplay;
int isrecord;
#ifdef lint
flag = flag;
#endif
as = (aud_stream_t *)q->q_ptr;
ASSERT(as != NULL);
as_output = as->play_as;
ASSERT(as_output != NULL);
as_input = as->record_as;
ASSERT(as_input != NULL);
ATRACE(audio_close, ATR_BEGINCLOSE, as->info.minordev);
isplay = ISPLAYSTREAM(as);
isrecord = ISRECORDSTREAM(as);
if (isplay) {
ATRACE(audio_close, 'cply', as);
}
if (isrecord) {
ATRACE(audio_close, 'crec', as);
}
/* Stop recording */
if (isrecord) {
ATRACE(audio_close, ATR_CLOSEREC, as->info.minordev);
AUD_STOP(as_input);
audio_flush_cmdlist(as_input);
/* Call the device-dependent close routine */
AUD_CLOSE(as_input);
as_input->info.open = FALSE;
as_input->info.pause = FALSE;
as_input->info.error = FALSE;
as_input->info.eof = 0;
as_input->info.waiting = FALSE;
/* Clear the record-side stream info */
as_input->readq = NULL;
as_input->writeq = NULL;
as_input->openflag &= ~FREAD;
if (isplay)
as_output->openflag &= ~FREAD;
}
/* Stop playing */
if (isplay) {
ATRACE(audio_close, ATR_CLOSEPLAY, as->info.minordev);
/*
* If there is any data waiting to be written, then sleep
* until it is all gone. This makes most applications
* behave as expected. Note that a signal (eg, ^C) will
* end the sleep.
*
* Since a process may use the control device to pause
* output, it is not necessary to worry about the pause
* flag here.
*/
if (!(u.u_procp->p_flag & SWEXIT)) {
ATRACE(audio_close, ATR_EXITCLOSE, as->info.minordev);
s = splstr(); /* XXX - close always called at splstr */
as_output->draining = TRUE;
audio_process_play(as_output);
if (as_output->draining) {
ATRACE(audio_close, ATR_CLOSEDRAIN,
as->info.minordev);
/*
* Sleep until output complete or signal.
*
* XXX - checking p->cursig here is
* promiscuous but useful since the
* signal may have been caught by the
* stream head's dumb drain code before
* we got a shot at it.
*/
if (u.u_procp->p_cursig == 0)
(void) sleep((caddr_t)
&as_output->draining,
SLEEPPRI);
}
(void) splx(s);
as_output->draining = FALSE;
}
AUD_STOP(as_output);
audio_flush_cmdlist(as_output);
/* Call the device-dependent close routine */
AUD_CLOSE(as_output);
as_output->info.open = FALSE;
as_output->info.pause = FALSE;
as_output->info.error = FALSE;
as_output->info.eof = 0;
as_output->info.waiting = FALSE;
/* Clear the record-side stream info */
as_output->readq = NULL;
as_output->writeq = NULL;
as_output->openflag &= ~FWRITE;
if (isrecord)
as_input->openflag &= ~FWRITE;
}
/* If closing play or record, signal the control stream */
if (ISDATASTREAM(as)) {
audio_sendsig(as->control_as);
wakeup((caddr_t)as->control_as); /* wakeup audio_open() */
}
/*
* If this stream is only a control stream, then cleanup is needed.
* Otherwise, the following is redundant.
*/
as->info.open = FALSE;
as->readq = NULL;
as->writeq = NULL;
as->openflag = 0;
ATRACE(audio_close, ATR_CLOSED, as);
return;
}
/*
* In addition to the streamio(4) and filio(4) ioctls, the driver accepts:
* AUDIO_DRAIN - hang until output is drained
* AUDIO_GETINFO - get state information
* AUDIO_SETINFO - set state information
*
* Other ioctls may be processed by the device-specific ioctl handler.
*
* If the IOCTL is done on the control stream and channel is not
* specified assume normal play/record stream. If channel is "all" then
* return status of all streams (need to use streams data xfer
* messgages?). If channel is specified, then return/affect only that
* stream.
*
* XXX - fix up this definition.
*/
static void
audio_ioctl(as, q, mp)
aud_stream_t *as;
queue_t *q;
mblk_t *mp;
{
aud_state_t *v;
struct iocblk *iocp;
audio_info_t *ip;
aud_return_t change;
aud_stream_t *as_input;
aud_stream_t *as_output;
as = (aud_stream_t *)q->q_ptr;
v = as->v;
iocp = (struct iocblk *)mp->b_rptr;
iocp->ioc_count = 0; /* assume nothing returned */
iocp->ioc_error = 0; /* initialize to no error */
change = AUDRETURN_NOCHANGE; /* detect dev state change */
/* If there is output for this ioctl, allocate the space now */
if (((iocp->ioc_cmd & _IOC_OUT) != 0) && (mp->b_cont == NULL)) {
mblk_t *datap;
int size;
size = (iocp->ioc_cmd >> 16) & _IOCPARM_MASK;
if ((datap = (mblk_t *)allocb(size, BPRI_HI)) == NULL) {
iocp->ioc_error = ENOSR; /* no space */
goto othererrno;
} else {
mp->b_cont = datap;
}
}
ATRACE(audio_ioctl, ATR_IOCTL, iocp->ioc_cmd);
switch (iocp->ioc_cmd) {
case AUDIO_SETINFO: { /* Set state information */
int s;
unsigned int play_eof;
unsigned char play_err;
unsigned char rec_err;
ip = (audio_info_t *)mp->b_cont->b_rptr;
as_output = as->play_as;
as_input = as->record_as;
/*
* Error indicators and play eof count are updated
* atomically so that processes may reset them safely.
* Sample counts are also updated like this, but are
* handled in the device-specific setinfo routine.
*/
s = splstr();
play_eof = as_output->info.eof; /* Save old values */
play_err = as_output->info.error;
rec_err = as_input->info.error;
change = audio_do_setinfo(as, mp, iocp);
(void) splx(s);
if (iocp->ioc_error != 0)
goto einval;
/* Copy current state */
ip->play = as_output->info;
ip->record = as_input->info;
ip->monitor_gain = v->monitor_gain;
ip->output_muted = v->output_muted;
ip->play.eof = play_eof; /* Restore old values */
ip->play.error = play_err;
ip->record.error = rec_err;
iocp->ioc_count = sizeof (audio_info_t);
break;
}
case AUDIO_GETINFO: /* Get state information */
ip = ((audio_info_t *)mp->b_cont->b_rptr);
mp->b_cont->b_wptr += sizeof (audio_info_t);
as_output = as->play_as;
as_input = as->record_as;
/* Update values that are not stored in state structure */
AUD_GETINFO(as_output);
/* Copy current state */
ip->play = as_output->info;
ip->record = as_input->info;
ip->monitor_gain = v->monitor_gain;
ip->output_muted = v->output_muted;
iocp->ioc_count = sizeof (audio_info_t);
break;
case AUDIO_DRAIN: /* Drain output */
/*
* AUDIO_DRAIN must be queued to the service procedure,
* since there is no user context in which to sleep. If
* the request is not for a play device, return an error.
*/
if (!ISPLAYSTREAM(as))
goto einval;
putq(q, mp);
return; /* don't acknowledge now */
/* Other ioctls may be handled by the device-specific module */
default:
change = AUD_IOCTL(as, mp, iocp);
if (iocp->ioc_error == 0)
break;
einval: /* NACK gives EINVAL by default */
othererrno:
mp->b_datap->db_type = M_IOCNAK;
iocp->ioc_rval = -1;
goto reply;
} /* switch */
/*
* At this point, there has been no error, but we may or may
* not be ready to reply depending on the value of change.
* If change indicates no reply yet (DELAY), the device
* specific code is free to modify any of the fields we
* have just filled in here. It becomes the resonsibility
* of the device specific code therefore, to do a sendsig
*/
mp->b_datap->db_type = M_IOCACK; /* everything was OK */
if (change == AUDRETURN_DELAYED)
return;
reply:
qreply(q, mp); /* Send M_IOCACK (or NAK) upstream */
/*
* If a parameter change has occurred send SIGPOLL upstream. The
* user process must have executed "ioctl(fd, I_SETSIG, S_MSG)"
* and "signal(SIGPOLL, subroutine)" to receive the signal.
*/
if (change == AUDRETURN_CHANGE) {
/* Signal the the control device stream if it is open */
audio_sendsig(as->control_as);
}
}
/*
* Set all modified fields in the AUDIO_SETINFO structure. Return TRUE
* if no error, with 'v' updated to reflect new values. Otherwise,
* returns FALSE.
*/
static aud_return_t
audio_do_setinfo(as, mp, iocp)
aud_stream_t *as;
mblk_t *mp;
struct iocblk *iocp;
{
aud_stream_t *as_output;
aud_stream_t *as_input;
audio_info_t *ip;
aud_return_t ret;
as_output = as->play_as;
as_input = as->record_as;
ip = (audio_info_t *)mp->b_cont->b_rptr;
/*
* Make sure user structure is reasonable.
* Unsigned fields don't need bounds check for < 0
*/
if ((Modify(ip->play.gain) && (ip->play.gain > AUDIO_MAX_GAIN)) ||
(Modify(ip->record.gain) && (ip->record.gain > AUDIO_MAX_GAIN)) ||
(Modify(ip->monitor_gain) && (ip->monitor_gain > AUDIO_MAX_GAIN))) {
iocp->ioc_error = EINVAL;
return (AUDRETURN_NOCHANGE); /* if error, return ignored */
}
if ((Modifyc(ip->play.balance) &&
(ip->play.balance > AUDIO_RIGHT_BALANCE)) ||
(Modifyc(ip->record.balance) &&
(ip->record.balance > AUDIO_RIGHT_BALANCE))) {
iocp->ioc_error = EINVAL;
return (AUDRETURN_NOCHANGE);
}
/* Validate and set device-specific values */
ret = AUD_SETINFO(as, mp, iocp);
if (iocp->ioc_error != 0)
return (ret);
/*
* The following parameters are zeroed on close() of the i/o
* device. Attempts to change them are silently ignored if it is
* closed. Applications should check the info struct returned by
* AUDIO_SETINFO to determine whether they succeeded.
*/
if (as_output->info.open) {
if (Modifyc(ip->play.pause)) {
if (ip->play.pause) {
audio_pause_play(as_output);
} else {
audio_resume_play(as_output);
}
}
if (Modify(ip->play.eof))
as_output->info.eof = ip->play.eof;
if (Modifyc(ip->play.error))
as_output->info.error = (ip->play.error != 0);
/* The waiting flags may only be set. close() clears them. */
if (Modifyc(ip->play.waiting) && ip->play.waiting)
as_output->info.waiting = TRUE;
/*
* Get active flag again, since pause/resume may have
* changed them. If we called the getinfo routine here,
* then the sample count would get overwritten as well.
*/
as_output->info.active = AUD_GETFLAG(as_output, AUD_ACTIVE);
}
if (as_input->info.open) {
if (Modifyc(ip->record.pause)) {
if (ip->record.pause) {
audio_pause_record(as_input);
} else {
audio_resume_record(as_input);
}
}
if (Modifyc(ip->record.error))
as_input->info.error = (ip->record.error != 0);
if (Modifyc(ip->record.waiting) && ip->record.waiting)
as_input->info.waiting = TRUE;
/* Get active flag again */
as_input->info.active = AUD_GETFLAG(as_input, AUD_ACTIVE);
}
return (ret);
}
/*
* audio_wput - Stream write queue put procedure. All messages from
* above arrive first in this routine. All control device messages
* should be handled or dismissed here.
*/
void
audio_wput(q, mp)
queue_t *q;
mblk_t *mp;
{
aud_stream_t *as;
ASSERT(q != NULL);
ASSERT(mp != NULL);
as = (aud_stream_t *)q->q_ptr;
if (as == NULL) {
call_debug("audio_wput called with q->q_ptr == NULL");
freemsg(mp);
return;
}
ATRACE(audio_wput, 'wpas', as);
switch (mp->b_datap->db_type) {
case M_CTL: /* device specific streams messages */
/*
* XXX - The intent is for M_CTL messages to be available
* for use by the device specific portion of a driver.
* However, no one has tried them yet, so throw them away
* for now.
*/
freemsg(mp);
break;
case M_DATA: /* regular data */
/* Only queue data on play stream */
if (ISPLAYSTREAM(as)) {
/*
* If audio_process_play() has previously
* executed, as it would have during open(), then
* it may have found an empty queue (getq()). If
* the queue was previously found empty, then
* getq() will have set QWANTR in the queue_t and
* this call to putq() will schedule the write
* service procedure, audio_wsrv(). Therefore,
* there is no need for this routine to directly
* call audio_process_play().
*
* XXX - Correct?
*/
putq(q, mp);
qenable(q); /* XXX should not need this? */
ATRACE(audio_wput, ATR_WQ, mp);
} else {
freemsg(mp); /* No data on ctl or record streams */
}
break;
case M_IOCTL: /* ioctl */
/*
* Most ioctls take effect immediately. audio_ioctl()
* queues AUDIO_DRAIN to the service procedure.
*/
audio_ioctl(as, q, mp);
break;
case M_FLUSH: /* flush queues */
/*
* Any stream can flush its queues. We must be careful
* to flush the device command list only when flushing
* the relevant queue.
*/
ATRACE(audio_wput, ATR_FLUSH, *mp->b_rptr);
if (*mp->b_rptr & FLUSHW) {
*mp->b_rptr &= ~FLUSHW;
flushq(q, FLUSHDATA);
if (ISPLAYSTREAM(as)) {
AUD_STOP(as->play_as); /* XXX ? */
audio_flush_cmdlist(as->play_as);
qenable(q); /* schedule audio_wsrv() */
}
}
if (*mp->b_rptr & FLUSHR) {
/*
* Don't bother flushing the record buffers if
* this is not the record device or recording is
* already paused (buffers are flushed when
* pausing record).
*/
if (ISRECORDSTREAM(as) && !as->info.pause) {
AUD_STOP(as->record_as);
audio_flush_cmdlist(as->record_as);
}
flushq(RD(q), FLUSHDATA);
qreply(q, mp);
qenable(RD(q)); /* schedule audio_rsrv() */
} else {
freemsg(mp);
}
break;
default:
freemsg(mp);
break;
}
return;
}
/*
* Write service procedure can find the following on its queue:
* data messages queued for writing
* AUDIO_DRAIN ioctl messages
* Only messages for the audio i/o stream should be found on the queue.
*/
void
audio_wsrv(q)
queue_t *q;
{
aud_stream_t *as;
ATRACE(audio_wsrv, ATR_NONE, 0);
ASSERT(q != NULL);
as = (aud_stream_t *)q->q_ptr;
ASSERT(as != NULL);
audio_process_play(as->play_as);
return;
}
/*
* audio_xmit_garbage_collect - Reclaim used transmit buffers.
*
* Always called at splstr().
*
* Returns TRUE if application needs to be signaled.
*/
int
audio_xmit_garbage_collect(as)
aud_stream_t *as;
{
mblk_t *mp;
aud_cmd_t *cmdp;
aud_cmd_t *headp;
aud_cmd_t *lastp;
int notify, s;
aud_state_t *v = as->v;
notify = FALSE;
/*
* Insure that beyond first processed cmd lies a valid done
* command.
*
* XXX - Remember error case where entire list is NULLED out
* cmdptr == NULL and so is cmdlast. (pas)
*/
for (cmdp = as->cmdlist.head; cmdp != NULL; cmdp = as->cmdlist.head) {
/* Don't look at cmd's still owned by the device */
if (!cmdp->done)
break;
/*
* Headp points to the first cmd on the list that can
* be deleted.
*
* It may be that the head of the list has been previously
* processed and commands completed since then allow for the
* head to be removed (ouch!).
*
* Everything from headp to lastp will be removed from the
* list.
*/
headp = cmdp;
if (cmdp->processed) {
/*
* If the command has been processed here already,
* it still cannot be reclaimed if it is the last
* done command in the transmit chain.
*/
if ((cmdp->next == NULL) || !cmdp->next->done)
break;
cmdp = cmdp->next;
ASSERT(cmdp != NULL);
/*
* Headp is left pointing at the "processed"
* command while cmdp has advanced to the next command.
*/
}
ASSERT(cmdp->processed == 0);
ASSERT(cmdp->lastfragment != NULL);
lastp = cmdp->lastfragment;
/*
* Check if the last command of the packet is done and do
* nothing if it is still uncompleted.
*/
if (!lastp->done)
break;
mp = (mblk_t *)lastp->dihandle;
ATRACE(audio_xmit_garbage_collect, 'mp ', mp);
switch (cmdp->iotype) {
case M_IOCTL:
ATRACE(audio_xmit_garbage_collect, 'ictl', cmdp);
/* ACK the AUDIO_DRAIN ioctl */
mp->b_datap->db_type = M_IOCACK;
qreply(as->writeq, mp);
lastp->dihandle = 0;
/* ignore error after drain */
(void) AUD_GETFLAG(as, AUD_ERRORRESET);
/*
* Do not delete device's "continuation" command.
*/
headp = cmdp;
/* Delete everything from headp to lastp */
break;
case (unsigned char)(0xff): /* XXX - Pseudo IO, Audio Marker */
ATRACE(audio_xmit_garbage_collect, 'psio', cmdp);
if (mp) {
ATRACE(audio_xmit_garbage_collect, 'fmsg', mp);
freemsg(mp);
mp = NULL;
lastp->dihandle = NULL;
}
headp = cmdp; /* Device's cmd will remain */
if (as->mode == AUDMODE_AUDIO) {
ATRACE(audio_xmit_garbage_collect,
ATR_EOF, mp);
as->info.eof++;
notify = TRUE;
/* ignore error after eof */
(void) AUD_GETFLAG(as, AUD_ERRORRESET);
}
break;
case M_DATA:
ATRACE(audio_xmit_garbage_collect, 'data', cmdp);
/*
* The current aud_cmd has been completely transmitted
* or otherwise processed. Therefore, it is ok to free
* the mblk.
*/
if (mp != NULL) {
ATRACE(audio_xmit_garbage_collect, 'fmsg', mp);
freemsg(mp);
mp = NULL;
lastp->dihandle = NULL;
}
if (cmdp->skip) {
ATRACE(audio_xmit_garbage_collect, 'skip',
cmdp);
/*
* XXX - We will probably have to
* differentiate between "skip" which is
* never seen by the device, and some new
* flag, "error", which is on the device
* IO list.
*/
/*
* There was a transmission error, and
* the packet was marked as "skip" as
* part of discarding it. Transmission
* errors include trying to transmit on
* an inactive channel.
*
* XXX - check for other types of errors,
* does this code still work?
*/
headp = cmdp; /* Device's cmd will remain */
/* Delete this entire aud_cmd */
break;
}
/*
* aud_cmd contained real data.
*/
/*
* If the packet was owned by the device, then it
* may be important for the device specific code
* to retain partial "ownership" of the last
* command so that it can pick up the forward
* pointer if it is told simply to "continue IO".
*
* If the packet following the current packet is
* also marked as "done", then the current packet
* can be completely garbage collected,
* otherwise, the last fragment of the current
* packet must remain on the list.
*/
if (lastp->next != NULL &&
lastp->next->done &&
!lastp->next->skip) {
/*
* This packet, and the possible
* preceeding "processed" command, can be
* completely gc'ed, which is what headp
* and lastp currently indicate.
*
* XXX - Packets marked skip MAY be on
* the device IO list!
*/
;
ATRACE(audio_xmit_garbage_collect, 'all ', mp);
} else if (cmdp == lastp) {
/*
* This packet consists of one fragment
* and there is no completed packet after
* it. It must remain on the chain.
*/
cmdp->processed = TRUE;
/*
* If there was a previously "processed"
* packet at the head of the list, it can
* now be removed.
*/
if (headp != cmdp) {
lastp = headp->lastfragment;
} else {
ATRACE(audio_xmit_garbage_collect,
'nada', cmdp);
headp = NULL; /* XXX - not needed */
lastp = NULL; /* nothing to delete */
}
} else {
aud_cmd_t *p;
/*
* This is a multi-fragment packet where
* all but the last fragment can be
* collected.
*
* Set lastp to the penultimate fragment.
*/
for (p = cmdp;
p != p->lastfragment;
p = p->next) {
lastp = p;
}
ASSERT(lastp != NULL);
cmdp->lastfragment->processed = TRUE;
ATRACE(audio_xmit_garbage_collect,
'pcsd', cmdp->lastfragment);
}
break;
default:
ATRACE(audio_xmit_garbage_collect, 'unkn', cmdp);
if (mp) {
freemsg(mp);
cmdp->dihandle = 0;
}
break;
} /* switch */
/* Delete cmd struct from play list and add to free list */
if (lastp != NULL) {
struct aud_cmd *p;
ASSERT(headp != NULL);
/*
* Be tidy...
*/
for (p = headp; p; p = p->next) {
ASSERT(p->dihandle == 0);
p->lastfragment = 0;
p->iotype = 0;
if (p == lastp)
break;
}
if (v->audio_spl)
s = splr(v->audio_spl);
audio_delete_cmds(&as->cmdlist, headp, lastp);
if (v->audio_spl)
(void) splx(s);
}
} /* for loop */
return (notify);
}
/*
* audio_process_play - Deliver new play buffers to the interrupt routine
* and clean up used buffers.
*
* XXX - Returns TRUE if output buffers are in use. Returns FALSE if
* output drained.
*/
void
audio_process_play(as)
aud_stream_t *as;
{
mblk_t *mp;
int s, ss;
aud_cmd_t *cmdp;
aud_cmd_t *head_cmdp;
int notify;
int iotype;
aud_state_t *v;
as = as->play_as; /* XXX */
v = as->v;
/* If no write access, don't even bother trying */
if (!ISPLAYSTREAM(as)) {
aprintf("audio_process_play: as(%x) not output stream\n",
(unsigned int)as);
return;
}
s = splstr();
/*
* Free recently emptied play buffers.
*/
notify = audio_xmit_garbage_collect(as);
/*
* Dequeue messages as long as there are command blocks available.
*/
mp = NULL;
if (as->cmdlist.free == NULL) {
ATRACEI(audio_process_play, 'full', as);
}
while ((as->cmdlist.free != NULL) && ((mp = getq(as->writeq)) != NULL)){
mblk_t *head_mp;
head_mp = mp;
head_cmdp = 0;
iotype = mp->b_datap->db_type;
/*
* Attach each element of a mblk chain to a command structure.
*/
do {
/*
* Allocate and initialize a command block
*/
/*
* It is assumed that an mblk_t and all of its
* continuation blocks are of the same type.
* The processing of M_DATA messages depends on
* this assumption.
*/
ASSERT(iotype == mp->b_datap->db_type);
/*
* Do not allocate command blocks for null fragments
* in M_DATA messages.
*/
if ((iotype == M_DATA) && (mp->b_rptr == mp->b_wptr)) {
ATRACE(audio_process_play, 'Zlen', mp);
mp = mp->b_cont;
continue;
}
/*
* cmdp gets the next free aud_cmd_t from the free list
*/
audio_alloc_cmd(&as->cmdlist, cmdp);
if (head_cmdp == NULL) {
head_cmdp = cmdp;
}
/*
* Initialize aud_cmd defaults
*/
cmdp->data = 0;
cmdp->enddata = 0;
cmdp->next = 0;
cmdp->lastfragment = NULL;
cmdp->iotype = iotype;
cmdp->skip = FALSE;
cmdp->done = FALSE;
cmdp->processed = FALSE;
cmdp->dihandle = 0;
/*
* AUDIO_DRAIN M_IOCTL, 0 length M_DATA messages (EOF),
* and M_CTL messages go through the command path for
* synchronization but do not get played.
*/
if (iotype != M_DATA) {
cmdp->skip = TRUE;
cmdp->dihandle = (void *)mp;
} else {
/*
* Non-null M_DATA fragment.
*
* Empty M_DATA fragments have already
* been filtered out.
*/
cmdp->skip = FALSE;
cmdp->data = mp->b_rptr;
cmdp->enddata = mp->b_wptr;
}
/*
* NB: although the "driver" list is being appended
* here, the "device" list is not being affected.
* Even if the device is currently running, it is not
* allowed to "notice" these new aud_cmd's until
* the AUD_QUEUE() primitive is executed.
*/
if (v->audio_spl)
ss = splr(v->audio_spl);
audio_append_cmd(&as->cmdlist, cmdp);
if (v->audio_spl)
(void) splx(ss);
/*
* Since the device routine doesn't
* really process non-M_DATA messages,
* it is not important to allocate a
* separate aud_cmd for each one.
*/
if (iotype != M_DATA) {
/* Exit loop successfully */
mp = NULL;
} else {
/* Advance to next mblk on chain */
mp = mp->b_cont;
}
/*
* Stop when there are no more mblk fragments
* or when there are no free aud_cmd_t's left.
*/
} while ((mp != NULL) && (as->cmdlist.free != NULL));
/*
* If non-zero, head_cmdp points to the start of the first
* aud_cmd representing the first M_DATA fragment that has
* some data in it, or, if not an M_DATA message, the first
* fragment of the mblk.
*
* If head_cmdp is zero, it is because the message was a zero
* length M_DATA message. If we are here, there was at least
* one aud_cmd structure on the free list.
*
* If non-zero, cmdp points to the last aud_cmd used to
* represent the mblk.
*/
if (head_cmdp == NULL) {
ATRACE(audio_process_play, ATR_EOF, cmdp);
/*
* Zero length M_DATA is used as an "Audio Marker".
* It is queued the same as a non-data message.
*/
audio_alloc_cmd(&as->cmdlist, cmdp);
/*
* The encompassing while loop condition ensures that
* there is at least one aud_cmd structure available.
*/
ASSERT(cmdp != NULL);
cmdp->iotype = (unsigned char)(0xff); /* XXX */;
cmdp->data = 0;
cmdp->enddata = 0;
cmdp->next = 0;
cmdp->lastfragment = NULL; /* set later */
cmdp->skip = TRUE;
cmdp->done = FALSE;
cmdp->processed = FALSE;
cmdp->dihandle = NULL; /* later set to head_mp */
head_cmdp = cmdp;
if (v->audio_spl)
ss = splr(v->audio_spl);
audio_append_cmd(&as->cmdlist, cmdp);
if (v->audio_spl)
(void) splx(ss);
/*
* XXX - It would be nice to freemsg(mp) now, but
* other code uses the db_type field.
*/
} else if (mp != NULL) {
ATRACE(audio_process_play, ATR_TXOUT, mp);
/*
* If mp is not null, it is because we ran out of
* aud_cmd structures. Release any aud_cmd's that
* may have been used and put the mblk back on
* the queue.
*
* XXX - If mblk_t is larger than MAX resources
* then it will block the queue forever!
*/
/*
* Release mblk and command chains at the tail of
* the list.
*/
putbq(as->writeq, head_mp); /* restore mblk chain */
if (v->audio_spl)
ss = splr(v->audio_spl);
audio_delete_cmds(&as->cmdlist, head_cmdp, cmdp);
if (v->audio_spl)
(void) splx(ss);
/*
* Don't try to process any more mblk's at this
* time.
*/
break;
}
/*
* Cmdp still points to the last fragment in the chain.
* Set the lastfragment pointer in each aud_cmd to point
* to the last fragment to simplify future processing.
*/
{
struct aud_cmd *p;
for (p = head_cmdp; p; p = p->next) {
p->lastfragment = cmdp;
}
}
ASSERT(head_cmdp->lastfragment != 0);
ASSERT(head_cmdp->lastfragment->lastfragment != 0);
/*
* The last fragment gets the pointer to the mblk.
*/
head_cmdp->lastfragment->dihandle = (void *)head_mp;
/*
* Make the device aware of the new output tasks
*/
if (v->audio_spl)
ss = splr(v->audio_spl);
AUD_QUEUECMD(as, head_cmdp);
if (v->audio_spl)
(void) splx(ss);
}
ATRACE(audio_process_play, 'DONE', cmdp);
/*
* The device dependent portion of the driver is responsible for
* completing pseudo IO. The device dependent driver must mark
* the pseudo IO as "done" and then call audio_xmit_garbage_collect().
*/
/*
* If no messages left, and no data in write buffers, wake up
* audio_close() if necessary. Ignore errors if draining.
*
* XXX - The test for "empty list" is ugly due to the "processed"
* fragment that may be at the end of the list.
*/
if (as->draining && (mp == NULL) &&
((as->cmdlist.head == NULL) ||
(as->cmdlist.head == as->cmdlist.tail &&
as->cmdlist.head->processed))) {
as->draining = FALSE;
wakeup((caddr_t)&as->draining);
} else if (AUD_GETFLAG(as, AUD_ERRORRESET)) {
/* Only signal when this flag is set for the first time */
if (!as->info.error) {
as->info.error = TRUE;
notify = TRUE;
}
}
(void) splx(s);
/* If a state change occurred, send a signal to the control device */
if (notify)
audio_sendsig(as->control_as);
return;
} /* audio_process_play */
/*
* Since putnext() is a macro, it is convenient to have this simple read
* put procedure to keep from having to dequeue packets in the service
* procedure.
*/
void
audio_rput(q, mp)
queue_t *q;
mblk_t *mp;
{
ATRACE(audio_rput, ATR_RQ, mp->b_datap->db_type);
aprintf("NOT EXPECTED: audio_rput called!\n");
call_debug("audio_rput");
putnext(q, mp);
return;
}
/*
* The read service procedure is scheduled when the upstream read queue
* is flushed, to make sure that further record buffers are processed.
*
* It can also be scheduled if the driver is flow controlled by (canput() == 0)
*/
void
audio_rsrv(q)
queue_t *q;
{
aud_stream_t *as;
ATRACE(audio_rsrv, ATR_NONE, q);
as = (aud_stream_t *)q->q_ptr;
audio_process_record(as->record_as);
return;
}
/*
* audio_receive_collect - Collect completed receive buffers. Also
* garbage collect unused receive buffers when IO has been stopped.
* Return 1 if read side was flow controlled.
*/
static int
audio_receive_collect(as)
aud_stream_t *as;
{
mblk_t *mp;
struct {
aud_cmd_t *head;
aud_cmd_t *tail;
} packet;
aud_cmd_t *cmdp;
int flow_control, s;
aud_state_t *v;
ASSERT(as != NULL);
v = as->v;
for (cmdp = as->cmdlist.head;
cmdp != NULL && cmdp->done;
cmdp = as->cmdlist.head) {
packet.head = NULL;
do {
mp = (mblk_t *)cmdp->dihandle; /* get buffer ptr */
ASSERT(mp != NULL);
/*
* Empty fragments should not hurt anyone.
* Packet.head gets set to 1st non-empty fragment.
*/
if (cmdp->skip) {
ATRACEI(audio_receive_collect, 'FREE', mp);
freemsg(mp);
/* XXX - TIDY this up */
cmdp->dihandle = 0;
cmdp->data = 0;
cmdp->enddata = 0;
mp = 0;
/*
* XXX - If this is going to remain a
* subroutine, it should return both
* "notify" and "flow_control".
*/
audio_sendsig(as); /* notify user of error */
} else {
/* Set STREAMS end of data */
mp->b_wptr = cmdp->data;
ATRACEI(audio_receive_collect,
'frag', mp->b_wptr - mp->b_rptr);
/*
* If start of packet not set yet, this must
* be it. Don't chain the 1st fragment to the
* "previous".
*/
if (packet.head == NULL) {
packet.head = cmdp;
} else {
mblk_t *tmp;
/* Chain up current mblk to list */
tmp = (mblk_t *)packet.tail->dihandle;
tmp->b_cont = mp;
}
}
packet.tail = cmdp;
if (cmdp == cmdp->lastfragment)
break;
} while ((cmdp = cmdp->next) != NULL);
if (packet.head) {
/*
* Collect new received packets on driver's private
* readq for this aud_stream.
*/
ASSERT(as->readq->q_flag & QREADR);
mp = (mblk_t *)packet.head->dihandle;
putq(as->readq, mp);
}
/*
* XXX - As soon as we start using DBRI's CDP command for
* the receive side, we will need logic similar to that
* in audio_xmit_garbage_collect() in order to maintain
* an end-of-list command structure for the benefit of
* the device.
*/
if (v->audio_spl)
s = splr(v->audio_spl);
audio_delete_cmds(&as->cmdlist, as->cmdlist.head,
packet.tail);
if (v->audio_spl)
(void) splx(s);
} /* for loop */
ATRACEI(audio_receive_collect, 'DONE', cmdp);
flow_control = FALSE;
ASSERT(as->readq->q_flag & QREADR);
while ((mp = getq(as->readq)) != NULL) {
if (mp->b_datap->db_type <= QPCTL &&
!canput(as->readq->q_next)) {
ATRACEI(audio_receive_collect,
'flow', as->readq->q_next);
putbq(as->readq, mp); /* read side is blocked */
flow_control = TRUE;
break;
}
ATRACEI(audio_receive_collect, 'putn', mp);
/*
* Flow control is ok. Send received packet to upper module.
*/
putnext(as->readq, mp);
}
return (flow_control);
}
/*
* Send record buffers upstream, if ready. If recording is not paused,
* make sure record buffers are allocated.
*/
void
audio_process_record(as)
aud_stream_t *as;
{
mblk_t *mp;
int s, ss;
aud_cmd_t *cmdp;
aud_cmd_t *headp;
int flow_control;
aud_state_t *v;
ASSERT(as != NULL);
v = as->v;
/* If no read access, don't bother even trying */
if (!ISRECORDSTREAM(as)) {
ATRACE(audio_process_record, 'bgus', as);
return;
}
ATRACE(audio_process_record, 'recP', as);
/*
* Collect finished record buffers and send upstream. If
* recording was paused, all buffers were marked done, even if
* they were unused. The same goes for error condition.
*
* Note: We need to chain up potentially multiple mblks for
* datacomm.
*/
s = splstr();
flow_control = audio_receive_collect(as);
if (flow_control) {
ATRACE(audio_process_record, '-fc-', as);
} else {
ATRACE(audio_process_record, '-ok-', as);
}
/*
* If paused or upstream flow control hit high water, don't
* allocate new record buffers.
*/
headp = (aud_cmd_t *)NULL;
if (!as->info.pause && !flow_control) {
/*
* As long as there are free command blocks, allocate new
* buffers for recording.
*/
mp = NULL;
while ((as->cmdlist.free != NULL) &&
((mp = allocb(as->input_size + 8, BPRI_MED)) != NULL)) {
/* XXX3 - 2 word safety region on buffer */
/* Allocate and initialize a command block */
audio_alloc_cmd(&as->cmdlist, cmdp);
if (headp == NULL) {
headp = cmdp;
}
cmdp->data = mp->b_rptr = mp->b_wptr;
cmdp->enddata = cmdp->data + as->input_size;
ASSERT(cmdp->enddata - cmdp->data >= as->input_size);
cmdp->dihandle = (void *)mp;
/* iotype not used for receive */
cmdp->iotype = M_DATA;
cmdp->lastfragment = cmdp; /* not known yet */
cmdp->done = FALSE;
cmdp->skip = FALSE;
cmdp->processed = FALSE;
/* Add it to the cmd chain */
if (v->audio_spl)
ss = splr(v->audio_spl);
audio_append_cmd(&as->cmdlist, cmdp);
if (v->audio_spl)
(void) splx(ss);
ATRACE(audio_process_record, ATR_NEWR, mp);
}
if ((mp == NULL) && (as->cmdlist.free != NULL)) {
aprintf("audio_process_record: allocb mp == NULL\n");
}
/*
* Queue up dbri cmd after available free cmds are
* chained up and send to device if we have allocated new
* command blocks.
*/
if (headp != NULL) {
if (v->audio_spl)
ss = splr(v->audio_spl);
AUD_QUEUECMD(as, headp);
if (v->audio_spl)
(void) splx(ss);
}
}
/* If record overflow occurred, send a signal to the control device */
if (AUD_GETFLAG(as, AUD_ERRORRESET)) {
/* Only signal when this flag is set for the first time */
if (!as->info.error) {
as->info.error = TRUE;
audio_sendsig(as->control_as);
}
}
(void) splx(s);
return;
} /* audio_process_record */
/*
* Send a SIGPOLL up the specified stream.
*/
void
audio_sendsig(as)
aud_stream_t *as; /* always points to write side */
{
mblk_t *notify_mp;
/* If stream is not open, simply return */
if (as->readq == NULL)
return;
/* Init a message to send a SIGPOLL upstream */
if ((notify_mp = allocb(sizeof (char), BPRI_HI)) == NULL)
return;
notify_mp->b_datap->db_type = M_PCSIG;
*notify_mp->b_wptr++ = SIGPOLL;
/* Signal the specified stream */
ATRACE(audio_sendsig, ATR_SIGCTL, as);
putnext(as->readq, notify_mp);
return;
}
/*
* The next two routines are used to pause reads or writes. Pause is
* used to temporarily suspend i/o without losing the contents of the
* buffer.
*/
void
audio_pause_record(as)
aud_stream_t *as;
{
aud_cmd_t *cmdp;
if (!ISRECORDSTREAM(as) || (as->mode != AUDMODE_AUDIO))
return;
as->info.pause = TRUE;
AUD_STOP(as);
/*
* When recording is paused, partially filled buffers are sent
* upstream and unused buffers are released. Mark all command
* buffers done and let audio_process_record() handle them.
*
* XXX - There could be a problem here as packets have multiple
* cmds.
*/
for (cmdp = as->cmdlist.head; cmdp != NULL; cmdp = cmdp->next)
cmdp->done = TRUE;
/* Flush the device's chained command list */
AUD_FLUSHCMD(as);
/* Process partially filled buffer and release the rest */
audio_process_record(as);
return;
}
void
audio_pause_play(as)
aud_stream_t *as;
{
int s;
if (as != as->play_as) {
aprintf("pause_play called for non-play stream!\n");
return;
}
if (as->info.open && (as->mode == AUDMODE_AUDIO)) {
s = splstr();
as->info.pause = TRUE;
AUD_STOP(as);
(void) splx(s);
}
return;
}
/*
* The next two routines are called from ioctls to resume paused
* read/write.
*/
void
audio_resume_record(as)
aud_stream_t *as;
{
if (!as->info.pause)
return;
/* Must clear pause flag before calling audio_process_record */
as->info.pause = FALSE;
/* audio_process_record() will call the AUD_START routine */
audio_process_record(as);
return;
}
void
audio_resume_play(as)
aud_stream_t *as;
{
if (!as->info.pause)
return;
/* Must clear pause flag before calling audio_process_play */
as->info.pause = FALSE;
/* Queue up output buffers and enable output conversion */
audio_process_play(as);
AUD_START(as);
return;
}