The attached patch adds viocon(4) to NetBSD.  Lightly tested under
qemu.  Review welcome!  I'm not very familiar with tty drivers so I'm
hoping someone who is will take a look and see if I did anything the
wrong way in copying from OpenBSD.

As in OpenBSD, it uses new device nodes /dev/ttyVI[0-9][0-9], where
the first digit is the unit number and the second digit is the port
number.  The port number is currently always zero but in principle it
might change.  These are now unconditionally included in the MI
MAKEDEV.tmpl, and ttyVI00 through ttyVI30 are created by default.
>From 50c5c4d195f82ad7b665a7e0adfa922a448e9cfb Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <[email protected]>
Date: Tue, 9 Aug 2022 14:09:38 +0000
Subject: [PATCH] viocon(4): New virtio tty driver imported from OpenBSD.

viocon* at virtio?

Tested under qemu with:

qemu-system-aarch64 ... \
  -device virtio-serial \
  -chardev socket,path=/tmp/ttyVI00,server=on,wait=off,id=ttyVI00 \
  -device virtconsole,chardev=ttyVI00,name=org.NetBSD.dev.ttyVI00 \
  ...
---
 distrib/sets/lists/man/mi   |   3 +
 etc/MAKEDEV.tmpl            |   9 +
 share/man/man4/Makefile     |   2 +-
 share/man/man4/viocon.4     |  66 ++++
 sys/conf/majors             |   1 +
 sys/dev/virtio/files.virtio |   4 +
 sys/dev/virtio/viocon.c     | 632 ++++++++++++++++++++++++++++++++++++
 7 files changed, 716 insertions(+), 1 deletion(-)
 create mode 100644 share/man/man4/viocon.4
 create mode 100644 sys/dev/virtio/viocon.c

diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi
index 3b354d653ca0..c56263b6dffc 100644
--- a/distrib/sets/lists/man/mi
+++ b/distrib/sets/lists/man/mi
@@ -2047,6 +2047,7 @@
 ./usr/share/man/cat4/video.0                   man-sys-catman          .cat
 ./usr/share/man/cat4/vinum.0                   man-obsolete            obsolete
 ./usr/share/man/cat4/vio9p.0                   man-sys-catman          .cat
+./usr/share/man/cat4/viocon.0                  man-sys-catman          .cat
 ./usr/share/man/cat4/vioif.0                   man-sys-catman          .cat
 ./usr/share/man/cat4/viomb.0                   man-sys-catman          .cat
 ./usr/share/man/cat4/viornd.0                  man-sys-catman          .cat
@@ -5233,6 +5234,7 @@
 ./usr/share/man/html4/viaide.html              man-sys-htmlman         html
 ./usr/share/man/html4/video.html               man-sys-htmlman         html
 ./usr/share/man/html4/vio9p.html               man-sys-htmlman         html
+./usr/share/man/html4/viocon.html              man-sys-htmlman         html
 ./usr/share/man/html4/vioif.html               man-sys-htmlman         html
 ./usr/share/man/html4/viomb.html               man-sys-htmlman         html
 ./usr/share/man/html4/viornd.html              man-sys-htmlman         html
@@ -8351,6 +8353,7 @@
 ./usr/share/man/man4/video.4                   man-sys-man             .man
 ./usr/share/man/man4/vinum.4                   man-obsolete            obsolete
 ./usr/share/man/man4/vio9p.4                   man-sys-man             .man
+./usr/share/man/man4/viocon.4                  man-sys-man             .man
 ./usr/share/man/man4/vioif.4                   man-sys-man             .man
 ./usr/share/man/man4/viomb.4                   man-sys-man             .man
 ./usr/share/man/man4/viornd.4                  man-sys-man             .man
diff --git a/etc/MAKEDEV.tmpl b/etc/MAKEDEV.tmpl
index 51fb821f244b..6576679045a8 100644
--- a/etc/MAKEDEV.tmpl
+++ b/etc/MAKEDEV.tmpl
@@ -148,6 +148,7 @@
 #      dmz*    UNIBUS DMZ32 (vax)
 #      dl*     UNIBUS DL11 (vax)
 #      xencons Xen virtual console
+#      ttyVI?? viocon(4)
 #
 # Terminal multiplexors:
 #      dc*     4 channel serial interface (keyboard, mouse, modem, printer)
@@ -849,6 +850,7 @@ all)
        makedev qemufwcfg
        makedev sht3xtemp0
        makedev scmd0
+       makedev ttyVI00 ttyVI10 ttyVI20 ttyVI30
        makedev local # do this last
        ;;
 
@@ -2272,6 +2274,13 @@ scmd[0-9]*)
        mkdev scmd$unit c %scmd_chr% $unit 666
        ;;
 
+ttyVI[0-9][0-9])
+       port=${i#ttyVI?}
+       devunit=${i%$port}
+       unit=${devunit#ttyVI}
+       mkdev ttyVI$unit$port c %viocon_chr% $((16*$unit + $port))
+       ;;
+
 midevend)
 %MI_DEVICES_END%
 local)
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index d98073dccd63..897dca9ebd64 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -68,7 +68,7 @@ MAN=  aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
        uark.4 ubsec.4 udp.4 uep.4 ug.4 uha.4 uk.4 ukphy.4 umb.4 \
        unix.4 userconf.4 \
        vald.4 valz.4 veriexec.4 vga.4 vge.4 viaide.4 video.4 \
-       vio9p.4 vioif.4 viomb.4 viornd.4 vioscsi.4 virt.4 virtio.4 \
+       vio9p.4 viocon.4 vioif.4 viomb.4 viornd.4 vioscsi.4 virt.4 virtio.4 \
        vether.4 vlan.4 vmmon.4 vmnet.4 vmt.4 vmx.4 vnd.4 voodoofb.4 vr.4 \
        vte.4 \
        wapbl.4 wb.4 wbsio.4 wd.4 wdc.4 wg.4 wi.4 wm.4 wpi.4 \
diff --git a/share/man/man4/viocon.4 b/share/man/man4/viocon.4
new file mode 100644
index 000000000000..e9946d0e124b
--- /dev/null
+++ b/share/man/man4/viocon.4
@@ -0,0 +1,66 @@
+.\"    $NetBSD$
+.\"     $OpenBSD: viocon.4,v 1.3 2017/06/21 08:21:14 akfaew Exp $
+.\"
+.\" Copyright (c) 2015 Stefan Fritsch <[email protected]>
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate: June 21 2017 $
+.Dt VIOCON 4
+.Os
+.Sh NAME
+.Nm viocon
+.Nd VirtIO console device
+.Sh SYNOPSIS
+.Cd "viocon* at virtio?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the
+.Xr virtio 4
+console interface provided by KVM, QEMU, and others.
+.Pp
+It provides serial ports that are attached as ttys.
+.Sh FILES
+.Bl -tag -width Pa -compact
+.It Pa /dev/ttyVI00
+.It Pa /dev/ttyVI10
+.It Pa /dev/ttyVI20
+.It Pa /dev/ttyVI30
+.It Pa /dev/ttyVI40
+.El
+.Sh SEE ALSO
+.Xr intro 4 ,
+.Xr tty 4 ,
+.Xr virtio 4
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Ox 5.9 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written for
+.Ox
+by
+.An Stefan Fritsch Aq Mt [email protected] .
+It was ported to
+.Nx 10.0 .
+.Sh BUGS
+Use as a kernel console for
+.Nx
+is not yet supported.
+.Pp
+The multiport feature is not yet supported.
diff --git a/sys/conf/majors b/sys/conf/majors
index 6498bc46a246..2aaaf847d117 100644
--- a/sys/conf/majors
+++ b/sys/conf/majors
@@ -95,3 +95,4 @@ device-major smbios    char 360            smbios
 device-major efi       char 361            efi
 device-major sht3xtemp char 362                   sht3xtemp
 device-major scmd      char 363                   scmd
+device-major viocon    char 364                   viocon
diff --git a/sys/dev/virtio/files.virtio b/sys/dev/virtio/files.virtio
index 4477ce08634f..88490ccb47b0 100644
--- a/sys/dev/virtio/files.virtio
+++ b/sys/dev/virtio/files.virtio
@@ -4,3 +4,7 @@
 include "dev/pci/files.virtio"
 
 file   dev/virtio/virtio_mmio.c        virtio_mmio
+
+device viocon
+attach viocon at virtio
+file   dev/virtio/viocon.c             viocon
diff --git a/sys/dev/virtio/viocon.c b/sys/dev/virtio/viocon.c
new file mode 100644
index 000000000000..3520395aadd5
--- /dev/null
+++ b/sys/dev/virtio/viocon.c
@@ -0,0 +1,632 @@
+/*     $NetSBD$        */
+/*     $OpenBSD: viocon.c,v 1.8 2021/11/05 11:38:29 mpi Exp $  */
+
+/*
+ * Copyright (c) 2013-2015 Stefan Fritsch <[email protected]>
+ *
+ * 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/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/device.h>
+#include <sys/kauth.h>
+#include <sys/kernel.h>
+#include <sys/kmem.h>
+#include <sys/lwp.h>
+#include <sys/systm.h>
+#include <sys/tty.h>
+
+#include <dev/pci/virtioreg.h>
+#include <dev/pci/virtiovar.h>
+
+#include "ioconf.h"
+
+/* OpenBSD compat shims */
+#define        ttymalloc(speed)        tty_alloc()
+#define        splassert(ipl)          __nothing
+#define        virtio_notify(vsc, vq)  virtio_enqueue_commit(vsc, vq, -1, true)
+#define        ttwakeupwr(tp)          __nothing
+
+/* 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
+
+#define        VIRTIO_CONSOLE_FLAG_BITS                                        
      \
+       VIRTIO_COMMON_FLAG_BITS                                               \
+       "b\x00" "SIZE\0"                                                      \
+       "b\x01" "MULTIPORT\0"                                                 \
+       "b\x02" "EMERG_WRITE\0"
+
+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
+
+#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_vqs;
+
+       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 *, struct cfdata *, 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 lwp *);
+int    vioconclose(dev_t, int, int, struct lwp *);
+int    vioconread(dev_t, struct uio *, int);
+int    vioconwrite(dev_t, struct uio *, int);
+void   vioconstop(struct tty *, int);
+int    vioconioctl(dev_t, u_long, void *, int, struct lwp *);
+struct tty     *viocontty(dev_t dev);
+
+CFATTACH_DECL_NEW(viocon, sizeof(struct viocon_softc),
+    viocon_match, viocon_attach, /*detach*/NULL, /*activate*/NULL);
+
+const struct cdevsw viocon_cdevsw = {
+       .d_open = vioconopen,
+       .d_close = vioconclose,
+       .d_read = vioconread,
+       .d_write = vioconwrite,
+       .d_ioctl = vioconioctl,
+       .d_stop = vioconstop,
+       .d_tty = viocontty,
+       .d_poll = nopoll,       /* XXX */
+       .d_mmap = nommap,
+       .d_kqfilter = ttykqfilter,
+       .d_discard = nodiscard,
+       .d_flag = D_TTY,
+};
+
+static inline struct viocon_softc *
+dev2sc(dev_t dev)
+{
+       return device_lookup_private(&viocon_cd, VIOCONUNIT(dev));
+}
+
+static inline struct viocon_port *
+dev2port(dev_t dev)
+{
+       return dev2sc(dev)->sc_ports[VIOCONPORT(dev)];
+}
+
+int viocon_match(struct device *parent, struct cfdata *match, void *aux)
+{
+       struct virtio_attach_args *va = aux;
+       if (va->sc_childdevid == VIRTIO_DEVICE_ID_CONSOLE)
+               return 1;
+       return 0;
+}
+
+void
+viocon_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct viocon_softc *sc = device_private(self);
+       struct virtio_softc *vsc = device_private(parent);
+       int maxports = 1;
+
+       sc->sc_dev = self;
+       if (virtio_child(vsc) != NULL) {
+               aprint_error(": parent %s already has a child\n",
+                   device_xname(parent));
+               return;
+       }
+       sc->sc_virtio = vsc;
+       sc->sc_max_ports = maxports;
+
+       sc->sc_vqs = kmem_zalloc(2 * (maxports + 1) * sizeof(sc->sc_vqs[0]),
+           KM_SLEEP);
+       sc->sc_ports = kmem_zalloc(maxports * sizeof(sc->sc_ports[0]),
+           KM_SLEEP);
+
+       virtio_child_attach_start(vsc, self, IPL_TTY, sc->sc_vqs,
+           /*config_change*/NULL, virtio_vq_intr,
+           /*req_flags*/0, /*req_features*/VIRTIO_CONSOLE_F_SIZE,
+           VIRTIO_CONSOLE_FLAG_BITS);
+
+       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;
+       }
+       viocon_rx_fill(sc->sc_ports[0]);
+
+       if (virtio_child_attach_finish(vsc) != 0)
+               goto err;
+
+       return;
+err:
+       kmem_free(sc->sc_vqs, 2 * (maxports + 1) * sizeof(sc->sc_vqs[0]));
+       kmem_free(sc->sc_ports, maxports * sizeof(sc->sc_ports[0]));
+       virtio_child_attach_failed(vsc);
+}
+
+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;
+       void *kva;
+       struct tty *tp;
+
+       vp = kmem_zalloc(sizeof(*vp), KM_SLEEP);
+       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, &sc->sc_vqs[rxidx], rxidx, BUFSIZE, 1,
+           name) != 0) {
+               printf("\nCan't alloc %s virtqueue\n", name);
+               goto err;
+       }
+       vp->vp_rx = &sc->sc_vqs[rxidx];
+       vp->vp_rx->vq_done = viocon_rx_intr;
+       vp->vp_si = softint_establish(SOFTINT_SERIAL, 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, &sc->sc_vqs[txidx], txidx, BUFSIZE, 1,
+           name) != 0) {
+               printf("\nCan't alloc %s virtqueue\n", name);
+               goto err;
+       }
+       vp->vp_tx = &sc->sc_vqs[txidx];
+       vp->vp_tx->vq_done = viocon_tx_intr;
+       DPRINTF("%s: tx: %p\n", __func__, vp->vp_tx);
+
+       allocsize = (vp->vp_rx->vq_num + vp->vp_tx->vq_num) * BUFSIZE;
+
+       if (bus_dmamap_create(virtio_dmat(vsc), allocsize, 1, allocsize, 0,
+           BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &vp->vp_dmamap) != 0)
+               goto err;
+       if (bus_dmamem_alloc(virtio_dmat(vsc), allocsize, 8, 0, &vp->vp_dmaseg,
+           1, &nsegs, BUS_DMA_NOWAIT) != 0)
+               goto err;
+       if (bus_dmamem_map(virtio_dmat(vsc), &vp->vp_dmaseg, nsegs,
+           allocsize, &kva, BUS_DMA_NOWAIT) != 0)
+               goto err;
+       memset(kva, 0, allocsize);
+       if (bus_dmamap_load(virtio_dmat(vsc), 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_features(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 = (device_unit(sc->sc_dev) << 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(virtio_dmat(vsc), vp->vp_dmamap,
+                   vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, BUFSIZE,
+                   BUS_DMASYNC_POSTREAD);
+               virtio_dequeue_commit(vsc, vq, slot);
+               ndone++;
+       }
+       return ndone;
+}
+
+int
+viocon_tx_intr(struct virtqueue *vq)
+{
+       struct virtio_softc *vsc = vq->vq_owner;
+       struct viocon_softc *sc = device_private(virtio_child(vsc));
+       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);
+               (*tp->t_linesw->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(vsc, vq, &slot)) == 0) {
+               if (virtio_enqueue_reserve(vsc, vq, slot, 1) != 0)
+                       break;
+               bus_dmamap_sync(virtio_dmat(vsc), vp->vp_dmamap, slot * BUFSIZE,
+                   BUFSIZE, BUS_DMASYNC_PREREAD);
+               virtio_enqueue_p(vsc, 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 = device_private(virtio_child(vsc));
+       int portidx = (vq->vq_index - 1) / 2;
+       struct viocon_port *vp = sc->sc_ports[portidx];
+
+       softint_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(virtio_dmat(vsc), vp->vp_dmamap,
+                   slot * BUFSIZE, BUFSIZE, BUS_DMASYNC_POSTREAD);
+               p = vp->vp_rx_buf + slot * BUFSIZE;
+               for (i = 0; i < len; i++)
+                       (*tp->t_linesw->l_rint)(*p++, tp);
+               virtio_dequeue_commit(vsc, 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(vsc, vq, &slot);
+               if (ret == EAGAIN) {
+                       SET(tp->t_state, TS_BUSY);
+                       break;
+               }
+               KASSERT(ret == 0);
+               ret = virtio_enqueue_reserve(vsc, 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(virtio_dmat(vsc), vp->vp_dmamap,
+                   vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt,
+                   BUS_DMASYNC_PREWRITE);
+               virtio_enqueue_p(vsc, 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 (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);
+               softint_schedule(vp->vp_si);
+       }
+       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 lwp *l)
+{
+       int unit = VIOCONUNIT(dev);
+       int port = VIOCONPORT(dev);
+       struct viocon_softc *sc;
+       struct viocon_port *vp;
+       struct tty *tp;
+       int s, error;
+
+       sc = device_lookup_private(&viocon_cd, unit);
+       if (sc == NULL)
+               return (ENXIO);
+       if (!device_is_active(sc->sc_dev))
+               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 (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp))
+               return (EBUSY);
+
+       s = spltty();
+       if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
+               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;
+               }
+
+               vioconparam(tp, &tp->t_termios);
+               ttsetwater(tp);
+       }
+       splx(s);
+
+       error = (*tp->t_linesw->l_open)(dev, tp);
+       return error;
+}
+
+int
+vioconclose(dev_t dev, int flag, int mode, struct lwp *l)
+{
+       struct viocon_port *vp = dev2port(dev);
+       struct tty *tp = vp->vp_tty;
+       int s;
+
+       if (!ISSET(tp->t_state, TS_ISOPEN))
+               return 0;
+
+       (*tp->t_linesw->l_close)(tp, flag);
+       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 (*tp->t_linesw->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 (*tp->t_linesw->l_write)(tp, uio, flag);
+}
+
+struct tty *
+viocontty(dev_t dev)
+{
+       struct viocon_port *vp = dev2port(dev);
+
+       return vp->vp_tty;
+}
+
+void
+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);
+}
+
+int
+vioconioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
+{
+       struct viocon_port *vp = dev2port(dev);
+       struct tty *tp;
+       int error1, error2;
+
+       tp = vp->vp_tty;
+
+       error1 = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l);
+       if (error1 >= 0)
+               return error1;
+       error2 = ttioctl(tp, cmd, data, flag, l);
+       if (error2 >= 0)
+               return error2;
+       return ENOTTY;
+}

Reply via email to