patchset review: full VirtIO console implementation

Emile `iMil' Heitor <[email protected]>
Newsgroups gmane.os.netbsd.devel.kernel,gmane.os.netbsd.ports.x86-64
Message-ID <[email protected]>
Hi,

This series of patches implements the full VirtIO console feature set as
specified in the VirtIO 1.4 specification:
https://docs.oasis-open.org/virtio/virtio/v1.4/virtio-v1.4.html#x1-3980003

[1]: 01_mmio_cmdline_rework.patch

This patch is not directly related to viocon(4). It reworks the VirtIO
MMIO command-line handling to improve early console detection and to
make the overall pv(4) integration cleaner.

[2]: 02_viocon_console.patch

Initial viocon(4) console support, originally written by
Taylor R. Campbell.

[3]: 03_viocon_early_console.patch

Adds early console support to viocon(4), allowing console output before
the full device configuration phase.

[4]: 04_viocon_multiport.patch

Adds multiport support to viocon(4), adapted from initial work by
Jonathan A. Kollasch.

Review and comments appreciated.

---

[1]: https://imil.net/NetBSD/01_mmio_cmdline_rework.patch

5821d8849470675a8a546db6a16ce16ae3dbc60c MMIO cmdline rework
diff --git a/sys/arch/x86/pv/pvbus.c b/sys/arch/x86/pv/pvbus.c
index 85946ea104a8..62020e6bd1a2 100644
--- a/sys/arch/x86/pv/pvbus.c
+++ b/sys/arch/x86/pv/pvbus.c
@@ -44,7 +44,6 @@
  static int _pv_dma_may_bounce(bus_dma_tag_t, bus_dmamap_t, int, int *);
  static int pv_match(device_t, cfdata_t, void *);
  static void pv_attach(device_t, device_t, void *);
-static int pv_submatch(device_t, cfdata_t, const int *, void *);

  struct x86_bus_dma_tag pvbus_bus_dma_tag = {
  	._tag_needs_free	= 0,
@@ -71,7 +70,6 @@ _pv_dma_may_bounce(bus_dma_tag_t t, bus_dmamap_t map, int flags,
  static int
  pv_match(device_t parent, cfdata_t match, void *aux)
  {
-
  	return 1;
  }

@@ -79,6 +77,7 @@ static void
  pv_attach(device_t parent, device_t self, void *aux)
  {
  	struct pv_attach_args pvaa;
+	struct mmio_cmdline_node *mmio_node;

  	pvaa.pvaa_memt = x86_bus_space_mem;
  	pvaa.pvaa_dmat = &pvbus_bus_dma_tag;
@@ -86,17 +85,16 @@ pv_attach(device_t parent, device_t self, void *aux)
  	aprint_naive("\n");
  	aprint_normal("\n");

-	config_found(self, &pvaa, NULL, CFARGS(.search = pv_submatch));
-}
+	pvaa.mmio_node = NULL;

-static int
-pv_submatch(device_t parent, cfdata_t cf, const int *ldesc, void *aux)
-{
-	struct pv_attach_args *pvaa = aux;
+	/* Non MMIO devices */
+	while (config_found(self, &pvaa, NULL, CFARGS_NONE) != NULL)
+		continue;
+
+	virtio_mmio_cmdline_parse();

-	if (config_probe(parent, cf, pvaa)) {
-		config_attach(parent, cf, pvaa, NULL, CFARGS_NONE);
-		return 0;
+	SLIST_FOREACH(mmio_node, &virtio_mmio_cmdline_devs, n_nodes) {
+		pvaa.mmio_node = mmio_node;
+		config_found(self, &pvaa, NULL, CFARGS_NONE);
  	}
-	return 0;
  }
diff --git a/sys/arch/x86/pv/pvvar.h b/sys/arch/x86/pv/pvvar.h
index 607f6f0a2ffa..414ce5b7c63f 100644
--- a/sys/arch/x86/pv/pvvar.h
+++ b/sys/arch/x86/pv/pvvar.h
@@ -32,6 +32,8 @@
  #ifndef _PVBUS_PVVAR_H_
  #define _PVBUS_PVVAR_H_

+#include <dev/virtio/virtio_mmiovar.h>
+
  struct pv_softc {
  	device_t		sc_dev;
  };
@@ -41,8 +43,9 @@ struct pvbus_attach_args {
  };

  struct pv_attach_args {
-	bus_space_tag_t		pvaa_memt;
-	bus_dma_tag_t		pvaa_dmat;
+	bus_space_tag_t			pvaa_memt;
+	bus_dma_tag_t			pvaa_dmat;
+	struct mmio_cmdline_node	*mmio_node;
  };

  #endif
diff --git a/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c b/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
index 18a0ce9f8bc3..92aca13eced4 100644
--- a/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
+++ b/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
@@ -58,6 +58,7 @@
  #include <sys/bus.h>
  #include <sys/device.h>
  #include <sys/kernel.h>
+#include <sys/kmem.h>
  #include <sys/module.h>
  #include <sys/systm.h>

@@ -65,28 +66,24 @@
  #include <dev/virtio/virtio_mmiovar.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;
  };

+struct mmio_cmdline_devs_head virtio_mmio_cmdline_devs = 
+    SLIST_HEAD_INITIALIZER(virtio_mmio_cmdline_devs);
+
  static int	virtio_mmio_cmdline_match(device_t, cfdata_t, void *);
  static void	virtio_mmio_cmdline_attach(device_t, device_t, void *);
-static int	virtio_mmio_cmdline_do_attach(device_t,
-		    struct pv_attach_args *, struct mmio_args *);
  static int	virtio_mmio_cmdline_detach(device_t, int);
  static int	virtio_mmio_cmdline_rescan(device_t, const char *, const int *);
  static int	virtio_mmio_cmdline_alloc_interrupts(struct virtio_mmio_softc *);
@@ -98,20 +95,14 @@ CFATTACH_DECL3_NEW(mmio_cmdline,
      virtio_mmio_cmdline_detach, NULL,
      virtio_mmio_cmdline_rescan, NULL, 0);

-static int
-virtio_mmio_cmdline_match(device_t parent, cfdata_t match, void *aux)
-{
-	if (strstr(xen_start_info.cmd_line, VMMIOSTR) == NULL)
-		return 0;
-
-	return 1;
-}

-static void
+static int
  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))
@@ -180,88 +171,96 @@ parsearg(struct mmio_args *margs, const char *arg)
  	if (*p)
  		goto bad;

-	return;
+	return 0;

  bad:
  	aprint_error("Error parsing virtio_mmio parameter: %s\n", arg);
+	return -1;
  }

-static void
-virtio_mmio_cmdline_attach(device_t parent, device_t self, void *aux)
+void
+virtio_mmio_cmdline_parse(void)
  {
-	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;
+	char tmpcmdline[sizeof(xen_start_info.cmd_line)];
+	struct mmio_args margs;
+	struct mmio_cmdline_node *mmio_node;
+	static bool mmio_cmdline_parsed = false;

-	aprint_normal("\n");
-	aprint_naive("\n");
+	if (mmio_cmdline_parsed)
+		return;

-	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);
-	}
+	mmio_cmdline_parsed = true;

-	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);
-		}
+	strlcpy(tmpcmdline, xen_start_info.cmd_line, sizeof(tmpcmdline));

-		aprint_normal_dev(self, "viommio: %s\n", parg);
-		parsearg(margs, parg);
+	for (char *p = strstr(tmpcmdline, VMMIOSTR);
+	    (p = strstr(p, VMMIOSTR)) != NULL && strlen(p) > keylen;) {
+		char *end = strchr(p, ' ');
+		p += keylen;

-		if (virtio_mmio_cmdline_do_attach(self, pvaa, margs))
-			return;
+		if (end)
+			while (*end == ' ')
+				*end++ = 0;

-		if (next) {
-			parg = next;
-			config_found(parent, pvaa, NULL, CFARGS_NONE);
+		if (parsearg(&margs, p) == 0) {
+			mmio_node = kmem_zalloc(sizeof(*mmio_node), KM_SLEEP);
+			mmio_node->margs = margs;
+
+			SLIST_INSERT_HEAD(&virtio_mmio_cmdline_devs,
+			    mmio_node, n_nodes);
  		}
+
+		if (end)
+			p = end;
  	}
  }

  static int
-virtio_mmio_cmdline_do_attach(device_t self,
-    struct pv_attach_args *pvaa,
-    struct mmio_args *margs)
+virtio_mmio_cmdline_match(device_t parent, cfdata_t match, void *aux)
+{
+	struct pv_attach_args *pvaa = aux;
+
+	if (pvaa->mmio_node != NULL)
+		return 1;
+
+	return 0;
+}
+
+static void
+virtio_mmio_cmdline_attach(device_t parent, device_t self, void *aux)
  {
+	struct pv_attach_args *pvaa = aux;
+	struct mmio_cmdline_node *mmio_node = pvaa->mmio_node;
  	struct virtio_mmio_cmdline_softc *sc = device_private(self);
  	struct virtio_mmio_softc *const msc = &sc->sc_msc;
  	struct virtio_softc *const vsc = &msc->sc_sc;
  	int error;

+	aprint_normal("\n");
+	aprint_naive("\n");
+
+	aprint_normal_dev(self, "viommio: @%#" PRIxPADDR "\n",
+	    mmio_node->margs.baseaddr);
+
  	msc->sc_iot = pvaa->pvaa_memt;
  	vsc->sc_dmat = pvaa->pvaa_dmat;
-	msc->sc_iosize = margs->sz;
+	msc->sc_iosize = mmio_node->margs.sz;
  	vsc->sc_dev = self;
+	sc->margs = mmio_node->margs;

-	error = bus_space_map(msc->sc_iot, margs->baseaddr, margs->sz, 0,
-	    &msc->sc_ioh);
+	error = bus_space_map(msc->sc_iot, mmio_node->margs.baseaddr,
+	    mmio_node->margs.sz, 0, &msc->sc_ioh);
  	if (error) {
-		aprint_error_dev(self, "couldn't map %#" PRIx64 ": %d",
-		    margs->baseaddr, error);
-		return error;
+		aprint_error_dev(self, "couldn't map %#" PRIxPADDR ": %d",
+		    mmio_node->margs.baseaddr, error);
+		return;
  	}
-
  	msc->sc_alloc_interrupts = virtio_mmio_cmdline_alloc_interrupts;
  	msc->sc_free_interrupts = virtio_mmio_cmdline_free_interrupts;

  	virtio_mmio_common_attach(msc);
  	virtio_mmio_cmdline_rescan(self, "virtio", NULL);
-
-	return 0;
  }

  static int
diff --git a/sys/dev/virtio/virtio_mmio.c b/sys/dev/virtio/virtio_mmio.c
index ba9307b198e0..97c17a5a3814 100644
--- a/sys/dev/virtio/virtio_mmio.c
+++ b/sys/dev/virtio/virtio_mmio.c
@@ -66,39 +66,9 @@ __KERNEL_RCSID(0, "$NetBSD: virtio_mmio.c,v 1.15 2025/07/26 14:18:14 martin Exp
  #include <sys/device.h>
  #include <sys/mutex.h>

-#define VIRTIO_PRIVATE
+#include <dev/virtio/virtio_mmioreg.h>
  #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.
   *
@@ -116,7 +86,6 @@ __KERNEL_RCSID(0, "$NetBSD: virtio_mmio.c,v 1.15 2025/07/26 14:18:14 martin Exp
  #	define STRUCT_ENDIAN	LITTLE_ENDIAN
  #endif

-
  static void	virtio_mmio_kick(struct virtio_softc *, uint16_t);
  static uint16_t	virtio_mmio_read_queue_size(struct virtio_softc *, uint16_t);
  static void	virtio_mmio_v1_setup_queue(struct virtio_softc *, uint16_t, uint64_t);
@@ -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_mmioreg.h b/sys/dev/virtio/virtio_mmioreg.h
new file mode 100644
index 000000000000..b0ccfa71e330
--- /dev/null
+++ b/sys/dev/virtio/virtio_mmioreg.h
@@ -0,0 +1,64 @@
+/* $NetBSD$ */
+/*
+ * Copyright (c) 2018 Jonathan A. Kollasch
+ * All rights reserved.
+ *
+ * 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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_MMIOREG_H_
+#define _VIRTIO_MMIOREG_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
+
+/* VirtIO Console MMIO register offsets */
+#define VIRTIO_CONSOLE_EMERG_WR_OFFSET	8	/* from VIRTIO_MMIO_CONFIG */
+
+#endif /* _VIRTIO_MMIOREG_H_ */
diff --git a/sys/dev/virtio/virtio_mmiovar.h b/sys/dev/virtio/virtio_mmiovar.h
index c95ec285287b..14465a1e2161 100644
--- a/sys/dev/virtio/virtio_mmiovar.h
+++ b/sys/dev/virtio/virtio_mmiovar.h
@@ -28,6 +28,8 @@
  #ifndef _VIRTIO_MMIOVAR_H_
  #define _VIRTIO_MMIOVAR_H_

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

  struct virtio_mmio_softc {
@@ -46,9 +48,28 @@ struct virtio_mmio_softc {

  };

+struct mmio_args {
+	bus_space_tag_t		bst;
+	uint64_t		sz;
+	paddr_t			baseaddr;
+	uint64_t		irq;
+	uint64_t		id;
+};
+
+struct mmio_cmdline_node {
+	struct mmio_args		margs;
+
+	SLIST_ENTRY(mmio_cmdline_node)	n_nodes;
+};
+
+SLIST_HEAD(mmio_cmdline_devs_head, mmio_cmdline_node);
+extern struct mmio_cmdline_devs_head virtio_mmio_cmdline_devs;
+
+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 *);
+void virtio_mmio_cmdline_parse(void);

  #endif /* _VIRTIO_MMIOVAR_H_ */

[2]: https://imil.net/NetBSD/02_viocon_console.patch

90756aaf222dee37ee4988dca5bae412746c8122 viocon(4) console support by Riastradh
diff --git a/share/man/man4/viocon.4 b/share/man/man4/viocon.4
index 26f2bd6c26de..4cdc1872d2c6 100644
--- a/share/man/man4/viocon.4
+++ b/share/man/man4/viocon.4
@@ -58,9 +58,7 @@ 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
diff --git a/sys/dev/virtio/viocon.c b/sys/dev/virtio/viocon.c
index 73c938936be1..e19ed91a892c 100644
--- a/sys/dev/virtio/viocon.c
+++ b/sys/dev/virtio/viocon.c
@@ -17,6 +17,8 @@
   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   */

+#define	VIOCON_CONSOLE
+
  #include <sys/cdefs.h>
  __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 riastradh Exp $");

@@ -33,6 +35,10 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 riastradh Exp $"
  #include <sys/systm.h>
  #include <sys/tty.h>

+#ifdef VIOCON_CONSOLE
+#include <dev/cons.h>
+#endif
+
  #include <dev/pci/virtioreg.h>
  #include <dev/pci/virtiovar.h>

@@ -117,6 +123,13 @@ struct viocon_port {
  	uint16_t		 vp_cols;
  	u_char			*vp_rx_buf;
  	u_char			*vp_tx_buf;
+
+#ifdef VIOCON_CONSOLE
+	struct consdev		 vp_cntab;
+	unsigned int		 vp_pollpos;
+	unsigned int		 vp_polllen;
+	bool			 vp_polling;
+#endif
  };

  struct viocon_softc {
@@ -153,6 +166,12 @@ void	vioconstop(struct tty *, int);
  int	vioconioctl(dev_t, u_long, void *, int, struct lwp *);
  struct tty	*viocontty(dev_t dev);

+#ifdef VIOCON_CONSOLE
+static void viocon_cnpollc(dev_t, int);
+static int viocon_cngetc(dev_t);
+static void viocon_cnputc(dev_t, int);
+#endif
+
  CFATTACH_DECL_NEW(viocon, sizeof(struct viocon_softc),
      viocon_match, viocon_attach, /*detach*/NULL, /*activate*/NULL);

@@ -229,6 +248,20 @@ viocon_attach(struct device *parent, struct device *self, void *aux)

  	viocon_rx_fill(sc->sc_ports[0]);

+#ifdef VIOCON_CONSOLE
+	if (cn_tab == NULL || cn_tab->cn_dev == NODEV) {
+		sc->sc_ports[0]->vp_cntab = (struct consdev) {
+			.cn_pollc = viocon_cnpollc,
+			.cn_getc = viocon_cngetc,
+			.cn_putc = viocon_cnputc,
+			.cn_dev = VIOCONDEV(device_unit(self), 0),
+			.cn_pri = CN_REMOTE,
+		};
+		aprint_normal_dev(sc->sc_dev, "console\n");
+		cn_tab = &sc->sc_ports[0]->vp_cntab;
+	}
+#endif
+
  	return;
  err:
  	kmem_free(sc->sc_vqs, nvqs * sizeof(sc->sc_vqs[0]));
@@ -634,3 +667,77 @@ vioconioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
  		return error2;
  	return ENOTTY;
  }
+
+#ifdef VIOCON_CONSOLE
+
+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);
+}
+
+#endif

[3]: https://imil.net/NetBSD/03_viocon_early_console.patch

59991624d5d9ac95c555b895b5fc6a51fc9a1d91 viocon(4) early console support
diff --git a/share/man/man4/viocon.4 b/share/man/man4/viocon.4
index 4cdc1872d2c6..1498fdba58bb 100644
--- a/share/man/man4/viocon.4
+++ b/share/man/man4/viocon.4
@@ -62,3 +62,5 @@ It was ported to
  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] .
diff --git a/sys/arch/x86/x86/consinit.c b/sys/arch/x86/x86/consinit.c
index b300fb4aa4e8..f24f80d5c846 100644
--- a/sys/arch/x86/x86/consinit.c
+++ b/sys/arch/x86/x86/consinit.c
@@ -109,6 +109,17 @@ __KERNEL_RCSID(0, "$NetBSD: consinit.c,v 1.44 2025/11/29 01:33:40 manu Exp $");
  #include <xen/xen.h>
  #endif

+#include "viocon.h"
+#include "virtio_mmio.h"
+#if (NVIOCON > 0) && (NVIRTIO_MMIO > 0)
+#include <dev/virtio/virtio_vioconvar.h>
+#include <dev/virtio/virtio_mmioreg.h>
+#include <dev/virtio/virtio_mmiovar.h>
+#include <uvm/uvm_extern.h> /* for kernel_map */
+#endif
+
+#include "pv.h"
+
  #ifndef CONSDEVNAME
  #define CONSDEVNAME "pc"
  #endif
@@ -159,6 +170,40 @@ int comkgdbmode = KGDB_DEVMODE;

  #endif /* KGDB */

+/* XXX only on VirtIO MMIO on pvbus for now, MMIO on ACPI could be added */
+#if (NVIOCON > 0) && (NVIRTIO_MMIO > 0)
+static int
+viocon_early_probe(void)
+{
+	struct mmio_cmdline_node *mmio_node;
+
+	virtio_mmio_cmdline_parse();
+
+	SLIST_FOREACH(mmio_node, &virtio_mmio_cmdline_devs, n_nodes) {
+		bus_space_handle_t bsh;
+
+		if (_x86_memio_map(mmio_node->margs.bst,
+		    mmio_node->margs.baseaddr, mmio_node->margs.sz, 0, &bsh) != 0) {
+			aprint_error("%s: failed to map MMIO region at "
+			    "%#" PRIxPADDR "\n", __func__,\
+			    mmio_node->margs.baseaddr);
+			continue;
+		}
+
+		aprint_verbose("mmio addr:%#" PRIxPADDR "\n",
+		    mmio_node->margs.baseaddr);
+
+		if (viocon_earlyinit(mmio_node->margs.bst, bsh) == 0)
+			return 0;
+
+		_x86_memio_unmap(mmio_node->margs.bst, bsh, mmio_node->margs.sz, NULL);
+	}
+
+	aprint_error("%s: no virtio console found\n", __func__);
+	return -1;
+}
+#endif
+
  /*
   * consinit:
   * initialize the system console.
@@ -268,6 +313,19 @@ dokbd:
  			       error);
  		return;
  	}
+#if (NVIOCON > 0) && (NVIRTIO_MMIO > 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;
+		}
+#if NPV > 0
+		if (viocon_early_probe() == 0)
+			return;
+#endif
+	}
+#endif
  #if (NCOM > 0)
  	if (!strcmp(console_devname, "com")) {
  		int addr = consinfo->addr;
diff --git a/sys/dev/virtio/files.virtio b/sys/dev/virtio/files.virtio
index 4e399624ab0b..8d3d8def6ea3 100644
--- a/sys/dev/virtio/files.virtio
+++ b/sys/dev/virtio/files.virtio
@@ -3,8 +3,8 @@
  # XXX the contents of the following included file should be moved here
  include "dev/pci/files.virtio"

-file	dev/virtio/virtio_mmio.c	virtio_mmio
+file	dev/virtio/virtio_mmio.c	virtio_mmio	needs-flag

  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 e19ed91a892c..e452dd9780e3 100644
--- a/sys/dev/virtio/viocon.c
+++ b/sys/dev/virtio/viocon.c
@@ -17,8 +17,6 @@
   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   */

-#define	VIOCON_CONSOLE
-
  #include <sys/cdefs.h>
  __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 riastradh Exp $");

@@ -35,10 +33,11 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 riastradh Exp $"
  #include <sys/systm.h>
  #include <sys/tty.h>

-#ifdef VIOCON_CONSOLE
  #include <dev/cons.h>
-#endif
+#include <dev/virtio/virtio_vioconvar.h>
+#include <dev/virtio/virtio_mmioreg.h>

+#define VIRTIO_PRIVATE
  #include <dev/pci/virtioreg.h>
  #include <dev/pci/virtiovar.h>

@@ -124,12 +123,10 @@ struct viocon_port {
  	u_char			*vp_rx_buf;
  	u_char			*vp_tx_buf;

-#ifdef VIOCON_CONSOLE
  	struct consdev		 vp_cntab;
  	unsigned int		 vp_pollpos;
  	unsigned int		 vp_polllen;
  	bool			 vp_polling;
-#endif
  };

  struct viocon_softc {
@@ -166,11 +163,10 @@ void	vioconstop(struct tty *, int);
  int	vioconioctl(dev_t, u_long, void *, int, struct lwp *);
  struct tty	*viocontty(dev_t dev);

-#ifdef VIOCON_CONSOLE
  static void viocon_cnpollc(dev_t, int);
  static int viocon_cngetc(dev_t);
  static void viocon_cnputc(dev_t, int);
-#endif
+static void viocon_early_putc(dev_t, int);

  CFATTACH_DECL_NEW(viocon, sizeof(struct viocon_softc),
      viocon_match, viocon_attach, /*detach*/NULL, /*activate*/NULL);
@@ -202,7 +198,65 @@ dev2port(dev_t dev)
  	return dev2sc(dev)->sc_ports[VIOCONPORT(dev)];
  }

-int viocon_match(struct device *parent, struct cfdata *match, void *aux)
+static struct {
+	bus_space_tag_t		ec_bst;
+	bus_space_handle_t	ec_bsh;
+	bus_addr_t		ec_reg_offset;
+} early_console;
+
+static void
+viocon_early_putc(dev_t dev, int c)
+{
+	bus_space_write_4(early_console.ec_bst, early_console.ec_bsh,
+	    early_console.ec_reg_offset, c);
+}
+
+int
+viocon_earlyinit(bus_space_tag_t bst, bus_space_handle_t bsh)
+{
+	static struct consdev viocon_early_consdev = {
+	    .cn_putc = viocon_early_putc,
+	    .cn_getc = NULL,
+	    .cn_pollc = NULL,
+	    .cn_dev = NODEV,
+	    .cn_pri = CN_NORMAL
+	};
+
+	if (bus_space_read_4(bst, bsh, VIRTIO_MMIO_MAGIC_VALUE) !=
+	    VIRTIO_MMIO_MAGIC) {
+		return -1;
+	}
+
+	if (bus_space_read_4(bst, bsh, VIRTIO_MMIO_DEVICE_ID) !=
+	    VIRTIO_DEVICE_ID_CONSOLE) {
+		return -1;
+	}
+
+	if (!(bus_space_read_4(bst, bsh, VIRTIO_MMIO_DEVICE_FEATURES) &
+	    VIRTIO_CONSOLE_F_EMERG_WRITE)) {
+		return -1;
+	}
+
+	/*
+	 * https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.html#x1-3250004
+	 * emerg_wr is 32 bits long
+	 */
+	if (bus_space_subregion(bst, bsh,
+	    VIRTIO_MMIO_CONFIG + VIRTIO_CONSOLE_EMERG_WR_OFFSET,
+	    sizeof(uint32_t), &early_console.ec_bsh) != 0) {
+		return -1;
+	}
+
+	early_console.ec_bst = bst;
+	early_console.ec_reg_offset = 0; /* already adjusted by subregion */
+
+	cn_tab = &viocon_early_consdev;
+
+	return 0;
+}
+
+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)
@@ -248,7 +302,6 @@ viocon_attach(struct device *parent, struct device *self, void *aux)

  	viocon_rx_fill(sc->sc_ports[0]);

-#ifdef VIOCON_CONSOLE
  	if (cn_tab == NULL || cn_tab->cn_dev == NODEV) {
  		sc->sc_ports[0]->vp_cntab = (struct consdev) {
  			.cn_pollc = viocon_cnpollc,
@@ -260,7 +313,6 @@ viocon_attach(struct device *parent, struct device *self, void *aux)
  		aprint_normal_dev(sc->sc_dev, "console\n");
  		cn_tab = &sc->sc_ports[0]->vp_cntab;
  	}
-#endif

  	return;
  err:
@@ -668,8 +720,6 @@ vioconioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
  	return ENOTTY;
  }

-#ifdef VIOCON_CONSOLE
-
  static void
  viocon_cnpollc(dev_t dev, int on)
  {
@@ -739,5 +789,3 @@ viocon_cnputc(dev_t dev, int c)
  	KERNEL_UNLOCK_ONE(NULL);
  	splx(s);
  }
-
-#endif
diff --git a/sys/dev/virtio/virtio_vioconvar.h b/sys/dev/virtio/virtio_vioconvar.h
new file mode 100644
index 000000000000..6d75186ff0b1
--- /dev/null
+++ b/sys/dev/virtio/virtio_vioconvar.h
@@ -0,0 +1,9 @@
+
+#ifndef _VIRTIO_VIOCONVAR_H_
+#define _VIRTIO_VIOCONVAR_H_
+
+#include <sys/bus.h>
+
+int viocon_earlyinit(bus_space_tag_t, bus_space_handle_t);
+
+#endif /* _VIRTIO_VIOCONVAR_H_ */

[4]: https://imil.net/NetBSD/04_viocon_multiport.patch

d3660f82a40da23fbcd88acb878adaf25ce5a06f viocon(4) multiport support
diff --git a/share/man/man4/viocon.4 b/share/man/man4/viocon.4
index 1498fdba58bb..12dee3068596 100644
--- a/share/man/man4/viocon.4
+++ b/share/man/man4/viocon.4
@@ -64,3 +64,6 @@ Kernel support was implemented by
  .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/dev/virtio/viocon.c b/sys/dev/virtio/viocon.c
index e452dd9780e3..df85031aeed3 100644
--- a/sys/dev/virtio/viocon.c
+++ b/sys/dev/virtio/viocon.c
@@ -32,6 +32,8 @@ __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 <dev/virtio/virtio_vioconvar.h>
@@ -41,6 +43,8 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 riastradh Exp $"
  #include <dev/pci/virtioreg.h>
  #include <dev/pci/virtiovar.h>

+#include <prop/proplib.h>
+
  #include "ioconf.h"

  /* OpenBSD compat shims */
@@ -54,6 +58,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 */
@@ -74,12 +80,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
@@ -88,7 +97,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 */
@@ -98,10 +107,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;
@@ -109,14 +118,14 @@ 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;
@@ -142,10 +151,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 *);
@@ -161,8 +186,16 @@ 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);
@@ -179,7 +212,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,
@@ -198,6 +231,12 @@ 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 struct {
  	bus_space_tag_t		ec_bst;
  	bus_space_handle_t	ec_bsh;
@@ -269,6 +308,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;

@@ -278,40 +319,61 @@ 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 (cn_tab == NULL || cn_tab->cn_dev == NODEV) {
-		sc->sc_ports[0]->vp_cntab = (struct consdev) {
-			.cn_pollc = viocon_cnpollc,
-			.cn_getc = viocon_cngetc,
-			.cn_putc = viocon_cnputc,
-			.cn_dev = VIOCONDEV(device_unit(self), 0),
-			.cn_pri = CN_REMOTE,
-		};
-		aprint_normal_dev(sc->sc_dev, "console\n");
-		cn_tab = &sc->sc_ports[0]->vp_cntab;
+	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;
@@ -321,48 +383,271 @@ 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);
+
+		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);
+			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, strlen(vp->vp_name) + 1);
+				vp->vp_name = kmem_zalloc(namelen + 1, 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);
+
+	if (bus_dmamap_load(virtio_dmat(vsc), sc->sc_dmamap_tx, sc->sc_tx_buf,
+	    8, NULL, BUS_DMA_NOWAIT|BUS_DMA_WRITE) != 0)
+		goto err;
+	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;

@@ -399,16 +684,60 @@ 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;
+}
+
  int
  viocon_tx_drain(struct viocon_port *vp, struct virtqueue *vq)
  {
@@ -431,17 +760,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;
  }
@@ -473,7 +804,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);
@@ -499,6 +830,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;
@@ -529,10 +863,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) {
@@ -551,6 +891,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);
@@ -611,9 +952,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))
@@ -638,7 +978,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;
  }

@@ -654,13 +1005,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;
  }

@@ -789,3 +1145,15 @@ viocon_cnputc(dev_t dev, int c)
  	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;
+}


------------------------------------------------------------------------
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.