#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/kobj.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#include <sys/uio.h>
#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#endif
#include <dev/sound/midi/midi.h>
#include <dev/sound/midi/midiq.h>
#include "mpu_if.h"
MALLOC_DEFINE(M_MIDI, "midi buffers", "Midi data allocation area");
#define MIDI_NAMELEN 16
struct snd_midi {
KOBJ_FIELDS;
struct mtx lock;
void *cookie;
int unit;
int channel;
int flags;
MIDIQ_HEAD(, char) inq, outq;
int rchan, wchan;
struct selinfo rsel, wsel;
int hiwat;
struct cdev *dev;
};
static d_open_t midi_open;
static d_close_t midi_close;
static d_ioctl_t midi_ioctl;
static d_read_t midi_read;
static d_write_t midi_write;
static d_poll_t midi_poll;
static struct cdevsw midi_cdevsw = {
.d_version = D_VERSION,
.d_open = midi_open,
.d_close = midi_close,
.d_read = midi_read,
.d_write = midi_write,
.d_ioctl = midi_ioctl,
.d_poll = midi_poll,
.d_name = "midi",
};
struct unrhdr *dev_unr = NULL;
struct unrhdr *chn_unr = NULL;
struct snd_midi *
midi_init(kobj_class_t cls, void *cookie)
{
struct snd_midi *m;
int inqsize, outqsize;
uint8_t *ibuf = NULL;
uint8_t *obuf = NULL;
m = malloc(sizeof(*m), M_MIDI, M_WAITOK | M_ZERO);
kobj_init((kobj_t)m, cls);
inqsize = MPU_INQSIZE(m, cookie);
outqsize = MPU_OUTQSIZE(m, cookie);
if (!inqsize && !outqsize)
goto err1;
mtx_init(&m->lock, "raw midi", NULL, 0);
if (inqsize)
ibuf = malloc(inqsize, M_MIDI, M_WAITOK);
if (outqsize)
obuf = malloc(outqsize, M_MIDI, M_WAITOK);
mtx_lock(&m->lock);
m->hiwat = outqsize / 2;
MIDIQ_INIT(m->inq, ibuf, inqsize);
MIDIQ_INIT(m->outq, obuf, outqsize);
m->flags = 0;
m->unit = alloc_unr(dev_unr);
m->channel = alloc_unr(chn_unr);
m->cookie = cookie;
if (MPU_INIT(m, cookie))
goto err2;
mtx_unlock(&m->lock);
m->dev = make_dev(&midi_cdevsw, m->unit, UID_ROOT, GID_AUDIO, 0660,
"midi%d.%d", m->unit, m->channel);
m->dev->si_drv1 = m;
return m;
err2:
mtx_destroy(&m->lock);
free(MIDIQ_BUF(m->inq), M_MIDI);
free(MIDIQ_BUF(m->outq), M_MIDI);
err1:
free(m, M_MIDI);
return NULL;
}
int
midi_uninit(struct snd_midi *m)
{
mtx_lock(&m->lock);
if (m->rchan) {
wakeup(&m->rchan);
m->rchan = 0;
}
if (m->wchan) {
wakeup(&m->wchan);
m->wchan = 0;
}
mtx_unlock(&m->lock);
MPU_UNINIT(m, m->cookie);
destroy_dev(m->dev);
free_unr(dev_unr, m->unit);
free_unr(chn_unr, m->channel);
free(MIDIQ_BUF(m->inq), M_MIDI);
free(MIDIQ_BUF(m->outq), M_MIDI);
mtx_destroy(&m->lock);
free(m, M_MIDI);
return (0);
}
int
midi_in(struct snd_midi *m, uint8_t *buf, int size)
{
int used;
mtx_lock(&m->lock);
if (!(m->flags & M_RX)) {
mtx_unlock(&m->lock);
return (size);
}
used = 0;
if (MIDIQ_AVAIL(m->inq) > size) {
used = size;
MIDIQ_ENQ(m->inq, buf, size);
} else {
mtx_unlock(&m->lock);
return 0;
}
if (m->rchan) {
wakeup(&m->rchan);
m->rchan = 0;
}
selwakeup(&m->rsel);
mtx_unlock(&m->lock);
return used;
}
int
midi_out(struct snd_midi *m, uint8_t *buf, int size)
{
int used;
mtx_lock(&m->lock);
if (!(m->flags & M_TXEN)) {
mtx_unlock(&m->lock);
return (0);
}
used = min(size, MIDIQ_LEN(m->outq));
if (used)
MIDIQ_DEQ(m->outq, buf, used);
if (MIDIQ_EMPTY(m->outq)) {
m->flags &= ~M_TXEN;
MPU_CALLBACK(m, m->cookie, m->flags);
}
if (used && MIDIQ_AVAIL(m->outq) > m->hiwat) {
if (m->wchan) {
wakeup(&m->wchan);
m->wchan = 0;
}
selwakeup(&m->wsel);
}
mtx_unlock(&m->lock);
return used;
}
int
midi_open(struct cdev *i_dev, int flags, int mode, struct thread *td)
{
struct snd_midi *m = i_dev->si_drv1;
int retval;
if (m == NULL)
return ENXIO;
mtx_lock(&m->lock);
retval = 0;
if (flags & FREAD) {
if (MIDIQ_SIZE(m->inq) == 0)
retval = ENXIO;
else if (m->flags & M_RX)
retval = EBUSY;
if (retval)
goto err;
}
if (flags & FWRITE) {
if (MIDIQ_SIZE(m->outq) == 0)
retval = ENXIO;
else if (m->flags & M_TX)
retval = EBUSY;
if (retval)
goto err;
}
m->rchan = 0;
m->wchan = 0;
if (flags & FREAD) {
m->flags |= M_RX | M_RXEN;
MIDIQ_CLEAR(m->inq);
}
if (flags & FWRITE)
m->flags |= M_TX;
MPU_CALLBACK(m, m->cookie, m->flags);
err:
mtx_unlock(&m->lock);
return retval;
}
int
midi_close(struct cdev *i_dev, int flags, int mode, struct thread *td)
{
struct snd_midi *m = i_dev->si_drv1;
int retval;
int oldflags;
if (m == NULL)
return ENXIO;
mtx_lock(&m->lock);
if ((flags & FREAD && !(m->flags & M_RX)) ||
(flags & FWRITE && !(m->flags & M_TX))) {
retval = ENXIO;
goto err;
}
oldflags = m->flags;
if (flags & FREAD)
m->flags &= ~(M_RX | M_RXEN);
if (flags & FWRITE)
m->flags &= ~M_TX;
if ((m->flags & (M_TXEN | M_RXEN)) != (oldflags & (M_RXEN | M_TXEN)))
MPU_CALLBACK(m, m->cookie, m->flags);
mtx_unlock(&m->lock);
retval = 0;
err: return retval;
}
int
midi_read(struct cdev *i_dev, struct uio *uio, int ioflag)
{
#define MIDI_RSIZE 32
struct snd_midi *m = i_dev->si_drv1;
int retval;
int used;
char buf[MIDI_RSIZE];
retval = EIO;
if (m == NULL)
goto err0;
mtx_lock(&m->lock);
if (!(m->flags & M_RX))
goto err1;
while (uio->uio_resid > 0) {
while (MIDIQ_EMPTY(m->inq)) {
retval = EWOULDBLOCK;
if (ioflag & O_NONBLOCK)
goto err1;
m->rchan = 1;
retval = msleep(&m->rchan, &m->lock,
PCATCH | PDROP, "midi RX", 0);
if (retval == EINTR)
goto err0;
if (m != i_dev->si_drv1)
retval = ENXIO;
if (retval)
goto err0;
mtx_lock(&m->lock);
m->rchan = 0;
}
used = min(MIDIQ_LEN(m->inq), uio->uio_resid);
used = min(used, MIDI_RSIZE);
MIDIQ_DEQ(m->inq, buf, used);
mtx_unlock(&m->lock);
retval = uiomove(buf, used, uio);
if (retval)
goto err0;
mtx_lock(&m->lock);
}
retval = 0;
err1:
mtx_unlock(&m->lock);
err0:
return retval;
}
int
midi_write(struct cdev *i_dev, struct uio *uio, int ioflag)
{
#define MIDI_WSIZE 32
struct snd_midi *m = i_dev->si_drv1;
int retval;
int used;
char buf[MIDI_WSIZE];
retval = EIO;
if (m == NULL)
goto err0;
mtx_lock(&m->lock);
if (!(m->flags & M_TX))
goto err1;
while (uio->uio_resid > 0) {
while (MIDIQ_AVAIL(m->outq) == 0) {
retval = EWOULDBLOCK;
if (ioflag & O_NONBLOCK)
goto err1;
m->wchan = 1;
retval = msleep(&m->wchan, &m->lock,
PCATCH | PDROP, "midi TX", 0);
if (retval == EINTR)
goto err0;
if (m != i_dev->si_drv1)
retval = ENXIO;
if (retval)
goto err0;
mtx_lock(&m->lock);
m->wchan = 0;
}
used = min(MIDIQ_AVAIL(m->outq), uio->uio_resid);
used = min(used, MIDI_WSIZE);
mtx_unlock(&m->lock);
retval = uiomove(buf, used, uio);
if (retval)
goto err0;
mtx_lock(&m->lock);
MIDIQ_ENQ(m->outq, buf, used);
if (!(m->flags & M_TXEN)) {
m->flags |= M_TXEN;
MPU_CALLBACK(m, m->cookie, m->flags);
}
}
retval = 0;
err1:
mtx_unlock(&m->lock);
err0: return retval;
}
int
midi_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode,
struct thread *td)
{
return ENXIO;
}
int
midi_poll(struct cdev *i_dev, int events, struct thread *td)
{
struct snd_midi *m = i_dev->si_drv1;
int revents;
if (m == NULL)
return 0;
revents = 0;
mtx_lock(&m->lock);
if (events & (POLLIN | POLLRDNORM)) {
if (!MIDIQ_EMPTY(m->inq))
revents |= events & (POLLIN | POLLRDNORM);
else
selrecord(td, &m->rsel);
}
if (events & (POLLOUT | POLLWRNORM)) {
if (MIDIQ_AVAIL(m->outq) < m->hiwat)
revents |= events & (POLLOUT | POLLWRNORM);
else
selrecord(td, &m->wsel);
}
mtx_unlock(&m->lock);
return (revents);
}
static void
midi_sysinit(void *data __unused)
{
dev_unr = new_unrhdr(0, INT_MAX, NULL);
chn_unr = new_unrhdr(0, INT_MAX, NULL);
}
SYSINIT(midi_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, midi_sysinit, NULL);
static void
midi_sysuninit(void *data __unused)
{
if (dev_unr != NULL)
delete_unrhdr(dev_unr);
if (chn_unr != NULL)
delete_unrhdr(chn_unr);
}
SYSUNINIT(midi_sysuninit, SI_SUB_DRIVERS, SI_ORDER_ANY, midi_sysuninit, NULL);