Re: patch review: VirtIO early console (+ multiport) support

Emile `iMil' Heitor <[email protected]>
Newsgroups gmane.os.netbsd.devel.kernel
Message-ID <[email protected]>
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 [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.
+Kernel support was implemented by
+.An Taylor R. Campbell Aq Mt [email protected] .
+.Pp
+Early console support was implemented by
+.An Emile So iMil Sc Heitor Aq Mt [email protected] .
+.Pp
+Multiport support was implemented by
+.An  Jonathan A. Kollasch Aq Mt [email protected] .
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.