tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

patchset review: full VirtIO console implementation




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 sf%sfritsch.de@localhost .
 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 riastradh%NetBSD.org@localhost .
+.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 riastradh%NetBSD.org@localhost .
 .Pp
+Early console support was implemented by
+.An Emile So iMil Sc Heitor Aq Mt imil%NetBSD.org@localhost .
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 imil%NetBSD.org@localhost .
+.Pp
+Multiport support was implemented by
+.An  Jonathan A. Kollasch Aq Mt jakllsch%NetBSD.org@localhost .
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



Home | Main Index | Thread Index | Old Index