[BACK]Return to midi.c CVS log [TXT][DIR] Up to [local] / src / sys / dev

File: [local] / src / sys / dev / midi.c (download)

Revision 1.57, Mon May 13 01:15:50 2024 UTC (4 weeks, 1 day ago) by jsg
Branch: MAIN
CVS Tags: HEAD
Changes since 1.56: +1 -2 lines

remove prototypes with no matching function
ok mpi@

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