#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 #include #include #include #include #include #include #include #include #include #include #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; }