Edit

IABSD.fr/src/sys/dev/midi.c

Branch :

  • Show log

    Commit

  • Author : guenther
    Date : 2024-12-30 02:46:00
    Hash : b9ae17a0
    Message : All the device and file type ioctl routines just ignore FIONBIO, so stop calling down into those layer from fcntl(F_SETFL) or ioctl(FIONBIO) and delete the "do nothing for this" stubs in all the *ioctl routines. ok dlg@

  • sys/dev/midi.c
  • /*	$OpenBSD: midi.c,v 1.58 2024/12/30 02:46:00 guenther 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) {
    	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);
    }