There's been a bit more work in this area since last month. Here's a
patch including virtio early console and multiport support.

I merged jakllsch@ work and added missing bits from VirtIO spec,
it's now possible to dynamically add serial ports to a running
virtual machine, and/or start a vm with more than 1 port, for instance
to create a direct UNIX socket between the guest and the host.

Link to the patch: https://imil.net/NetBSD/viocon_multiport.patch

Inlined:

---8<---
diff --git a/share/man/man4/viocon.4 b/share/man/man4/viocon.4
index 26f2bd6c26d..12dee306859 100644
--- a/share/man/man4/viocon.4
+++ b/share/man/man4/viocon.4
@@ -58,9 +58,12 @@ by
 .An Stefan Fritsch Aq Mt s...@sfritsch.de .
 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.
+Kernel support was implemented by
+.An Taylor R. Campbell Aq Mt riastr...@netbsd.org .
+.Pp
+Early console support was implemented by
+.An Emile So iMil Sc Heitor Aq Mt i...@netbsd.org .
+.Pp
+Multiport support was implemented by
+.An  Jonathan A. Kollasch Aq Mt jakll...@netbsd.org .
diff --git a/sys/arch/x86/x86/consinit.c b/sys/arch/x86/x86/consinit.c
index 9ef384c2c07..bee9b3a7df6 100644
--- a/sys/arch/x86/x86/consinit.c
+++ b/sys/arch/x86/x86/consinit.c
@@ -104,6 +104,14 @@ __KERNEL_RCSID(0, "$NetBSD: consinit.c,v 1.41 2025/04/30 
05:15:08 imil Exp $");
 #include <xen/xen.h>
 #endif

+#include "viocon.h"
+#if (NVIOCON > 0)
+#include <dev/virtio/virtio_vioconvar.h>
+#include <dev/virtio/virtio_mmiovar.h>
+#include <dev/virtio/arch/x86/virtio_mmio_parse.h>
+#include <uvm/uvm_extern.h> /* for kernel_map */
+#endif
+
 #ifndef CONSDEVNAME
 #define CONSDEVNAME "pc"
 #endif
@@ -263,6 +271,22 @@ dokbd:
                               error);
                return;
        }
+#if (NVIOCON > 0)
+       if (!strcmp(console_devname, "viocon")) {
+               /* We need uvm to be ready before we can map the MMIO region */
+               if (!kernel_map) {
+                       initted = 0;
+                       return;
+               }
+               /*
+                * In x86 microvm, mmio device enumeration is done by parsing
+                * the kernel command line
+                */
+               enumerate_mmio_devices = mmio_args_parse;
+               if (viocon_earlyinit() == 0)
+                       return;
+       }
+#endif
 #if (NCOM > 0)
        if (!strcmp(console_devname, "com")) {
                int addr = consinfo->addr;
diff --git a/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c 
b/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
index 18a0ce9f8bc..b55c5a03ae4 100644
--- a/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
+++ b/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
@@ -63,21 +63,17 @@

 #define VIRTIO_PRIVATE
 #include <dev/virtio/virtio_mmiovar.h>
+#include <dev/virtio/arch/x86/virtio_mmio_parse.h>
 #include <arch/x86/pv/pvvar.h>
 #include <xen/hypervisor.h>
+#include <uvm/uvm_extern.h>
+#include <dev/cons.h>

 #include <machine/i82093var.h>
 #include "ioapic.h"

 #define VMMIOSTR "virtio_mmio.device="

-struct mmio_args {
-       uint64_t        sz;
-       uint64_t        baseaddr;
-       uint64_t        irq;
-       uint64_t        id;
-};
-
 struct virtio_mmio_cmdline_softc {
        struct virtio_mmio_softc        sc_msc;
        struct mmio_args                margs;
@@ -112,6 +108,8 @@ parsearg(struct mmio_args *margs, const char *arg)
 {
        char *p;

+       /* Bus space type */
+       margs->bst = x86_bus_space_mem;
        /* <size> */
        margs->sz = strtoull(arg, (char **)&p, 0);
        if ((margs->sz == 0) || (margs->sz == UINT64_MAX))
@@ -186,50 +184,75 @@ bad:
        aprint_error("Error parsing virtio_mmio parameter: %s\n", arg);
 }

+int
+mmio_args_parse(struct mmio_args *margs)
+{
+       int keylen = strlen(VMMIOSTR);
+       char *next;
+       static char cmdline[LINE_MAX], *parg;
+
+       /* first pass, or emptied parg from last pass */
+       if (parg == NULL) {
+               strlcpy(cmdline, xen_start_info.cmd_line, sizeof(cmdline));
+               parg = strstr(cmdline, VMMIOSTR);
+       }
+
+       /* no args were found */
+       if (parg == NULL) {
+               return MMIO_NO_ARG;
+       }
+
+       /* useless, no parameters */
+       if (strlen(parg) <= keylen) {
+               parg = NULL;
+               return MMIO_NO_ARG;
+       }
+
+       parg += keylen;
+
+       next = parg;
+       while (*next && *next != ' ') /* find end of argument */
+               next++;
+       if (*next) { /* space */
+               *next++ = '\0'; /* end the argument string */
+               next = strstr(next,VMMIOSTR);
+       }
+       parsearg(margs, parg);
+
+       if (next != NULL)
+               parg = next;
+
+       if (!*parg) {
+               parg = NULL;
+               return MMIO_LAST_ARG;
+       }
+
+       return MMIO_NEXT_ARG;
+}
+
 static void
 virtio_mmio_cmdline_attach(device_t parent, device_t self, void *aux)
 {
        struct virtio_mmio_cmdline_softc *sc = device_private(self);
        struct pv_attach_args *pvaa = aux;
        struct mmio_args *margs = &sc->margs;
-       int keylen = strlen(VMMIOSTR);
-       char *next;
-       static char cmdline[LINE_MAX], *parg = NULL;
+       int mmioarg;

        aprint_normal("\n");
        aprint_naive("\n");

-       if (parg == NULL) { /* first pass */
-               strlcpy(cmdline, xen_start_info.cmd_line, sizeof(cmdline));
-               aprint_verbose_dev(self, "kernel parameters: %s\n",
-                   cmdline);
-               parg = strstr(cmdline, VMMIOSTR);
-       }
+       mmioarg = mmio_args_parse(margs);

-       if (parg != NULL) {
-               parg += keylen;
-               if (!*parg)
-                       return;
-
-               next = parg;
-               while (*next && *next != ' ') /* find end of argument */
-                       next++;
-               if (*next) { /* space */
-                       *next++ = '\0'; /* end the argument string */
-                       next = strstr(next, VMMIOSTR);
-               }
-
-               aprint_normal_dev(self, "viommio: %s\n", parg);
-               parsearg(margs, parg);
-
-               if (virtio_mmio_cmdline_do_attach(self, pvaa, margs))
-                       return;
-
-               if (next) {
-                       parg = next;
-                       config_found(parent, pvaa, NULL, CFARGS_NONE);
-               }
-       }
+       if (mmioarg == MMIO_NO_ARG)
+               return;
+
+       aprint_normal_dev(self, "viommio: @%#" PRIxPADDR "\n", margs->baseaddr);
+
+       if (virtio_mmio_cmdline_do_attach(self, pvaa, margs))
+               return;
+
+       if (mmioarg == MMIO_NEXT_ARG)
+               config_found(parent, pvaa, NULL, CFARGS_NONE);
 }

 static int
@@ -250,11 +273,10 @@ virtio_mmio_cmdline_do_attach(device_t self,
        error = bus_space_map(msc->sc_iot, margs->baseaddr, margs->sz, 0,
            &msc->sc_ioh);
        if (error) {
-               aprint_error_dev(self, "couldn't map %#" PRIx64 ": %d",
+               aprint_error_dev(self, "couldn't map %#" PRIxPADDR ": %d",
                    margs->baseaddr, error);
                return error;
        }
-
        msc->sc_alloc_interrupts = virtio_mmio_cmdline_alloc_interrupts;
        msc->sc_free_interrupts = virtio_mmio_cmdline_free_interrupts;

diff --git a/sys/dev/virtio/arch/x86/virtio_mmio_parse.h 
b/sys/dev/virtio/arch/x86/virtio_mmio_parse.h
new file mode 100644
index 00000000000..e78ac50b6d3
--- /dev/null
+++ b/sys/dev/virtio/arch/x86/virtio_mmio_parse.h
@@ -0,0 +1,41 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Emile 'iMil' Heitor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _VIRTIO_MMIO_PARSE_H_
+#define _VIRTIO_MMIO_PARSE_H_
+
+#define MMIO_NEXT_ARG 2
+#define MMIO_LAST_ARG 1
+#define MMIO_NO_ARG 0
+
+int mmio_args_parse(struct mmio_args *);
+
+#endif /* _VIRTIO_MMIO_PARSE_H_ */
diff --git a/sys/dev/virtio/files.virtio b/sys/dev/virtio/files.virtio
index 4e399624ab0..9380e10b3b5 100644
--- a/sys/dev/virtio/files.virtio
+++ b/sys/dev/virtio/files.virtio
@@ -7,4 +7,4 @@ file    dev/virtio/virtio_mmio.c        virtio_mmio

 device viocon
 attach viocon at virtio
-file   dev/virtio/viocon.c             viocon
+file   dev/virtio/viocon.c             viocon  needs-flag
diff --git a/sys/dev/virtio/viocon.c b/sys/dev/virtio/viocon.c
index 73c938936be..3c801ee1965 100644
--- a/sys/dev/virtio/viocon.c
+++ b/sys/dev/virtio/viocon.c
@@ -32,9 +32,17 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 
riastradh Exp $"
 #include <sys/lwp.h>
 #include <sys/systm.h>
 #include <sys/tty.h>
+#include <sys/fcntl.h>
+#include <sys/poll.h>
+
+#include <dev/cons.h>
+#include <uvm/uvm_extern.h>
+#include <dev/virtio/virtio_vioconvar.h>

 #include <dev/pci/virtioreg.h>
-#include <dev/pci/virtiovar.h>
+#include <dev/virtio/virtio_mmiovar.h>
+
+#include <prop/proplib.h>

 #include "ioconf.h"

@@ -49,6 +57,8 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 
riastradh Exp $"
 #define        VIRTIO_CONSOLE_F_MULTIPORT      (1ULL<<1)
 #define        VIRTIO_CONSOLE_F_EMERG_WRITE    (1ULL<<2)

+#define VIRTIO_CONSOLE_BAD_ID          (~(uint32_t)0)
+
 /* config space */
 #define VIRTIO_CONSOLE_COLS            0       /* 16 bits */
 #define VIRTIO_CONSOLE_ROWS            2       /* 16 bits */
@@ -69,12 +79,15 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 
riastradh Exp $"
        "b\x01" "MULTIPORT\0"                                                   
  \
        "b\x02" "EMERG_WRITE\0"

+/* The control virtqueues are after the first port */
+#define VIOCON_CTRL_RX_IDX 2
+#define VIOCON_CTRL_TX_IDX 3
 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_DEVICE_ADD       1
+#define        VIRTIO_CONSOLE_DEVICE_REMOVE    2
 #define        VIRTIO_CONSOLE_PORT_READY       3
 #define        VIRTIO_CONSOLE_CONSOLE_PORT     4
 #define        VIRTIO_CONSOLE_RESIZE           5
@@ -83,7 +96,7 @@ struct virtio_console_control {
        uint16_t event;

        uint16_t value;
-};
+} __packed;

 struct virtio_console_control_resize {
        /* yes, the order is different than in config space */
@@ -93,10 +106,10 @@ struct virtio_console_control_resize {

 #define        BUFSIZE         128

-#define        VIOCONDEV(u,p)  makedev(cdevsw_lookup_major(&viocon_cdevsw),    
          \
-                           ((u) << 4) | (p))
-#define VIOCONUNIT(x)  (minor(x) >> 4)
-#define VIOCONPORT(x)  (minor(x) & 0x0f)
+#define VIOCONDEV(u,p) makedev(cdevsw_lookup_major(&viocon_cdevsw),        \
+    ((u & 0xff) << 4) | (p & 0xf))
+#define VIOCONUNIT(x)  (TTUNIT(x) >> 4)
+#define VIOCONPORT(x)  (TTUNIT(x) & 0x0f)

 struct viocon_port {
        struct viocon_softc     *vp_sc;
@@ -104,19 +117,24 @@ struct viocon_port {
        struct virtqueue        *vp_tx;
        void                    *vp_si;
        struct tty              *vp_tty;
-       const char              *vp_name;
+       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_host_open:1;
+       unsigned int             vp_guest_open:1;
+       unsigned int             vp_is_console:1;
+
        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 consdev           vp_cntab;
+       unsigned int             vp_pollpos;
+       unsigned int             vp_polllen;
+       bool                     vp_polling;
 };

 struct viocon_softc {
@@ -132,10 +150,26 @@ struct viocon_softc {

        unsigned int             sc_max_ports;
        struct viocon_port      **sc_ports;
+
+       bool                    has_multiport;
+
+       bus_dma_segment_t        sc_dmaseg;
+       bus_dmamap_t             sc_dmamap;
+       struct {
+               struct virtio_console_control ctrl;
+               uint8_t buf[BUFSIZE-8];
+       }                       *sc_ctrl_rx;
+       bus_dmamap_t             sc_dmamap_tx;
+       union {
+               struct virtio_console_control sc_tx;
+               uint8_t         sc_tx_buf[16];
+       };
 };

 int    viocon_match(struct device *, struct cfdata *, void *);
 void   viocon_attach(struct device *, struct device *, void *);
+int    viocon_control_rx_intr(struct virtqueue *);
+int    viocon_control_tx_intr(struct virtqueue *);
 int    viocon_tx_intr(struct virtqueue *);
 int    viocon_tx_drain(struct viocon_port *, struct virtqueue *vq);
 int    viocon_rx_intr(struct virtqueue *);
@@ -151,7 +185,33 @@ 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);
+static int viocon_ports_vq_alloc(struct viocon_softc *, int);
+struct tty *viocontty(dev_t dev);
+static dev_type_poll(vioconpoll);
+static void viocon_port_destroy(struct viocon_softc *, int);
+static void viocon_control_rx_fill(struct viocon_softc *);
+static void viocon_control_dmamap(struct viocon_softc *);
+static int viocon_control_send(struct viocon_softc *, uint32_t, uint16_t,
+    uint16_t );
+
+static void viocon_console(struct viocon_softc *, int);
+static void viocon_cnpollc(dev_t, int);
+static int viocon_cngetc(dev_t);
+static void viocon_cnputc(dev_t, int);
+static void viocon_early_putc(dev_t, int);
+
+static uint32_t *early_console;
+static vaddr_t viocon_mmio_vaddr = 0;
+
+int (*enumerate_mmio_devices)(struct mmio_args *) = NULL;
+
+static struct consdev viocon_early_consdev = {
+    .cn_putc = viocon_early_putc,
+    .cn_getc = NULL,
+    .cn_pollc = NULL,
+    .cn_dev = NODEV,
+    .cn_pri = CN_NORMAL
+};

 CFATTACH_DECL_NEW(viocon, sizeof(struct viocon_softc),
     viocon_match, viocon_attach, /*detach*/NULL, /*activate*/NULL);
@@ -164,7 +224,7 @@ const struct cdevsw viocon_cdevsw = {
        .d_ioctl = vioconioctl,
        .d_stop = vioconstop,
        .d_tty = viocontty,
-       .d_poll = nopoll,       /* XXX */
+       .d_poll = vioconpoll,
        .d_mmap = nommap,
        .d_kqfilter = ttykqfilter,
        .d_discard = nodiscard,
@@ -182,8 +242,92 @@ dev2port(dev_t dev)
 {
        return dev2sc(dev)->sc_ports[VIOCONPORT(dev)];
 }
+static inline int
+viocon_vqidx2portidx(int vq)
+{
+       return (vq >= 4) ? (vq - VIOCON_PORT_NQS) / VIOCON_PORT_NQS : 0;
+}
+
+static void
+viocon_early_putc(dev_t dev, int c)
+{
+       *early_console = c;
+}

-int viocon_match(struct device *parent, struct cfdata *match, void *aux)
+static void
+free_viocon_mmio_vaddr(void)
+{
+       if (!viocon_mmio_vaddr)
+               return;
+
+       pmap_kremove(viocon_mmio_vaddr, PAGE_SIZE);
+       pmap_update(pmap_kernel());
+       uvm_km_free(kernel_map, viocon_mmio_vaddr, PAGE_SIZE, UVM_KMF_VAONLY);
+       viocon_mmio_vaddr = 0;
+}
+
+int
+viocon_earlyinit(void)
+{
+       struct mmio_args margs;
+       paddr_t mmio_baseaddr = 0;
+       struct virtio_mmio_softc sc;
+
+       if (enumerate_mmio_devices == NULL)
+               return -1;
+
+       while ((*enumerate_mmio_devices)(&margs)) {
+               if (!mmio_baseaddr) {
+                       /* Fetch a page for early MMIO mapping */
+                       viocon_mmio_vaddr = uvm_km_alloc(kernel_map, PAGE_SIZE, 
0,
+                           UVM_KMF_VAONLY | UVM_KMF_NOWAIT);
+                       if (!viocon_mmio_vaddr)
+                               return -1;
+                       /* map a page for early virtio console */
+                       mmio_baseaddr = margs.baseaddr & ~(PAGE_SIZE - 1);
+                       pmap_kenter_pa(viocon_mmio_vaddr, mmio_baseaddr,
+                           VM_PROT_READ|VM_PROT_WRITE, PMAP_NOCACHE);
+                       pmap_update(pmap_kernel());
+               }
+
+               sc.sc_iot = margs.bst;
+               /*
+                * viocon_mmio_vaddr = reserved page
+                * margs.baseaddr = pa
+                * mmio_baseaddr = MMIO base address
+                */
+               sc.sc_ioh = viocon_mmio_vaddr + (margs.baseaddr - 
mmio_baseaddr);
+               sc.sc_iosize = margs.sz;
+               sc.sc_le_regs = (BYTE_ORDER == LITTLE_ENDIAN);
+
+               aprint_verbose("mmio addr:%#" PRIxPADDR "\n", margs.baseaddr);
+
+               if (bus_space_read_4(sc.sc_iot, sc.sc_ioh,
+                   VIRTIO_MMIO_MAGIC_VALUE) != VIRTIO_MMIO_MAGIC)
+                       continue;
+               if (virtio_mmio_reg_read(&sc, VIRTIO_MMIO_DEVICE_ID) !=
+                   VIRTIO_DEVICE_ID_CONSOLE)
+                       continue;
+               if (!(virtio_mmio_reg_read(&sc, VIRTIO_MMIO_DEVICE_FEATURES) &
+                   VIRTIO_CONSOLE_F_EMERG_WRITE))
+                       continue;
+
+               early_console =
+                   (uint32_t *)(sc.sc_ioh +
+                   VIRTIO_MMIO_CONFIG + VIRTIO_CONSOLE_EMERG_WR);
+
+               cn_tab = &viocon_early_consdev;
+
+               return 0;
+       }
+
+       free_viocon_mmio_vaddr();
+
+       return -1;
+}
+
+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)
@@ -196,6 +340,8 @@ viocon_attach(struct device *parent, struct device *self, 
void *aux)
 {
        struct viocon_softc *sc = device_private(self);
        struct virtio_softc *vsc = device_private(parent);
+       prop_dictionary_t dict = device_properties(self);
+       prop_array_t namearray;
        int maxports = 1;
        size_t nvqs;

@@ -205,29 +351,63 @@ viocon_attach(struct device *parent, struct device *self, 
void *aux)
                    device_xname(parent));
                return;
        }
+
+       virtio_child_attach_start(vsc, self, IPL_TTY,
+           /*req_features*/VIRTIO_CONSOLE_F_SIZE | VIRTIO_CONSOLE_F_MULTIPORT
+           | VIRTIO_CONSOLE_F_EMERG_WRITE | VIRTIO_F_RING_EVENT_IDX,
+           VIRTIO_CONSOLE_FLAG_BITS);
+
+       if (vsc->sc_active_features & VIRTIO_CONSOLE_F_MULTIPORT) {
+               maxports = virtio_read_device_config_4(vsc,
+                   VIRTIO_CONSOLE_MAX_NR_PORTS);
+               aprint_verbose_dev(self,
+                   "has multiport feature, max ports: %u\n", maxports);
+               sc->has_multiport = true;
+       } else
+               sc->has_multiport = false;
+
        sc->sc_virtio = vsc;
        sc->sc_max_ports = maxports;
        nvqs = VIOCON_PORT_NQS * maxports;
+       if (sc->has_multiport)
+               nvqs += 2; /* rx and tx control */
+
+       prop_dictionary_set_uint32(dict, "max ports", sc->sc_max_ports);
+       namearray = prop_array_create_with_capacity(sc->sc_max_ports);
+       prop_dictionary_set(dict, "port names", namearray);

        sc->sc_vqs = kmem_zalloc(nvqs * 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,
-           /*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__);
+       if (viocon_ports_vq_alloc(sc, maxports) != 0)
                goto err;
+
+       if (!sc->has_multiport) {
+               DPRINTF("%s: softc: %p\n", __func__, sc);
+               /* virtqueue index handled in port creation */
+               if (viocon_port_create(sc, 0) != 0) {
+                       printf("\n%s: viocon_port_create failed\n", __func__);
+                       goto err;
+               }
+               viocon_console(sc, 0);
        }

        if (virtio_child_attach_finish(vsc, sc->sc_vqs, nvqs,
            /*config_change*/NULL, /*req_flags*/0) != 0)
                goto err;

-       viocon_rx_fill(sc->sc_ports[0]);
+       if (sc->has_multiport) {
+               viocon_control_rx_fill(sc);
+
+               viocon_control_send(sc, VIRTIO_CONSOLE_BAD_ID,
+                   VIRTIO_CONSOLE_DEVICE_READY, 1);
+
+               virtio_start_vq_intr(vsc, sc->sc_c_vq_rx);
+               virtio_start_vq_intr(vsc, sc->sc_c_vq_tx);
+       }
+

        return;
 err:
@@ -236,48 +416,276 @@ err:
        virtio_child_attach_failed(vsc);
 }

-int
-viocon_port_create(struct viocon_softc *sc, int portidx)
+static void
+viocon_control_dmamap(struct viocon_softc *sc)
+{
+       struct virtio_softc *vsc = sc->sc_virtio;
+       int nsegs;
+       void *kva;
+
+       if (bus_dmamap_create(virtio_dmat(vsc), BUFSIZE, 1, BUFSIZE, 0,
+           BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_dmamap) != 0) {
+               goto err;
+       }
+
+       if (bus_dmamap_create(virtio_dmat(vsc), 16, 1, 16, 0,
+           BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_dmamap_tx) != 0) {
+               goto err;
+       }
+
+       if (bus_dmamem_alloc(virtio_dmat(vsc), BUFSIZE,
+           sizeof(struct virtio_console_control), 0, &sc->sc_dmaseg,
+           1, &nsegs, BUS_DMA_NOWAIT) != 0)
+               goto err;
+
+       if (bus_dmamem_map(virtio_dmat(vsc), &sc->sc_dmaseg, nsegs,
+           BUFSIZE, &kva, BUS_DMA_NOWAIT) != 0)
+               goto err;
+
+       memset(kva, 0, BUFSIZE);
+       sc->sc_ctrl_rx = kva;
+
+       if (bus_dmamap_load(virtio_dmat(vsc), sc->sc_dmamap, sc->sc_ctrl_rx,
+           BUFSIZE, NULL, BUS_DMA_NOWAIT|BUS_DMA_READ) != 0)
+               goto err;
+
+       return;
+err:
+       panic("%s failed", __func__);
+}
+
+static int
+viocon_ports_vq_alloc(struct viocon_softc *sc, int maxports)
 {
        struct virtio_softc *vsc = sc->sc_virtio;
-       int rxidx, txidx, allocsize, nsegs;
+       int rxidx, txidx, i;
        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);
-
-       rxidx = (portidx * VIOCON_PORT_NQS) + VIOCON_PORT_RX;
-       txidx = (portidx * VIOCON_PORT_NQS) + VIOCON_PORT_TX;
-
-       snprintf(name, sizeof(name), "p%drx", portidx);
-       virtio_init_vq_vqdone(vsc, &sc->sc_vqs[rxidx], rxidx,
-           viocon_rx_intr);
-       if (virtio_alloc_vq(vsc, &sc->sc_vqs[rxidx], BUFSIZE, 1,
-           name) != 0) {
-               printf("\nCan't alloc %s virtqueue\n", name);
+       for (i = 0; i < maxports; i++) {
+               /* The control virtqueues are after the first port. */
+               if (sc->has_multiport && i == 1) {
+                       /* allocate control virtqueues */
+                       sc->sc_c_vq_rx = &sc->sc_vqs[VIOCON_CTRL_RX_IDX];
+                       sc->sc_c_vq_tx = &sc->sc_vqs[VIOCON_CTRL_TX_IDX];
+
+                       virtio_init_vq_vqdone(vsc, sc->sc_c_vq_rx, 
VIOCON_CTRL_RX_IDX,
+                           viocon_control_rx_intr);
+                       virtio_init_vq_vqdone(vsc, sc->sc_c_vq_tx, 
VIOCON_CTRL_TX_IDX,
+                           viocon_control_tx_intr);
+
+                       if (virtio_alloc_vq(vsc, 
&sc->sc_vqs[VIOCON_CTRL_RX_IDX],
+                           BUFSIZE, 1, "control_rx"))
+                               goto err;
+                       if (virtio_alloc_vq(vsc, 
&sc->sc_vqs[VIOCON_CTRL_TX_IDX],
+                           16, 1, "control_tx"))
+                               goto err;
+
+                       viocon_control_dmamap(sc);
+               }
+
+               vp = kmem_zalloc(sizeof(*vp), KM_SLEEP);
+               if (vp == NULL)
+                       return ENOMEM;
+
+               sc->sc_ports[i] = vp;
+               vp->vp_sc = sc;
+               DPRINTF("%s: vp: %p\n", __func__, vp);
+
+               rxidx = (i * VIOCON_PORT_NQS) + VIOCON_PORT_RX;
+               txidx = (i * VIOCON_PORT_NQS) + VIOCON_PORT_TX;
+
+               if (i > 0) {
+                       rxidx += VIOCON_CTRL_RX_IDX;
+                       txidx += VIOCON_CTRL_RX_IDX;
+               }
+
+               snprintf(name, sizeof(name), "p%drx", i);
+               virtio_init_vq_vqdone(vsc, &sc->sc_vqs[rxidx], rxidx,
+                   viocon_rx_intr);
+               if (virtio_alloc_vq(vsc, &sc->sc_vqs[rxidx], BUFSIZE, 1,
+                   name) != 0) {
+                       printf("\nCan't alloc %s virtqueue\n", name);
+                       goto err;
+               }
+               vp->vp_rx = &sc->sc_vqs[rxidx];
+               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", i);
+               virtio_init_vq_vqdone(vsc, &sc->sc_vqs[txidx], txidx,
+                   viocon_tx_intr);
+               if (virtio_alloc_vq(vsc, &sc->sc_vqs[txidx], BUFSIZE, 1,
+                   name) != 0) {
+                       printf("\nCan't alloc %s virtqueue\n", name);
+                       goto err;
+               }
+               vp->vp_tx = &sc->sc_vqs[txidx];
+               DPRINTF("%s: tx: %p\n", __func__, vp->vp_tx);
+       }
+
+       return 0;
+err:
+       panic("%s failed", __func__);
+       return -1;
+}
+
+static void
+viocon_control_rx_fill(struct viocon_softc *sc)
+{
+       struct virtio_softc *vsc = sc->sc_virtio;
+       struct virtqueue *vq = sc->sc_c_vq_rx;
+       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), sc->sc_dmamap, slot * BUFSIZE,
+                   BUFSIZE, BUS_DMASYNC_PREREAD);
+               virtio_enqueue_p(vsc, vq, slot, sc->sc_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_control_tx_intr(struct virtqueue *vq)
+{
+       struct virtio_softc *vsc = vq->vq_owner;
+       struct viocon_softc *sc = device_private(virtio_child(vsc));
+       int ndone = 0, slot, len;
+
+       while (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
+               bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap_tx,
+                   0, 8, BUS_DMASYNC_POSTWRITE);
+               virtio_dequeue_commit(vsc, vq, slot);
+               ndone++;
+       }
+       return ndone;
+}
+
+int
+viocon_control_rx_intr(struct virtqueue *vq)
+{
+       struct virtio_softc *vsc = vq->vq_owner;
+       struct viocon_softc *sc = device_private(virtio_child(vsc));
+       struct viocon_port *vp;
+       prop_dictionary_t properties = device_properties(sc->sc_dev);
+       prop_array_t namearray;
+       int ndone = 0, slot, len, namelen;
+       uint32_t id;
+       uint16_t event, value __unused;
+
+       while (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
+               bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap,
+                   slot * BUFSIZE, BUFSIZE, BUS_DMASYNC_POSTREAD);
+               id = virtio_rw32(vsc, sc->sc_ctrl_rx[slot].ctrl.id);
+               event = virtio_rw16(vsc, sc->sc_ctrl_rx[slot].ctrl.event);
+               value = virtio_rw16(vsc, sc->sc_ctrl_rx[slot].ctrl.value);
+
+               if (id != VIRTIO_CONSOLE_BAD_ID && id >= sc->sc_max_ports) {
+                       DPRINTF("%s: invalid port id %d\n", __func__, id);
+                       return 0;
+               }
+
+               switch (event) {
+               /* 
https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.html#x1-3290006
 */
+               case VIRTIO_CONSOLE_DEVICE_ADD:
+                       aprint_verbose_dev(sc->sc_dev, "adding port %u\n", id);
+                       if (viocon_port_create(sc, id) == 0) {
+                               viocon_control_send(sc, id,
+                                   VIRTIO_CONSOLE_PORT_READY, 1);
+                               /* Give time to catch CONSOLE_PORT and 
PORT_OPEN */
+                               DELAY(50);
+                       }
+                       break;
+               case VIRTIO_CONSOLE_CONSOLE_PORT:
+                       aprint_verbose_dev(sc->sc_dev, "%u is a console 
port\n", id);
+                       sc->sc_ports[id]->vp_is_console = 1;
+                       viocon_console(sc, id);
+                       break;
+               case VIRTIO_CONSOLE_PORT_OPEN:
+                       aprint_verbose_dev(sc->sc_dev, "host opened port %u\n", 
id);
+                       sc->sc_ports[id]->vp_host_open = 1;
+                       break;
+               case VIRTIO_CONSOLE_PORT_NAME:
+                       namelen = len - sizeof(sc->sc_ctrl_rx[slot].ctrl);
+                       if (namelen > 0) {
+                               vp = sc->sc_ports[id];
+                               if (vp->vp_name != NULL)
+                                       kmem_free(vp->vp_name, 7);
+                               vp->vp_name = kmem_zalloc(namelen, KM_SLEEP);
+                               memcpy(vp->vp_name, sc->sc_ctrl_rx[slot].buf, 
namelen);
+                               vp->vp_name[namelen] = '\0';
+                               aprint_normal_dev(sc->sc_dev, "port %u name: 
%s\n",
+                                   id, vp->vp_name);
+                               namearray = prop_dictionary_get(properties, "port 
names");
+                               prop_array_set_string_nocopy(namearray, id, 
vp->vp_name);
+                       }
+                       break;
+               case VIRTIO_CONSOLE_DEVICE_REMOVE:
+                       if (sc->sc_ports[id] != NULL)
+                               viocon_port_destroy(sc, id);
+                       break;
+               default:
+                       aprint_verbose("unsupported event: %u\n", event);
+               }
+
+               virtio_dequeue_commit(vsc, vq, slot);
+               ndone++;
+       }
+
+       viocon_control_rx_fill(sc);
+
+       return ndone;
+}
+
+static int
+viocon_control_send(struct viocon_softc *sc, uint32_t id, uint16_t event,
+    uint16_t value)
+{
+       struct virtio_softc *vsc = sc->sc_virtio;
+       int slot;
+
+       sc->sc_tx.id = virtio_rw32(vsc, id);
+       sc->sc_tx.event = virtio_rw16(vsc, event);
+       sc->sc_tx.value = virtio_rw16(vsc, value);
+
+       bus_dmamap_load(virtio_dmat(vsc), sc->sc_dmamap_tx, sc->sc_tx_buf,
+           8, NULL, BUS_DMA_NOWAIT|BUS_DMA_WRITE);
+       bus_dmamap_sync(virtio_dmat(vsc), sc->sc_dmamap_tx,
+           0, 8, BUS_DMASYNC_PREWRITE);
+
+       if (virtio_enqueue_prep(vsc, sc->sc_c_vq_tx, &slot)) {
                goto err;
        }
-       vp->vp_rx = &sc->sc_vqs[rxidx];
-       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);
-       virtio_init_vq_vqdone(vsc, &sc->sc_vqs[txidx], txidx,
-           viocon_tx_intr);
-       if (virtio_alloc_vq(vsc, &sc->sc_vqs[txidx], BUFSIZE, 1,
-           name) != 0) {
-               printf("\nCan't alloc %s virtqueue\n", name);
+       if (virtio_enqueue_reserve(vsc, sc->sc_c_vq_tx, slot, 1)) {
                goto err;
        }
-       vp->vp_tx = &sc->sc_vqs[txidx];
-       DPRINTF("%s: tx: %p\n", __func__, vp->vp_tx);
+
+       virtio_enqueue(vsc, sc->sc_c_vq_tx, slot, sc->sc_dmamap_tx, true);
+       virtio_enqueue_commit(vsc, sc->sc_c_vq_tx, slot, 1);
+
+       return 0;
+err:
+       return -1;
+}
+
+
+int
+viocon_port_create(struct viocon_softc *sc, int portidx)
+{
+       struct virtio_softc *vsc = sc->sc_virtio;
+       struct viocon_port *vp = sc->sc_ports[portidx];
+       struct tty *tp;
+       prop_dictionary_t properties = device_properties(sc->sc_dev);
+       prop_array_t namearray;
+       int allocsize, nsegs;
+       void *kva;

        allocsize = (vp->vp_rx->vq_num + vp->vp_tx->vq_num) * BUFSIZE;

@@ -314,16 +722,62 @@ viocon_port_create(struct viocon_softc *sc, int portidx)
        tp->t_dev = VIOCONDEV(device_unit(sc->sc_dev), portidx);
        vp->vp_tty = tp;
        DPRINTF("%s: tty: %p\n", __func__, tp);
+       vp->vp_name = kmem_zalloc(7, KM_SLEEP);
+       snprintf(vp->vp_name, 7, "port%02d", portidx);
+
+       tty_attach(tp);

        virtio_start_vq_intr(vsc, vp->vp_rx);
        virtio_start_vq_intr(vsc, vp->vp_tx);

+       viocon_rx_fill(sc->sc_ports[portidx]);
+
+       namearray = prop_dictionary_get(properties, "port names");
+       prop_array_set_string_nocopy(namearray, portidx, vp->vp_name);
+
        return 0;
 err:
        panic("%s failed", __func__);
        return -1;
 }

+static void
+viocon_port_destroy(struct viocon_softc *sc, int portidx)
+{
+       struct viocon_port *vp = sc->sc_ports[portidx];
+
+       if (vp == NULL)
+               return;
+
+       viocon_control_send(sc, portidx, VIRTIO_CONSOLE_PORT_READY, 0);
+
+       if (vp->vp_name != NULL)
+               kmem_free(vp->vp_name, strlen(vp->vp_name) + 1);
+       tty_free(vp->vp_tty);
+       vp = NULL;
+}
+
+static void
+viocon_console(struct viocon_softc *sc, int portidx)
+{
+       struct viocon_port *vp = sc->sc_ports[portidx];
+
+       if (cn_tab != NULL && cn_tab->cn_dev != NODEV)
+               return;
+
+       sc->sc_ports[portidx]->vp_cntab = (struct consdev) {
+               .cn_pollc = viocon_cnpollc,
+               .cn_getc = viocon_cngetc,
+               .cn_putc = viocon_cnputc,
+               .cn_dev = vp->vp_tty->t_dev,
+               .cn_pri = CN_REMOTE,
+       };
+       aprint_normal_dev(sc->sc_dev, "console\n");
+       cn_tab = &sc->sc_ports[portidx]->vp_cntab;
+       /* if a page was mapped for early console, free it */
+       free_viocon_mmio_vaddr();
+}
+
 int
 viocon_tx_drain(struct viocon_port *vp, struct virtqueue *vq)
 {
@@ -346,17 +800,19 @@ 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;
+       int portidx = viocon_vqidx2portidx(vq->vq_index);
        struct viocon_port *vp = sc->sc_ports[portidx];
        struct tty *tp = vp->vp_tty;
+       int ndone = 0;

        splassert(IPL_TTY);
        ndone = viocon_tx_drain(vp, vq);
+       ttylock(tp);
        if (ndone && ISSET(tp->t_state, TS_BUSY)) {
                CLR(tp->t_state, TS_BUSY);
                (*tp->t_linesw->l_start)(tp);
        }
+       ttyunlock(tp);

        return 1;
 }
@@ -388,7 +844,7 @@ 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;
+       int portidx = viocon_vqidx2portidx(vq->vq_index);
        struct viocon_port *vp = sc->sc_ports[portidx];

        softint_schedule(vp->vp_si);
@@ -414,6 +870,9 @@ viocon_rx_soft(void *arg)
                virtio_dequeue_commit(vsc, vq, slot);
        }

+       if (virtio_features(vsc) & VIRTIO_F_RING_EVENT_IDX)
+               virtio_start_vq_intr(vsc, vp->vp_rx);
+
        viocon_rx_fill(vp);

        return;
@@ -444,10 +903,16 @@ vioconstart(struct tty *tp)
        if (ISSET(tp->t_state, TS_TIMEOUT | TS_TTSTOP))
                goto out;

+       if (!ttypull(tp))
+               goto out;
+
        if (tp->t_outq.c_cc == 0)
                goto out;
        ndone = 0;

+       if (virtio_features(vsc) & VIRTIO_F_RING_EVENT_IDX)
+               virtio_start_vq_intr(vsc, vp->vp_tx);
+
        while (tp->t_outq.c_cc > 0) {
                ret = virtio_enqueue_prep(vsc, vq, &slot);
                if (ret == EAGAIN) {
@@ -466,6 +931,7 @@ vioconstart(struct tty *tp)
                    vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt, 1);
                virtio_enqueue_commit(vsc, vq, slot, 0);
                ndone++;
+               ttypull(tp);
        }
        if (ndone > 0)
                virtio_notify(vsc, vq);
@@ -526,9 +992,8 @@ vioconopen(dev_t dev, int flag, int mode, struct lwp *l)
        }
        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))
@@ -553,7 +1018,18 @@ vioconopen(dev_t dev, int flag, int mode, struct lwp *l)
        }
        splx(s);

+       error = ttyopen(tp, TTDIALOUT(dev), ISSET(flag, O_NONBLOCK));
+       if (error)
+               goto bad;
+
        error = (*tp->t_linesw->l_open)(dev, tp);
+       if (error)
+               goto bad;
+
+       if ((virtio_features(sc->sc_virtio) & VIRTIO_CONSOLE_F_MULTIPORT) != 0) 
{
+               viocon_control_send(sc, port, VIRTIO_CONSOLE_PORT_OPEN, 1);
+       }
+bad:
        return error;
 }

@@ -569,13 +1045,18 @@ vioconclose(dev_t dev, int flag, int mode, struct lwp *l)

        (*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);

+       if ((virtio_features(vp->vp_sc->sc_virtio) &
+           VIRTIO_CONSOLE_F_MULTIPORT) != 0) {
+               viocon_control_send(vp->vp_sc, VIOCONPORT(dev),
+                   VIRTIO_CONSOLE_PORT_OPEN, 0);
+       }
+
        return 0;
 }

@@ -634,3 +1115,86 @@ vioconioctl(dev_t dev, u_long cmd, void *data, int flag, 
struct lwp *l)
                return error2;
        return ENOTTY;
 }
+
+static void
+viocon_cnpollc(dev_t dev, int on)
+{
+       struct viocon_port *vp = dev2port(dev);
+       int s;
+
+       KASSERT((bool)on != vp->vp_polling);
+
+       s = spltty();
+       vp->vp_polling = on;
+       vioconhwiflow(vp->vp_tty, on);
+       splx(s);
+}
+
+static int
+viocon_cngetc(dev_t dev)
+{
+       struct viocon_softc *sc = dev2sc(dev);
+       struct viocon_port *vp = dev2port(dev);
+       struct virtqueue *vq = vp->vp_rx;
+       struct virtio_softc *vsc = sc->sc_virtio;
+       int slot, len;
+
+       KASSERT(vp->vp_polling);
+       while (vp->vp_polllen == 0) {
+               if (virtio_dequeue(vsc, vq, &slot, &len) == 0) {
+                       KASSERTMSG(slot >= 0, "slot=%d", slot);
+                       KASSERTMSG(slot < vq->vq_num, "slot=%d", slot);
+                       KASSERTMSG(len > 0, "len=%d", len);
+                       KASSERTMSG(len <= BUFSIZE, "len=%d", len);
+                       bus_dmamap_sync(virtio_dmat(vsc), vp->vp_dmamap,
+                           slot * BUFSIZE, BUFSIZE, BUS_DMASYNC_POSTREAD);
+                       vp->vp_polllen = len;
+                       vp->vp_pollpos = slot * BUFSIZE;
+               }
+       }
+       KASSERT(vp->vp_pollpos <= vq->vq_num * BUFSIZE);
+       vp->vp_polllen--;
+       return vp->vp_rx_buf[vp->vp_pollpos++];
+}
+
+static void
+viocon_cnputc(dev_t dev, int c)
+{
+       struct viocon_softc *sc = dev2sc(dev);
+       struct viocon_port *vp = dev2port(dev);
+       struct virtqueue *vq = vp->vp_tx;
+       struct virtio_softc *vsc = sc->sc_virtio;
+       int slot;
+       int s, error;
+
+       s = spltty();
+       KERNEL_LOCK(1, NULL);
+       (void)viocon_tx_drain(vp, vq);
+       error = virtio_enqueue_prep(vsc, vq, &slot);
+       if (error == 0) {
+               error = virtio_enqueue_reserve(vsc, vq, slot, 1);
+               KASSERTMSG(error == 0, "error=%d", error);
+               vp->vp_tx_buf[slot * BUFSIZE] = c;
+               bus_dmamap_sync(virtio_dmat(vsc), vp->vp_dmamap,
+                   vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, 1,
+                   BUS_DMASYNC_PREWRITE);
+               virtio_enqueue_p(vsc, vq, slot, vp->vp_dmamap,
+                   vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, 1, 1);
+               virtio_enqueue_commit(vsc, vq, slot, 1);
+       }
+       KERNEL_UNLOCK_ONE(NULL);
+       splx(s);
+}
+
+static int
+vioconpoll(dev_t dev, int events, struct lwp *l)
+{
+       struct viocon_port *vp = dev2port(dev);
+       struct tty *tp = vp->vp_tty;
+       int revents;
+
+       revents = tp->t_linesw->l_poll(tp, events, l);
+
+       return revents;
+}
+
diff --git a/sys/dev/virtio/virtio_mmio.c b/sys/dev/virtio/virtio_mmio.c
index eb827f528f7..5c3f085577b 100644
--- a/sys/dev/virtio/virtio_mmio.c
+++ b/sys/dev/virtio/virtio_mmio.c
@@ -66,39 +66,8 @@ __KERNEL_RCSID(0, "$NetBSD: virtio_mmio.c,v 1.14 2024/03/09 
11:55:59 isaki Exp $
 #include <sys/device.h>
 #include <sys/mutex.h>

-#define VIRTIO_PRIVATE
 #include <dev/virtio/virtio_mmiovar.h>

-#define VIRTIO_MMIO_MAGIC              ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)
-
-#define VIRTIO_MMIO_MAGIC_VALUE                0x000
-#define VIRTIO_MMIO_VERSION            0x004
-#define VIRTIO_MMIO_DEVICE_ID          0x008
-#define VIRTIO_MMIO_VENDOR_ID          0x00c
-#define VIRTIO_MMIO_DEVICE_FEATURES    0x010   /* "HostFeatures" in v1 */
-#define VIRTIO_MMIO_DEVICE_FEATURES_SEL        0x014   /* "HostFeaturesSel" in 
v1 */
-#define VIRTIO_MMIO_DRIVER_FEATURES    0x020   /* "GuestFeatures" in v1 */
-#define VIRTIO_MMIO_DRIVER_FEATURES_SEL        0x024   /* "GuestFeaturesSel" 
in v1 */
-#define VIRTIO_MMIO_V1_GUEST_PAGE_SIZE 0x028
-#define VIRTIO_MMIO_QUEUE_SEL          0x030
-#define VIRTIO_MMIO_QUEUE_NUM_MAX      0x034
-#define VIRTIO_MMIO_QUEUE_NUM          0x038
-#define VIRTIO_MMIO_V1_QUEUE_ALIGN     0x03c
-#define VIRTIO_MMIO_V1_QUEUE_PFN       0x040
-#define        VIRTIO_MMIO_QUEUE_READY         0x044
-#define VIRTIO_MMIO_QUEUE_NOTIFY       0x050
-#define VIRTIO_MMIO_INTERRUPT_STATUS   0x060
-#define VIRTIO_MMIO_INTERRUPT_ACK      0x064
-#define VIRTIO_MMIO_STATUS             0x070
-#define        VIRTIO_MMIO_V2_QUEUE_DESC_LOW   0x080
-#define        VIRTIO_MMIO_V2_QUEUE_DESC_HIGH  0x084
-#define        VIRTIO_MMIO_V2_QUEUE_AVAIL_LOW  0x090
-#define        VIRTIO_MMIO_V2_QUEUE_AVAIL_HIGH 0x094
-#define        VIRTIO_MMIO_V2_QUEUE_USED_LOW   0x0a0
-#define        VIRTIO_MMIO_V2_QUEUE_USED_HIGH  0x0a4
-#define        VIRTIO_MMIO_V2_CONFIG_GEN       0x0fc
-#define VIRTIO_MMIO_CONFIG             0x100
-
 /*
  * MMIO configuration space for virtio-mmio v1 is in guest byte order.
  *
@@ -128,7 +97,7 @@ static int   virtio_mmio_alloc_interrupts(struct 
virtio_softc *);
 static void    virtio_mmio_free_interrupts(struct virtio_softc *);
 static int     virtio_mmio_setup_interrupts(struct virtio_softc *, int);

-static uint32_t
+uint32_t
 virtio_mmio_reg_read(struct virtio_mmio_softc *sc, bus_addr_t reg)
 {
        uint32_t val;
diff --git a/sys/dev/virtio/virtio_mmiovar.h b/sys/dev/virtio/virtio_mmiovar.h
index c95ec285287..bcd4066b756 100644
--- a/sys/dev/virtio/virtio_mmiovar.h
+++ b/sys/dev/virtio/virtio_mmiovar.h
@@ -28,8 +28,40 @@
 #ifndef _VIRTIO_MMIOVAR_H_
 #define _VIRTIO_MMIOVAR_H_

+#define VIRTIO_PRIVATE
+
 #include <dev/pci/virtiovar.h> /* XXX: move to non-pci */

+#define VIRTIO_MMIO_MAGIC              ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)
+
+#define VIRTIO_MMIO_MAGIC_VALUE                0x000
+#define VIRTIO_MMIO_VERSION            0x004
+#define VIRTIO_MMIO_DEVICE_ID          0x008
+#define VIRTIO_MMIO_VENDOR_ID          0x00c
+#define VIRTIO_MMIO_DEVICE_FEATURES    0x010   /* "HostFeatures" in v1 */
+#define VIRTIO_MMIO_DEVICE_FEATURES_SEL        0x014   /* "HostFeaturesSel" in 
v1 */
+#define VIRTIO_MMIO_DRIVER_FEATURES    0x020   /* "GuestFeatures" in v1 */
+#define VIRTIO_MMIO_DRIVER_FEATURES_SEL        0x024   /* "GuestFeaturesSel" 
in v1 */
+#define VIRTIO_MMIO_V1_GUEST_PAGE_SIZE 0x028
+#define VIRTIO_MMIO_QUEUE_SEL          0x030
+#define VIRTIO_MMIO_QUEUE_NUM_MAX      0x034
+#define VIRTIO_MMIO_QUEUE_NUM          0x038
+#define VIRTIO_MMIO_V1_QUEUE_ALIGN     0x03c
+#define VIRTIO_MMIO_V1_QUEUE_PFN       0x040
+#define        VIRTIO_MMIO_QUEUE_READY         0x044
+#define VIRTIO_MMIO_QUEUE_NOTIFY       0x050
+#define VIRTIO_MMIO_INTERRUPT_STATUS   0x060
+#define VIRTIO_MMIO_INTERRUPT_ACK      0x064
+#define VIRTIO_MMIO_STATUS             0x070
+#define        VIRTIO_MMIO_V2_QUEUE_DESC_LOW   0x080
+#define        VIRTIO_MMIO_V2_QUEUE_DESC_HIGH  0x084
+#define        VIRTIO_MMIO_V2_QUEUE_AVAIL_LOW  0x090
+#define        VIRTIO_MMIO_V2_QUEUE_AVAIL_HIGH 0x094
+#define        VIRTIO_MMIO_V2_QUEUE_USED_LOW   0x0a0
+#define        VIRTIO_MMIO_V2_QUEUE_USED_HIGH  0x0a4
+#define        VIRTIO_MMIO_V2_CONFIG_GEN       0x0fc
+#define VIRTIO_MMIO_CONFIG             0x100
+
 struct virtio_mmio_softc {
        struct virtio_softc     sc_sc;

@@ -46,9 +78,20 @@ struct virtio_mmio_softc {

 };

+struct mmio_args {
+       bus_space_tag_t         bst;
+       uint64_t                sz;
+       paddr_t                 baseaddr;
+       uint64_t                irq;
+       uint64_t                id;
+};
+
+uint32_t virtio_mmio_reg_read(struct virtio_mmio_softc *, bus_addr_t);
 bool virtio_mmio_common_probe_present(struct virtio_mmio_softc *);
 void virtio_mmio_common_attach(struct virtio_mmio_softc *);
 int virtio_mmio_common_detach(struct virtio_mmio_softc *, int);
 int virtio_mmio_intr(void *);

+extern int (*enumerate_mmio_devices)(struct mmio_args *);
+
 #endif /* _VIRTIO_MMIOVAR_H_ */
diff --git a/sys/dev/virtio/virtio_vioconvar.h 
b/sys/dev/virtio/virtio_vioconvar.h
new file mode 100644
index 00000000000..207ac212f35
--- /dev/null
+++ b/sys/dev/virtio/virtio_vioconvar.h
@@ -0,0 +1,7 @@
+
+#ifndef _VIRTIO_VIOCONVAR_H_
+#define _VIRTIO_VIOCONVAR_H_
+
+int viocon_earlyinit(void);
+
+#endif /* _VIRTIO_VIOCONVAR_H_ */
--->8---

------------------------------------------------------------------------
Emile `iMil' Heitor <imil@{home.imil.net,NetBSD.org}> | https://imil.net




Reply via email to