/* $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 #include #include #include #include #include #include #include #include #include #include #include #include #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); }