Edit

IABSD.fr/src/sys/dev/pv/viocon.c

Branch :

  • Show log

    Commit

  • Author : sf
    Date : 2025-01-16 10:33:27
    Hash : 35393bd4
    Message : constify struct virtio_feature_name Most are already static const. Adjust the rest, too.

  • sys/dev/pv/viocon.c
  • /*	$OpenBSD: viocon.c,v 1.17 2025/01/16 10:33:27 sf Exp $	*/
    
    /*
     * Copyright (c) 2013-2015 Stefan Fritsch <sf@sfritsch.de>
     *
     * 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/systm.h>
    #include <sys/malloc.h>
    #include <machine/bus.h>
    #include <sys/device.h>
    #include <sys/conf.h>
    #include <sys/tty.h>
    #include <dev/pv/virtioreg.h>
    #include <dev/pv/virtiovar.h>
    
    
    /* features */
    #define	VIRTIO_CONSOLE_F_SIZE		(1ULL<<0)
    #define	VIRTIO_CONSOLE_F_MULTIPORT	(1ULL<<1)
    #define	VIRTIO_CONSOLE_F_EMERG_WRITE 	(1ULL<<2)
    
    /* config space */
    #define VIRTIO_CONSOLE_COLS		0	/* 16 bits */
    #define VIRTIO_CONSOLE_ROWS		2	/* 16 bits */
    #define VIRTIO_CONSOLE_MAX_NR_PORTS	4	/* 32 bits */
    #define VIRTIO_CONSOLE_EMERG_WR		8	/* 32 bits */
    
    #define VIOCON_DEBUG	0
    
    #if VIOCON_DEBUG
    #define DPRINTF(x...) printf(x)
    #else
    #define DPRINTF(x...)
    #endif
    
    static const struct virtio_feature_name viocon_feature_names[] = {
    #if VIRTIO_DEBUG
    	{ VIRTIO_CONSOLE_F_SIZE,	"Size" },
    	{ VIRTIO_CONSOLE_F_MULTIPORT,	"MultiPort" },
    	{ VIRTIO_CONSOLE_F_EMERG_WRITE,	"EmergWrite" },
    #endif
    	{ 0, 				NULL },
    };
    
    struct virtio_console_control {
    	uint32_t id;	/* Port number */
    
    #define	VIRTIO_CONSOLE_DEVICE_READY	0
    #define	VIRTIO_CONSOLE_PORT_ADD		1
    #define	VIRTIO_CONSOLE_PORT_REMOVE	2
    #define	VIRTIO_CONSOLE_PORT_READY	3
    #define	VIRTIO_CONSOLE_CONSOLE_PORT	4
    #define	VIRTIO_CONSOLE_RESIZE		5
    #define	VIRTIO_CONSOLE_PORT_OPEN	6
    #define	VIRTIO_CONSOLE_PORT_NAME	7
    	uint16_t event;
    
    	uint16_t value;
    };
    
    struct virtio_console_control_resize {
    	/* yes, the order is different than in config space */
    	uint16_t rows;
    	uint16_t cols;
    };
    
    #define	BUFSIZE		128
    CTASSERT(BUFSIZE < TTHIWATMINSPACE);
    
    #define VIOCONUNIT(x)	(minor(x) >> 4)
    #define VIOCONPORT(x)	(minor(x) & 0x0f)
    
    struct viocon_port {
    	struct viocon_softc	*vp_sc;
    	struct virtqueue	*vp_rx;
    	struct virtqueue	*vp_tx;
    	void			*vp_si;
    	struct tty		*vp_tty;
    	const char 		*vp_name;
    	bus_dma_segment_t	 vp_dmaseg;
    	bus_dmamap_t		 vp_dmamap;
    #ifdef NOTYET
    	unsigned int		 vp_host_open:1;	/* XXX needs F_MULTIPORT */
    	unsigned int		 vp_guest_open:1;	/* XXX needs F_MULTIPORT */
    	unsigned int		 vp_is_console:1;	/* XXX needs F_MULTIPORT */
    #endif
    	unsigned int		 vp_iflow:1;		/* rx flow control */
    	uint16_t		 vp_rows;
    	uint16_t		 vp_cols;
    	u_char			*vp_rx_buf;
    	u_char			*vp_tx_buf;
    };
    
    struct viocon_softc {
    	struct device		 sc_dev;
    	struct virtio_softc	*sc_virtio;
    
    	struct virtqueue        *sc_c_vq_rx;
    	struct virtqueue        *sc_c_vq_tx;
    
    	unsigned int		 sc_max_ports;
    	struct viocon_port	**sc_ports;
    
    	bus_dmamap_t		 sc_dmamap;
    };
    
    int	viocon_match(struct device *, void *, void *);
    void	viocon_attach(struct device *, struct device *, void *);
    int	viocon_tx_intr(struct virtqueue *);
    int	viocon_tx_drain(struct viocon_port *, struct virtqueue *vq);
    int	viocon_rx_intr(struct virtqueue *);
    void	viocon_rx_soft(void *);
    void	viocon_rx_fill(struct viocon_port *);
    int	viocon_port_create(struct viocon_softc *, int);
    void	vioconstart(struct tty *);
    int	vioconhwiflow(struct tty *, int);
    int	vioconparam(struct tty *, struct termios *);
    int	vioconopen(dev_t, int, int, struct proc *);
    int	vioconclose(dev_t, int, int, struct proc *);
    int	vioconread(dev_t, struct uio *, int);
    int	vioconwrite(dev_t, struct uio *, int);
    int	vioconstop(struct tty *, int);
    int	vioconioctl(dev_t, u_long, caddr_t, int, struct proc *);
    struct tty	*viocontty(dev_t dev);
    
    const struct cfattach viocon_ca = {
    	sizeof(struct viocon_softc),
    	viocon_match,
    	viocon_attach,
    	NULL
    };
    
    struct cfdriver viocon_cd = {
    	NULL, "viocon", DV_TTY
    };
    
    static inline struct viocon_softc *
    dev2sc(dev_t dev)
    {
    	return viocon_cd.cd_devs[VIOCONUNIT(dev)];
    }
    
    static inline struct viocon_port *
    dev2port(dev_t dev)
    {
    	return dev2sc(dev)->sc_ports[VIOCONPORT(dev)];
    }
    
    int
    viocon_match(struct device *parent, void *match, void *aux)
    {
    	struct virtio_attach_args *va = aux;
    	if (va->va_devid == PCI_PRODUCT_VIRTIO_CONSOLE)
    		return 1;
    	return 0;
    }
    
    void
    viocon_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct viocon_softc *sc = (struct viocon_softc *)self;
    	struct virtio_softc *vsc = (struct virtio_softc *)parent;
    	int maxports = 1;
    
    	if (vsc->sc_child)
    		panic("already attached to something else");
    	vsc->sc_child = self;
    	vsc->sc_ipl = IPL_TTY;
    	sc->sc_virtio = vsc;
    	sc->sc_max_ports = maxports;
    
    	vsc->sc_vqs = malloc(2 * (maxports + 1) * sizeof(struct virtqueue), M_DEVBUF,
    	    M_WAITOK|M_CANFAIL|M_ZERO);
    	sc->sc_ports = malloc(maxports * sizeof(sc->sc_ports[0]), M_DEVBUF,
    	    M_WAITOK|M_CANFAIL|M_ZERO);
    	if (vsc->sc_vqs == NULL || sc->sc_ports == NULL) {
    		printf("\n%s: Cannot allocate memory\n", __func__);
    		goto err;
    	}
    
    	vsc->sc_driver_features = VIRTIO_CONSOLE_F_SIZE;
    	if (virtio_negotiate_features(vsc, viocon_feature_names) != 0)
    		goto err;
    
    	printf("\n");
    	DPRINTF("%s: softc: %p\n", __func__, sc);
    	if (viocon_port_create(sc, 0) != 0) {
    		printf("\n%s: viocon_port_create failed\n", __func__);
    		goto err;
    	}
    	if (virtio_attach_finish(vsc, va) != 0)
    		goto err;
    	viocon_rx_fill(sc->sc_ports[0]);
    	return;
    
    err:
    	vsc->sc_child = VIRTIO_CHILD_ERROR;
    	free(vsc->sc_vqs, M_DEVBUF, 2 * (maxports + 1) * sizeof(struct virtqueue));
    	free(sc->sc_ports, M_DEVBUF, maxports * sizeof(sc->sc_ports[0]));
    }
    
    int
    viocon_port_create(struct viocon_softc *sc, int portidx)
    {
    	struct virtio_softc *vsc = sc->sc_virtio;
    	int rxidx, txidx, allocsize, nsegs;
    	char name[6];
    	struct viocon_port *vp;
    	caddr_t kva;
    	struct tty *tp;
    
    	vp = malloc(sizeof(*vp), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO);
    	if (vp == NULL)
    		return ENOMEM;
    	sc->sc_ports[portidx] = vp;
    	vp->vp_sc = sc;
    	DPRINTF("%s: vp: %p\n", __func__, vp);
    
    	if (portidx == 0)
    		rxidx = 0;
    	else
    		rxidx = 2 * (portidx + 1);
    	txidx = rxidx + 1;
    
    	snprintf(name, sizeof(name), "p%drx", portidx);
    	if (virtio_alloc_vq(vsc, &vsc->sc_vqs[rxidx], rxidx, 1, name) != 0) {
    		printf("\nCan't alloc %s virtqueue\n", name);
    		goto err;
    	}
    	vp->vp_rx = &vsc->sc_vqs[rxidx];
    	vp->vp_rx->vq_done = viocon_rx_intr;
    	vp->vp_si = softintr_establish(IPL_TTY, viocon_rx_soft, vp);
    	DPRINTF("%s: rx: %p\n", __func__, vp->vp_rx);
    
    	snprintf(name, sizeof(name), "p%dtx", portidx);
    	if (virtio_alloc_vq(vsc, &vsc->sc_vqs[txidx], txidx, 1, name) != 0) {
    		printf("\nCan't alloc %s virtqueue\n", name);
    		goto err;
    	}
    	vp->vp_tx = &vsc->sc_vqs[txidx];
    	vp->vp_tx->vq_done = viocon_tx_intr;
    	DPRINTF("%s: tx: %p\n", __func__, vp->vp_tx);
    
    	vsc->sc_nvqs += 2;
    
    	allocsize = (vp->vp_rx->vq_num + vp->vp_tx->vq_num) * BUFSIZE;
    
    	if (bus_dmamap_create(vsc->sc_dmat, allocsize, 1, allocsize, 0,
    	    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &vp->vp_dmamap) != 0)
    		goto err;
    	if (bus_dmamem_alloc(vsc->sc_dmat, allocsize, 8, 0, &vp->vp_dmaseg,
    	    1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0)
    		goto err;
    	if (bus_dmamem_map(vsc->sc_dmat, &vp->vp_dmaseg, nsegs,
    	    allocsize, &kva, BUS_DMA_NOWAIT) != 0)
    		goto err;
    	if (bus_dmamap_load(vsc->sc_dmat, vp->vp_dmamap, kva,
    	    allocsize, NULL, BUS_DMA_NOWAIT) != 0)
    		goto err;
    	vp->vp_rx_buf = (unsigned char *)kva;
    	/*
    	 * XXX use only a small circular tx buffer instead of many BUFSIZE buffers?
    	 */
    	vp->vp_tx_buf = vp->vp_rx_buf + vp->vp_rx->vq_num * BUFSIZE;
    
    	if (virtio_has_feature(vsc, VIRTIO_CONSOLE_F_SIZE)) {
    		vp->vp_cols = virtio_read_device_config_2(vsc,
    		    VIRTIO_CONSOLE_COLS);
    		vp->vp_rows = virtio_read_device_config_2(vsc,
    		    VIRTIO_CONSOLE_ROWS);
    	}
    
    	tp = ttymalloc(1000000);
    	tp->t_oproc = vioconstart;
    	tp->t_param = vioconparam;
    	tp->t_hwiflow = vioconhwiflow;
    	tp->t_dev = (sc->sc_dev.dv_unit << 4) | portidx;
    	vp->vp_tty = tp;
    	DPRINTF("%s: tty: %p\n", __func__, tp);
    
    	virtio_start_vq_intr(vsc, vp->vp_rx);
    	virtio_start_vq_intr(vsc, vp->vp_tx);
    
    	return 0;
    err:
    	panic("%s failed", __func__);
    	return -1;
    }
    
    int
    viocon_tx_drain(struct viocon_port *vp, struct virtqueue *vq)
    {
    	struct virtio_softc *vsc = vq->vq_owner;
    	int ndone = 0, len, slot;
    
    	splassert(IPL_TTY);
    	while (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
    		bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap,
    		    vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, BUFSIZE,
    		    BUS_DMASYNC_POSTREAD);
    		virtio_dequeue_commit(vq, slot);
    		ndone++;
    	}
    	return ndone;
    }
    
    int
    viocon_tx_intr(struct virtqueue *vq)
    {
    	struct virtio_softc *vsc = vq->vq_owner;
    	struct viocon_softc *sc = (struct viocon_softc *)vsc->sc_child;
    	int ndone = 0;
    	int portidx = (vq->vq_index - 1) / 2;
    	struct viocon_port *vp = sc->sc_ports[portidx];
    	struct tty *tp = vp->vp_tty;
    
    	splassert(IPL_TTY);
    	ndone = viocon_tx_drain(vp, vq);
    	if (ndone && ISSET(tp->t_state, TS_BUSY)) {
    		CLR(tp->t_state, TS_BUSY);
    		linesw[tp->t_line].l_start(tp);
    	}
    
    	return 1;
    }
    
    void
    viocon_rx_fill(struct viocon_port *vp)
    {
    	struct virtqueue *vq = vp->vp_rx;
    	struct virtio_softc *vsc = vp->vp_sc->sc_virtio;
    	int r, slot, ndone = 0;
    
    	while ((r = virtio_enqueue_prep(vq, &slot)) == 0) {
    		if (virtio_enqueue_reserve(vq, slot, 1) != 0)
    			break;
    		bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap, slot * BUFSIZE,
    		    BUFSIZE, BUS_DMASYNC_PREREAD);
    		virtio_enqueue_p(vq, slot, vp->vp_dmamap, slot * BUFSIZE,
    		    BUFSIZE, 0);
    		virtio_enqueue_commit(vsc, vq, slot, 0);
    		ndone++;
    	}
    	KASSERT(r == 0 || r == EAGAIN);
    	if (ndone > 0)
    		virtio_notify(vsc, vq);
    }
    
    int
    viocon_rx_intr(struct virtqueue *vq)
    {
    	struct virtio_softc *vsc = vq->vq_owner;
    	struct viocon_softc *sc = (struct viocon_softc *)vsc->sc_child;
    	int portidx = (vq->vq_index - 1) / 2;
    	struct viocon_port *vp = sc->sc_ports[portidx];
    
    	softintr_schedule(vp->vp_si);
    	return 1;
    }
    
    void
    viocon_rx_soft(void *arg)
    {
    	struct viocon_port *vp = arg;
    	struct virtqueue *vq = vp->vp_rx;
    	struct virtio_softc *vsc = vq->vq_owner;
    	struct tty *tp = vp->vp_tty;
    	int slot, len, i;
    	u_char *p;
    
    	while (!vp->vp_iflow && virtio_dequeue(vsc, vq, &slot, &len) == 0) {
    		bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap,
    		    slot * BUFSIZE, BUFSIZE, BUS_DMASYNC_POSTREAD);
    		p = vp->vp_rx_buf + slot * BUFSIZE;
    		for (i = 0; i < len; i++)
    			(*linesw[tp->t_line].l_rint)(*p++, tp);
    		virtio_dequeue_commit(vq, slot);
    	}
    
    	viocon_rx_fill(vp);
    
    	return;
    }
    
    void
    vioconstart(struct tty *tp)
    {
    	struct viocon_softc *sc = dev2sc(tp->t_dev);
    	struct virtio_softc *vsc;
    	struct viocon_port *vp = dev2port(tp->t_dev);
    	struct virtqueue *vq;
    	u_char *buf;
    	int s, cnt, slot, ret, ndone;
    
    	vsc = sc->sc_virtio;
    	vq = vp->vp_tx;
    
    	s = spltty();
    
    	ndone = viocon_tx_drain(vp, vq);
    	if (ISSET(tp->t_state, TS_BUSY)) {
    		if (ndone > 0)
    			CLR(tp->t_state, TS_BUSY);
    		else
    			goto out;
    	}
    	if (ISSET(tp->t_state, TS_TIMEOUT | TS_TTSTOP))
    		goto out;
    
    	if (tp->t_outq.c_cc == 0)
    		goto out;
    	ndone = 0;
    
    	while (tp->t_outq.c_cc > 0) {
    		ret = virtio_enqueue_prep(vq, &slot);
    		if (ret == EAGAIN)
    			break;
    		KASSERT(ret == 0);
    		ret = virtio_enqueue_reserve(vq, slot, 1);
    		KASSERT(ret == 0);
    		buf = vp->vp_tx_buf + slot * BUFSIZE;
    		cnt = q_to_b(&tp->t_outq, buf, BUFSIZE);
    		bus_dmamap_sync(vsc->sc_dmat, vp->vp_dmamap,
    		    vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt,
    		    BUS_DMASYNC_PREWRITE);
    		virtio_enqueue_p(vq, slot, vp->vp_dmamap,
    		    vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt, 1);
    		virtio_enqueue_commit(vsc, vq, slot, 0);
    		ndone++;
    	}
    	if (ret == EAGAIN)
    		SET(tp->t_state, TS_BUSY);
    	if (ndone > 0)
    		virtio_notify(vsc, vq);
    	ttwakeupwr(tp);
    out:
    	splx(s);
    }
    
    int
    vioconhwiflow(struct tty *tp, int stop)
    {
    	struct viocon_port *vp = dev2port(tp->t_dev);
    	int s;
    
    	s = spltty();
    	vp->vp_iflow = stop;
    	if (stop) {
    		virtio_stop_vq_intr(vp->vp_sc->sc_virtio, vp->vp_rx);
    	} else {
    		virtio_start_vq_intr(vp->vp_sc->sc_virtio, vp->vp_rx);
    		virtio_check_vq(vp->vp_sc->sc_virtio, vp->vp_rx);
    	}
    	splx(s);
    	return 1;
    }
    
    int
    vioconparam(struct tty *tp, struct termios *t)
    {
    	tp->t_ispeed = t->c_ispeed;
    	tp->t_ospeed = t->c_ospeed;
    	tp->t_cflag = t->c_cflag;
    
    	vioconstart(tp);
    	return 0;
    }
    
    int
    vioconopen(dev_t dev, int flag, int mode, struct proc *p)
    {
    	int unit = VIOCONUNIT(dev);
    	int port = VIOCONPORT(dev);
    	struct viocon_softc *sc;
    	struct viocon_port *vp;
    	struct tty *tp;
    	int s, error;
    
    	if (unit >= viocon_cd.cd_ndevs)
    		return (ENXIO);
    	sc = viocon_cd.cd_devs[unit];
    	if (sc == NULL)
    		return (ENXIO);
    	if (ISSET(sc->sc_dev.dv_flags, DVF_ACTIVE) == 0)
    		return (ENXIO);
    
    	s = spltty();
    	if (port >= sc->sc_max_ports) {
    		splx(s);
    		return (ENXIO);
    	}
    	vp = sc->sc_ports[port];
    	tp = vp->vp_tty;
    #ifdef NOTYET
    	vp->vp_guest_open = 1;
    #endif
    	splx(s);
    
    	if (!ISSET(tp->t_state, TS_ISOPEN)) {
    		SET(tp->t_state, TS_WOPEN);
    		ttychars(tp);
    		tp->t_ispeed = 1000000;
    		tp->t_ospeed = 1000000;
    		tp->t_cflag = TTYDEF_CFLAG|CLOCAL|CRTSCTS;
    		tp->t_iflag = TTYDEF_IFLAG;
    		tp->t_oflag = TTYDEF_OFLAG;
    		tp->t_lflag = TTYDEF_LFLAG;
    		if (vp->vp_cols != 0) {
    			tp->t_winsize.ws_col = vp->vp_cols;
    			tp->t_winsize.ws_row = vp->vp_rows;
    		}
    
    		s = spltty();
    		vioconparam(tp, &tp->t_termios);
    		ttsetwater(tp);
    		splx(s);
    	}
    	else if (ISSET(tp->t_state, TS_XCLUDE) && suser(p) != 0) {
    		return (EBUSY);
    	}
    
    	error = linesw[tp->t_line].l_open(dev, tp, p);
    	return error;
    }
    
    int
    vioconclose(dev_t dev, int flag, int mode, struct proc *p)
    {
    	struct viocon_port *vp = dev2port(dev);
    	struct tty *tp = vp->vp_tty;
    	int s;
    
    	if (!ISSET(tp->t_state, TS_ISOPEN))
    		return 0;
    
    	linesw[tp->t_line].l_close(tp, flag, p);
    	s = spltty();
    #ifdef NOTYET
    	vp->vp_guest_open = 0;
    #endif
    	CLR(tp->t_state, TS_BUSY | TS_FLUSH);
    	ttyclose(tp);
    	splx(s);
    
    	return 0;
    }
    
    int
    vioconread(dev_t dev, struct uio *uio, int flag)
    {
    	struct viocon_port *vp = dev2port(dev);
    	struct tty *tp = vp->vp_tty;
    
    	return linesw[tp->t_line].l_read(tp, uio, flag);
    }
    
    int
    vioconwrite(dev_t dev, struct uio *uio, int flag)
    {
    	struct viocon_port *vp = dev2port(dev);
    	struct tty *tp = vp->vp_tty;
    
    	return linesw[tp->t_line].l_write(tp, uio, flag);
    }
    
    struct tty *
    viocontty(dev_t dev)
    {
    	struct viocon_port *vp = dev2port(dev);
    
    	return vp->vp_tty;
    }
    
    int
    vioconstop(struct tty *tp, int flag)
    {
    	int s;
    
    	s = spltty();
    	if (ISSET(tp->t_state, TS_BUSY))
    		if (!ISSET(tp->t_state, TS_TTSTOP))
    			SET(tp->t_state, TS_FLUSH);
    	splx(s);
    	return 0;
    }
    
    int
    vioconioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
    {
    	struct viocon_port *vp = dev2port(dev);
    	struct tty *tp;
    	int error1, error2;
    
    	tp = vp->vp_tty;
    
    	error1 = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
    	if (error1 >= 0)
    		return error1;
    	error2 = ttioctl(tp, cmd, data, flag, p);
    	if (error2 >= 0)
    		return error2;
    	return ENOTTY;
    }