freebsd-dev/sys/dev/sound/midi/midibuf.c
Alfred Perlstein 85f190e4d1 Fixes to make select/poll mpsafe.
Problem:
  selwakeup required calling pfind which would cause lock order
  reversals with the allproc_lock and the per-process filedesc lock.
Solution:
  Instead of recording the pid of the select()'ing process into the
  selinfo structure, actually record a pointer to the thread.  To
  avoid dereferencing a bad address all the selinfo structures that
  are in use by a thread are kept in a list hung off the thread
  (protected by sellock).  When a selwakeup occurs the selinfo is
  removed from that threads list, it is also removed on the way out
  of select or poll where the thread will traverse its list removing
  all the selinfos from its own list.

Problem:
  Previously the PROC_LOCK was used to provide the mutual exclusion
  needed to ensure proper locking, this couldn't work because there
  was a single condvar used for select and poll and condvars can
  only be used with a single mutex.
Solution:
  Introduce a global mutex 'sellock' which is used to provide mutual
  exclusion when recording events to wait on as well as performing
  notification when an event occurs.

Interesting note:
  schedlock is required to manipulate the per-thread TDF_SELECT
  flag, however if given its own field it would not need schedlock,
  also because TDF_SELECT is only manipulated under sellock one
  doesn't actually use schedlock for syncronization, only to protect
  against corruption.

Proc locks are no longer used in select/poll.

Portions contributed by: davidc
2002-03-14 01:32:30 +00:00

404 lines
9.4 KiB
C

/*
* Copyright (C) 1999 Seigo Tanimura
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS
* IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*
*/
/*
* This file implements a midi event/message queue. A midi
* event/message queue holds midi events and messages to
* transmit to or received from a midi interface.
*/
#include <dev/sound/midi/midi.h>
/* Some macros to handle the queue. */
#define DATA_AVAIL(dbuf) ((dbuf)->rl)
#define SPACE_AVAIL(dbuf) ((dbuf)->fl)
static void queuerawdata(midi_dbuf *dbuf, char *data, int len);
static void dequeuerawdata(midi_dbuf *dbuf, char *data, int len);
static void copyrawdata(midi_dbuf *dbuf, int offset, char *data, int len);
static void deleterawdata(midi_dbuf *dbuf, int len);
/*
* Here are the functions to interact to the midi device drivers.
* These are called from midi device driver functions under sys/i386/isa/snd.
*/
int
midibuf_init(midi_dbuf *dbuf)
{
if (dbuf->buf == NULL) {
dbuf->buf = malloc(MIDI_BUFFSIZE, M_DEVBUF, M_WAITOK | M_ZERO);
cv_init(&dbuf->cv_in, "midi queue in");
cv_init(&dbuf->cv_out, "midi queue out");
}
return (midibuf_clear(dbuf));
}
int
midibuf_destroy(midi_dbuf *dbuf)
{
if (dbuf->buf != NULL) {
free(dbuf->buf, M_DEVBUF);
cv_destroy(&dbuf->cv_in);
cv_destroy(&dbuf->cv_out);
}
return (0);
}
int
midibuf_clear(midi_dbuf *dbuf)
{
bzero(dbuf->buf, MIDI_BUFFSIZE);
dbuf->bufsize = MIDI_BUFFSIZE;
dbuf->rp = dbuf->fp = 0;
dbuf->dl = 0;
dbuf->rl = 0;
dbuf->fl = dbuf->bufsize;
dbuf->int_count = 0;
dbuf->chan = 0;
/*dbuf->unit_size = 1;*/ /* The drivers are responsible. */
bzero(&dbuf->sel, sizeof(dbuf->sel));
dbuf->total = 0;
dbuf->prev_total = 0;
dbuf->blocksize = dbuf->bufsize / 4;
return (0);
}
/* The sequencer calls this function to queue data. */
int
midibuf_seqwrite(midi_dbuf *dbuf, u_char* data, int len, int *lenw, midi_callback_t *cb, void *d, int reason, struct mtx *m)
{
int i, lwrt;
if (m != NULL)
mtx_assert(m, MA_OWNED);
if (lenw == NULL)
return (EINVAL);
*lenw = 0;
/* Is this a real queue? */
if (dbuf == (midi_dbuf *)NULL)
return (EINVAL);
/* Write down every single byte. */
while (len > 0) {
/* Find out the number of bytes to write. */
lwrt = SPACE_AVAIL(dbuf);
if (lwrt > len)
lwrt = len;
if (lwrt > 0) {
/* We can write some now. Queue the data. */
queuerawdata(dbuf, data, lwrt);
*lenw += lwrt;
len -= lwrt;
data += lwrt;
}
if (cb != NULL)
(*cb)(d, reason);
/* Have we got still more data to write? */
if (len > 0) {
/* Sleep until we have enough space. */
i = cv_wait_sig(&dbuf->cv_out, m);
if (i == EINTR || i == ERESTART)
return (i);
}
}
return (0);
}
int
midibuf_output_intr(midi_dbuf *dbuf, u_char *data, int len, int *leno)
{
if (leno == NULL)
return (EINVAL);
*leno = 0;
/* Is this a real queue? */
if (dbuf == (midi_dbuf *)NULL)
return (EINVAL);
/* Have we got any data in the queue? */
*leno = DATA_AVAIL(dbuf);
if (*leno == 0)
return (EAGAIN);
/* Dequeue the data. */
if (*leno > len)
*leno = len;
dequeuerawdata(dbuf, data, *leno);
return (0);
}
int
midibuf_input_intr(midi_dbuf *dbuf, u_char *data, int len, int *leni)
{
if (leni == NULL)
return (EINVAL);
*leni = 0;
/* Is this a real queue? */
if (dbuf == (midi_dbuf *)NULL)
return (EINVAL);
/* Have we got any data to write? */
if (len == 0)
return (0);
/* Can we write now? */
if (SPACE_AVAIL(dbuf) < len)
return (EAGAIN);
/* We can write some now. Queue the data. */
queuerawdata(dbuf, data, len);
*leni = len;
return (0);
}
/* The sequencer calls this function to dequeue data. */
int
midibuf_seqread(midi_dbuf *dbuf, u_char* data, int len, int *lenr, midi_callback_t *cb, void *d, int reason, struct mtx *m)
{
int i, lrd;
if (m != NULL)
mtx_assert(m, MA_OWNED);
if (lenr == NULL)
return (EINVAL);
*lenr = 0;
/* Is this a real queue? */
if (dbuf == (midi_dbuf *)NULL)
return (EINVAL);
/* Write down every single byte. */
while (len > 0) {
if (cb != NULL)
(*cb)(d, reason);
/* Have we got data to read? */
if ((lrd = DATA_AVAIL(dbuf)) == 0) {
/* Sleep until we have data ready to read. */
i = cv_wait_sig(&dbuf->cv_in, m);
if (i == EINTR || i == ERESTART)
return (i);
/* Find out the number of bytes to read. */
lrd = DATA_AVAIL(dbuf);
}
if (lrd > len)
lrd = len;
if (lrd > 0) {
/* We can read some data now. Dequeue the data. */
dequeuerawdata(dbuf, data, lrd);
*lenr += lrd;
len -= lrd;
data += lrd;
}
}
return (0);
}
/* The sequencer calls this function to copy data without dequeueing. */
int
midibuf_seqcopy(midi_dbuf *dbuf, u_char* data, int len, int *lenc, midi_callback_t *cb, void *d, int reason, struct mtx *m)
{
int i, lrd;
if (m != NULL)
mtx_assert(m, MA_OWNED);
if (lenc == NULL)
return (EINVAL);
*lenc = 0;
/* Is this a real queue? */
if (dbuf == (midi_dbuf *)NULL)
return (EINVAL);
/* Write down every single byte. */
while (len > 0) {
if (cb != NULL)
(*cb)(d, reason);
/* Have we got data to read? */
if ((lrd = DATA_AVAIL(dbuf)) == 0) {
/* Sleep until we have data ready to read. */
i = cv_wait_sig(&dbuf->cv_in, m);
if (i == EINTR || i == ERESTART)
return (i);
/* Find out the number of bytes to read. */
lrd = DATA_AVAIL(dbuf);
}
if (lrd > len)
lrd = len;
if (lrd > 0) {
/* We can read some data now. Copy the data. */
copyrawdata(dbuf, *lenc, data, lrd);
*lenc += lrd;
len -= lrd;
data += lrd;
}
}
return (0);
}
/*
* The sequencer calls this function to delete the data
* that the sequencer has already read.
*/
int
midibuf_seqdelete(midi_dbuf *dbuf, int len, int *lenr, midi_callback_t *cb, void *d, int reason, struct mtx *m)
{
int i, lrd;
if (m != NULL)
mtx_assert(m, MA_OWNED);
if (lenr == NULL)
return (EINVAL);
*lenr = 0;
/* Is this a real queue? */
if (dbuf == (midi_dbuf *)NULL)
return (EINVAL);
/* Write down every single byte. */
while (len > 0) {
if (cb != NULL)
(*cb)(d, reason);
/* Have we got data to read? */
if ((lrd = DATA_AVAIL(dbuf)) == 0) {
/* Sleep until we have data ready to read. */
i = cv_wait_sig(&dbuf->cv_in, m);
if (i == EINTR || i == ERESTART)
return (i);
/* Find out the number of bytes to read. */
lrd = DATA_AVAIL(dbuf);
}
if (lrd > len)
lrd = len;
if (lrd > 0) {
/* We can read some data now. Delete the data. */
deleterawdata(dbuf, lrd);
*lenr += lrd;
len -= lrd;
}
}
return (0);
}
/*
* The functions below here are the libraries for the above ones.
*/
static void
queuerawdata(midi_dbuf *dbuf, char *data, int len)
{
/* dbuf->fp might wrap around dbuf->bufsize. */
if (dbuf->bufsize - dbuf->fp < len) {
/* The new data wraps, copy them twice. */
bcopy(data, dbuf->buf + dbuf->fp, dbuf->bufsize - dbuf->fp);
bcopy(data + dbuf->bufsize - dbuf->fp, dbuf->buf, len - (dbuf->bufsize - dbuf->fp));
} else
/* The new data do not wrap, once is enough. */
bcopy(data, dbuf->buf + dbuf->fp, len);
/* Adjust the pointer and the length counters. */
dbuf->fp = (dbuf->fp + len) % dbuf->bufsize;
dbuf->fl -= len;
dbuf->rl += len;
/* Wake up the processes sleeping on input data. */
cv_broadcast(&dbuf->cv_in);
if (SEL_WAITING(&dbuf->sel) && dbuf->rl >= dbuf->blocksize)
selwakeup(&dbuf->sel);
}
static void
dequeuerawdata(midi_dbuf *dbuf, char *data, int len)
{
/* Copy the data. */
copyrawdata(dbuf, 0, data, len);
/* Delete the data. */
deleterawdata(dbuf, len);
}
static void
copyrawdata(midi_dbuf *dbuf, int offset, char *data, int len)
{
int rp;
rp = (dbuf->rp + offset) % dbuf->bufsize;
/* dbuf->rp might wrap around dbuf->bufsize. */
if (dbuf->bufsize - rp < len) {
/* The data to be read wraps, copy them twice. */
bcopy(dbuf->buf + rp, data, dbuf->bufsize - rp);
bcopy(dbuf->buf, data + dbuf->bufsize - rp, len - (dbuf->bufsize - rp));
} else
/* The new data do not wrap, once is enough. */
bcopy(dbuf->buf + rp, data, len);
}
static void
deleterawdata(midi_dbuf *dbuf, int len)
{
/* Adjust the pointer and the length counters. */
dbuf->rp = (dbuf->rp + len) % dbuf->bufsize;
dbuf->rl -= len;
dbuf->fl += len;
/* Wake up the processes sleeping on queueing. */
cv_broadcast(&dbuf->cv_out);
if (SEL_WAITING(&dbuf->sel) && dbuf->fl >= dbuf->blocksize)
selwakeup(&dbuf->sel);
}