589 lines
13 KiB
C
589 lines
13 KiB
C
/* $OpenBSD: midi.c,v 1.57 2024/05/13 01:15:50 jsg Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2003, 2004 Alexandre Ratchov
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/timeout.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/signalvar.h>
|
|
#include <sys/device.h>
|
|
|
|
#include <dev/midi_if.h>
|
|
#include <dev/audio_if.h>
|
|
#include <dev/midivar.h>
|
|
|
|
#define DEVNAME(sc) ((sc)->dev.dv_xname)
|
|
|
|
int midiopen(dev_t, int, int, struct proc *);
|
|
int midiclose(dev_t, int, int, struct proc *);
|
|
int midiread(dev_t, struct uio *, int);
|
|
int midiwrite(dev_t, struct uio *, int);
|
|
int midikqfilter(dev_t, struct knote *);
|
|
int midiioctl(dev_t, u_long, caddr_t, int, struct proc *);
|
|
int midiprobe(struct device *, void *, void *);
|
|
void midiattach(struct device *, struct device *, void *);
|
|
int mididetach(struct device *, int);
|
|
int midiprint(void *, const char *);
|
|
|
|
void midi_iintr(void *, int);
|
|
void midi_ointr(void *);
|
|
void midi_timeout(void *);
|
|
void midi_out_start(struct midi_softc *);
|
|
void midi_out_stop(struct midi_softc *);
|
|
void midi_out_do(struct midi_softc *);
|
|
|
|
|
|
const struct cfattach midi_ca = {
|
|
sizeof(struct midi_softc), midiprobe, midiattach, mididetach
|
|
};
|
|
|
|
struct cfdriver midi_cd = {
|
|
NULL, "midi", DV_DULL
|
|
};
|
|
|
|
|
|
void filt_midiwdetach(struct knote *);
|
|
int filt_midiwrite(struct knote *, long);
|
|
int filt_midimodify(struct kevent *, struct knote *);
|
|
int filt_midiprocess(struct knote *, struct kevent *);
|
|
|
|
const struct filterops midiwrite_filtops = {
|
|
.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
|
|
.f_attach = NULL,
|
|
.f_detach = filt_midiwdetach,
|
|
.f_event = filt_midiwrite,
|
|
.f_modify = filt_midimodify,
|
|
.f_process = filt_midiprocess,
|
|
};
|
|
|
|
void filt_midirdetach(struct knote *);
|
|
int filt_midiread(struct knote *, long);
|
|
|
|
const struct filterops midiread_filtops = {
|
|
.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
|
|
.f_attach = NULL,
|
|
.f_detach = filt_midirdetach,
|
|
.f_event = filt_midiread,
|
|
.f_modify = filt_midimodify,
|
|
.f_process = filt_midiprocess,
|
|
};
|
|
|
|
void
|
|
midi_buf_wakeup(struct midi_buffer *buf)
|
|
{
|
|
if (buf->blocking) {
|
|
wakeup(&buf->blocking);
|
|
buf->blocking = 0;
|
|
}
|
|
knote_locked(&buf->klist, 0);
|
|
}
|
|
|
|
void
|
|
midi_iintr(void *addr, int data)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)addr;
|
|
struct midi_buffer *mb = &sc->inbuf;
|
|
|
|
MUTEX_ASSERT_LOCKED(&audio_lock);
|
|
if (!(sc->dev.dv_flags & DVF_ACTIVE) || !(sc->flags & FREAD))
|
|
return;
|
|
|
|
if (MIDIBUF_ISFULL(mb))
|
|
return; /* discard data */
|
|
|
|
MIDIBUF_WRITE(mb, data);
|
|
|
|
midi_buf_wakeup(mb);
|
|
}
|
|
|
|
int
|
|
midiread(dev_t dev, struct uio *uio, int ioflag)
|
|
{
|
|
struct midi_softc *sc;
|
|
struct midi_buffer *mb;
|
|
size_t count;
|
|
int error;
|
|
|
|
sc = (struct midi_softc *)device_lookup(&midi_cd, minor(dev));
|
|
if (sc == NULL)
|
|
return ENXIO;
|
|
if (!(sc->flags & FREAD)) {
|
|
error = ENXIO;
|
|
goto done;
|
|
}
|
|
mb = &sc->inbuf;
|
|
|
|
/* if there is no data then sleep (unless IO_NDELAY flag is set) */
|
|
error = 0;
|
|
mtx_enter(&audio_lock);
|
|
while (MIDIBUF_ISEMPTY(mb)) {
|
|
if (ioflag & IO_NDELAY) {
|
|
error = EWOULDBLOCK;
|
|
goto done_mtx;
|
|
}
|
|
sc->inbuf.blocking = 1;
|
|
error = msleep_nsec(&sc->inbuf.blocking, &audio_lock,
|
|
PWAIT | PCATCH, "mid_rd", INFSLP);
|
|
if (!(sc->dev.dv_flags & DVF_ACTIVE))
|
|
error = EIO;
|
|
if (error)
|
|
goto done_mtx;
|
|
}
|
|
|
|
/* at this stage, there is at least 1 byte */
|
|
|
|
while (uio->uio_resid > 0 && mb->used > 0) {
|
|
count = MIDIBUF_SIZE - mb->start;
|
|
if (count > mb->used)
|
|
count = mb->used;
|
|
if (count > uio->uio_resid)
|
|
count = uio->uio_resid;
|
|
mtx_leave(&audio_lock);
|
|
error = uiomove(mb->data + mb->start, count, uio);
|
|
if (error)
|
|
goto done;
|
|
mtx_enter(&audio_lock);
|
|
MIDIBUF_REMOVE(mb, count);
|
|
}
|
|
|
|
done_mtx:
|
|
mtx_leave(&audio_lock);
|
|
done:
|
|
device_unref(&sc->dev);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
midi_ointr(void *addr)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)addr;
|
|
struct midi_buffer *mb;
|
|
|
|
MUTEX_ASSERT_LOCKED(&audio_lock);
|
|
if (!(sc->dev.dv_flags & DVF_ACTIVE) || !(sc->flags & FWRITE))
|
|
return;
|
|
|
|
mb = &sc->outbuf;
|
|
if (mb->used > 0) {
|
|
#ifdef MIDI_DEBUG
|
|
if (!sc->isbusy) {
|
|
printf("midi_ointr: output must be busy\n");
|
|
}
|
|
#endif
|
|
midi_out_do(sc);
|
|
} else if (sc->isbusy)
|
|
midi_out_stop(sc);
|
|
}
|
|
|
|
void
|
|
midi_timeout(void *addr)
|
|
{
|
|
mtx_enter(&audio_lock);
|
|
midi_ointr(addr);
|
|
mtx_leave(&audio_lock);
|
|
}
|
|
|
|
void
|
|
midi_out_start(struct midi_softc *sc)
|
|
{
|
|
if (!sc->isbusy) {
|
|
sc->isbusy = 1;
|
|
midi_out_do(sc);
|
|
}
|
|
}
|
|
|
|
void
|
|
midi_out_stop(struct midi_softc *sc)
|
|
{
|
|
sc->isbusy = 0;
|
|
midi_buf_wakeup(&sc->outbuf);
|
|
}
|
|
|
|
void
|
|
midi_out_do(struct midi_softc *sc)
|
|
{
|
|
struct midi_buffer *mb = &sc->outbuf;
|
|
|
|
while (mb->used > 0) {
|
|
if (!sc->hw_if->output(sc->hw_hdl, mb->data[mb->start]))
|
|
break;
|
|
MIDIBUF_REMOVE(mb, 1);
|
|
if (MIDIBUF_ISEMPTY(mb)) {
|
|
if (sc->hw_if->flush != NULL)
|
|
sc->hw_if->flush(sc->hw_hdl);
|
|
midi_out_stop(sc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!(sc->props & MIDI_PROP_OUT_INTR)) {
|
|
if (MIDIBUF_ISEMPTY(mb))
|
|
midi_out_stop(sc);
|
|
else
|
|
timeout_add(&sc->timeo, 1);
|
|
}
|
|
}
|
|
|
|
int
|
|
midiwrite(dev_t dev, struct uio *uio, int ioflag)
|
|
{
|
|
struct midi_softc *sc;
|
|
struct midi_buffer *mb;
|
|
size_t count;
|
|
int error;
|
|
|
|
sc = (struct midi_softc *)device_lookup(&midi_cd, minor(dev));
|
|
if (sc == NULL)
|
|
return ENXIO;
|
|
if (!(sc->flags & FWRITE)) {
|
|
error = ENXIO;
|
|
goto done;
|
|
}
|
|
mb = &sc->outbuf;
|
|
|
|
/*
|
|
* If IO_NDELAY flag is set then check if there is enough room
|
|
* in the buffer to store at least one byte. If not then dont
|
|
* start the write process.
|
|
*/
|
|
error = 0;
|
|
mtx_enter(&audio_lock);
|
|
if ((ioflag & IO_NDELAY) && MIDIBUF_ISFULL(mb) && (uio->uio_resid > 0)) {
|
|
error = EWOULDBLOCK;
|
|
goto done_mtx;
|
|
}
|
|
|
|
while (uio->uio_resid > 0) {
|
|
while (MIDIBUF_ISFULL(mb)) {
|
|
if (ioflag & IO_NDELAY) {
|
|
/*
|
|
* At this stage at least one byte is already
|
|
* moved so we do not return EWOULDBLOCK
|
|
*/
|
|
goto done_mtx;
|
|
}
|
|
sc->outbuf.blocking = 1;
|
|
error = msleep_nsec(&sc->outbuf.blocking, &audio_lock,
|
|
PWAIT | PCATCH, "mid_wr", INFSLP);
|
|
if (!(sc->dev.dv_flags & DVF_ACTIVE))
|
|
error = EIO;
|
|
if (error)
|
|
goto done_mtx;
|
|
}
|
|
|
|
count = MIDIBUF_SIZE - MIDIBUF_END(mb);
|
|
if (count > MIDIBUF_AVAIL(mb))
|
|
count = MIDIBUF_AVAIL(mb);
|
|
if (count > uio->uio_resid)
|
|
count = uio->uio_resid;
|
|
mtx_leave(&audio_lock);
|
|
error = uiomove(mb->data + MIDIBUF_END(mb), count, uio);
|
|
if (error)
|
|
goto done;
|
|
mtx_enter(&audio_lock);
|
|
mb->used += count;
|
|
midi_out_start(sc);
|
|
}
|
|
|
|
done_mtx:
|
|
mtx_leave(&audio_lock);
|
|
done:
|
|
device_unref(&sc->dev);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
midikqfilter(dev_t dev, struct knote *kn)
|
|
{
|
|
struct midi_softc *sc;
|
|
struct klist *klist;
|
|
int error;
|
|
|
|
sc = (struct midi_softc *)device_lookup(&midi_cd, minor(dev));
|
|
if (sc == NULL)
|
|
return ENXIO;
|
|
error = 0;
|
|
switch (kn->kn_filter) {
|
|
case EVFILT_READ:
|
|
klist = &sc->inbuf.klist;
|
|
kn->kn_fop = &midiread_filtops;
|
|
break;
|
|
case EVFILT_WRITE:
|
|
klist = &sc->outbuf.klist;
|
|
kn->kn_fop = &midiwrite_filtops;
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
goto done;
|
|
}
|
|
kn->kn_hook = (void *)sc;
|
|
|
|
klist_insert(klist, kn);
|
|
done:
|
|
device_unref(&sc->dev);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
filt_midirdetach(struct knote *kn)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)kn->kn_hook;
|
|
|
|
klist_remove(&sc->inbuf.klist, kn);
|
|
}
|
|
|
|
int
|
|
filt_midiread(struct knote *kn, long hint)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)kn->kn_hook;
|
|
|
|
return (!MIDIBUF_ISEMPTY(&sc->inbuf));
|
|
}
|
|
|
|
void
|
|
filt_midiwdetach(struct knote *kn)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)kn->kn_hook;
|
|
|
|
klist_remove(&sc->outbuf.klist, kn);
|
|
}
|
|
|
|
int
|
|
filt_midiwrite(struct knote *kn, long hint)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)kn->kn_hook;
|
|
|
|
return (!MIDIBUF_ISFULL(&sc->outbuf));
|
|
}
|
|
|
|
int
|
|
filt_midimodify(struct kevent *kev, struct knote *kn)
|
|
{
|
|
int active;
|
|
|
|
mtx_enter(&audio_lock);
|
|
active = knote_modify(kev, kn);
|
|
mtx_leave(&audio_lock);
|
|
|
|
return active;
|
|
}
|
|
|
|
int
|
|
filt_midiprocess(struct knote *kn, struct kevent *kev)
|
|
{
|
|
int active;
|
|
|
|
mtx_enter(&audio_lock);
|
|
active = knote_process(kn, kev);
|
|
mtx_leave(&audio_lock);
|
|
|
|
return active;
|
|
}
|
|
|
|
int
|
|
midiioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
|
|
{
|
|
struct midi_softc *sc;
|
|
int error;
|
|
|
|
sc = (struct midi_softc *)device_lookup(&midi_cd, minor(dev));
|
|
if (sc == NULL)
|
|
return ENXIO;
|
|
error = 0;
|
|
switch(cmd) {
|
|
case FIONBIO:
|
|
/* All handled in the upper FS layer */
|
|
break;
|
|
default:
|
|
error = ENOTTY;
|
|
}
|
|
device_unref(&sc->dev);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
midiopen(dev_t dev, int flags, int mode, struct proc *p)
|
|
{
|
|
struct midi_softc *sc;
|
|
int error;
|
|
|
|
sc = (struct midi_softc *)device_lookup(&midi_cd, minor(dev));
|
|
if (sc == NULL)
|
|
return ENXIO;
|
|
error = 0;
|
|
if (sc->flags) {
|
|
error = EBUSY;
|
|
goto done;
|
|
}
|
|
MIDIBUF_INIT(&sc->inbuf);
|
|
MIDIBUF_INIT(&sc->outbuf);
|
|
sc->isbusy = 0;
|
|
sc->inbuf.blocking = sc->outbuf.blocking = 0;
|
|
sc->flags = flags;
|
|
error = sc->hw_if->open(sc->hw_hdl, flags, midi_iintr, midi_ointr, sc);
|
|
if (error)
|
|
sc->flags = 0;
|
|
done:
|
|
device_unref(&sc->dev);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
midiclose(dev_t dev, int fflag, int devtype, struct proc *p)
|
|
{
|
|
struct midi_softc *sc;
|
|
struct midi_buffer *mb;
|
|
int error;
|
|
|
|
sc = (struct midi_softc *)device_lookup(&midi_cd, minor(dev));
|
|
if (sc == NULL)
|
|
return ENXIO;
|
|
|
|
/* start draining output buffer */
|
|
error = 0;
|
|
mb = &sc->outbuf;
|
|
mtx_enter(&audio_lock);
|
|
if (!MIDIBUF_ISEMPTY(mb))
|
|
midi_out_start(sc);
|
|
while (sc->isbusy) {
|
|
sc->outbuf.blocking = 1;
|
|
error = msleep_nsec(&sc->outbuf.blocking, &audio_lock,
|
|
PWAIT, "mid_dr", SEC_TO_NSEC(5));
|
|
if (!(sc->dev.dv_flags & DVF_ACTIVE))
|
|
error = EIO;
|
|
if (error)
|
|
break;
|
|
}
|
|
mtx_leave(&audio_lock);
|
|
|
|
/*
|
|
* some hw_if->close() reset immediately the midi uart
|
|
* which flushes the internal buffer of the uart device,
|
|
* so we may lose some (important) data. To avoid this,
|
|
* sleep 20ms (around 64 bytes) to give the time to the
|
|
* uart to drain its internal buffers.
|
|
*/
|
|
tsleep_nsec(&sc->outbuf.blocking, PWAIT, "mid_cl", MSEC_TO_NSEC(20));
|
|
sc->hw_if->close(sc->hw_hdl);
|
|
sc->flags = 0;
|
|
device_unref(&sc->dev);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
midiprobe(struct device *parent, void *match, void *aux)
|
|
{
|
|
struct audio_attach_args *sa = aux;
|
|
|
|
return (sa != NULL && (sa->type == AUDIODEV_TYPE_MIDI) ? 1 : 0);
|
|
}
|
|
|
|
void
|
|
midiattach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct midi_info mi;
|
|
struct midi_softc *sc = (struct midi_softc *)self;
|
|
struct audio_attach_args *sa = (struct audio_attach_args *)aux;
|
|
const struct midi_hw_if *hwif = sa->hwif;
|
|
void *hdl = sa->hdl;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
if (hwif == 0 ||
|
|
hwif->open == 0 ||
|
|
hwif->close == 0 ||
|
|
hwif->output == 0 ||
|
|
hwif->getinfo == 0) {
|
|
printf("%s: missing method\n", DEVNAME(sc));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
klist_init_mutex(&sc->inbuf.klist, &audio_lock);
|
|
klist_init_mutex(&sc->outbuf.klist, &audio_lock);
|
|
|
|
sc->hw_if = hwif;
|
|
sc->hw_hdl = hdl;
|
|
sc->hw_if->getinfo(sc->hw_hdl, &mi);
|
|
sc->props = mi.props;
|
|
sc->flags = 0;
|
|
timeout_set(&sc->timeo, midi_timeout, sc);
|
|
printf(": <%s>\n", mi.name);
|
|
}
|
|
|
|
int
|
|
mididetach(struct device *self, int flags)
|
|
{
|
|
struct midi_softc *sc = (struct midi_softc *)self;
|
|
int maj, mn;
|
|
|
|
/* locate the major number */
|
|
for (maj = 0; maj < nchrdev; maj++) {
|
|
if (cdevsw[maj].d_open == midiopen) {
|
|
/* Nuke the vnodes for any open instances (calls close). */
|
|
mn = self->dv_unit;
|
|
vdevgone(maj, mn, mn, VCHR);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The close() method did nothing (device_lookup() returns
|
|
* NULL), so quickly halt transfers (normally parent is already
|
|
* gone, and code below is no-op), and wake-up user-land blocked
|
|
* in read/write/ioctl, which return EIO.
|
|
*/
|
|
if (sc->flags) {
|
|
KERNEL_ASSERT_LOCKED();
|
|
if (sc->flags & FREAD)
|
|
wakeup(&sc->inbuf.blocking);
|
|
if (sc->flags & FWRITE)
|
|
wakeup(&sc->outbuf.blocking);
|
|
sc->hw_if->close(sc->hw_hdl);
|
|
sc->flags = 0;
|
|
}
|
|
|
|
klist_invalidate(&sc->inbuf.klist);
|
|
klist_invalidate(&sc->outbuf.klist);
|
|
klist_free(&sc->inbuf.klist);
|
|
klist_free(&sc->outbuf.klist);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
midiprint(void *aux, const char *pnp)
|
|
{
|
|
if (pnp)
|
|
printf("midi at %s", pnp);
|
|
return (UNCONF);
|
|
}
|
|
|
|
struct device *
|
|
midi_attach_mi(const struct midi_hw_if *hwif, void *hdl, struct device *dev)
|
|
{
|
|
struct audio_attach_args arg;
|
|
|
|
arg.type = AUDIODEV_TYPE_MIDI;
|
|
arg.hwif = hwif;
|
|
arg.hdl = hdl;
|
|
return config_found(dev, &arg, midiprint);
|
|
}
|