Re: patch review: VirtIO early console + multiport support

Emile `iMil' Heitor <[email protected]>
Newsgroups gmane.os.netbsd.devel.kernel
Message-ID <[email protected]>
Here's what I believe to be the final form of this patch.
If there are no objections, I plan to commit it next week, as I'll
be mostly AFK until then.

https://imil.net/NetBSD/viocon_multiport.patch

Inlined:

8725513c83a81fc06b61d17c197af7ea868d9c2a full viocon support
diff --git a/share/man/man4/viocon.4 b/share/man/man4/viocon.4
index 26f2bd6c26d..12dee306859 100644
--- a/share/man/man4/viocon.4
+++ b/share/man/man4/viocon.4
@@ -58,9 +58,12 @@ by
  .An Stefan Fritsch Aq Mt [email protected] .
  It was ported to
  .Nx 10.0 .
-.Sh BUGS
-Use as a kernel console for
-.Nx
-is not yet supported.
  .Pp
-The multiport feature is not yet supported.
+Kernel support was implemented by
+.An Taylor R. Campbell Aq Mt [email protected] .
+.Pp
+Early console support was implemented by
+.An Emile So iMil Sc Heitor Aq Mt [email protected] .
+.Pp
+Multiport support was implemented by
+.An  Jonathan A. Kollasch Aq Mt [email protected] .
diff --git a/sys/arch/x86/x86/consinit.c b/sys/arch/x86/x86/consinit.c
index 9ef384c2c07..55546095136 100644
--- a/sys/arch/x86/x86/consinit.c
+++ b/sys/arch/x86/x86/consinit.c
@@ -104,6 +104,14 @@ __KERNEL_RCSID(0, "$NetBSD: consinit.c,v 1.41 2025/04/30 05:15:08 imil Exp $");
  #include <xen/xen.h>
  #endif

+#include "viocon.h"
+#include "virtio_mmio.h"
+/* XXX only on pvbus / VirtIO MMIO for now */
+#if (NVIOCON > 0) && (NVIRTIO_MMIO > 0)
+#include <dev/virtio/virtio_vioconvar.h>
+#include <uvm/uvm_extern.h> /* for kernel_map */
+#endif
+
  #ifndef CONSDEVNAME
  #define CONSDEVNAME "pc"
  #endif
@@ -263,6 +271,17 @@ 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 (viocon_earlyinit() == 0)
+			return;
+	}
+#endif
  #if (NCOM > 0)
  	if (!strcmp(console_devname, "com")) {
  		int addr = consinfo->addr;
diff --git a/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c b/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
index 18a0ce9f8bc..b55c5a03ae4 100644
--- a/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
+++ b/sys/dev/virtio/arch/x86/virtio_mmio_cmdline.c
@@ -63,21 +63,17 @@

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

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

  #define VMMIOSTR "virtio_mmio.device="

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

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

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

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

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

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

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

diff --git a/sys/dev/virtio/files.virtio b/sys/dev/virtio/files.virtio
index 4e399624ab0..8d3d8def6ea 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 73c938936be..d402440f9f7 100644
--- a/sys/dev/virtio/viocon.c
+++ b/sys/dev/virtio/viocon.c
@@ -32,11 +32,24 @@ __KERNEL_RCSID(0, "$NetBSD: viocon.c,v 1.10 2024/08/05 19:13:34 riastradh Exp $"
  #include <sys/lwp.h>
  #include <sys/systm.h>
  #include <sys/tty.h>
+#include <sys/fcntl.h>
+#include <sys/poll.h>
+
+#include <dev/cons.h>
+#include <uvm/uvm_extern.h>
+#include <dev/virtio/virtio_vioconvar.h>
+#include <dev/virtio/virtio_mmiovar.h>

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

  #include "ioconf.h"
+#include "virtio_mmio.h"
+#include "pv.h"
+#if NPV > 0
+#include <dev/virtio/arch/x86/virtio_mmio_parse.h>
+#endif

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

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

+/* The control virtqueues are after the first port */
+#define VIOCON_CTRL_RX_IDX 2
+#define VIOCON_CTRL_TX_IDX 3
  struct virtio_console_control {
  	uint32_t id;	/* Port number */

  #define	VIRTIO_CONSOLE_DEVICE_READY	0
-#define	VIRTIO_CONSOLE_PORT_ADD		1
-#define	VIRTIO_CONSOLE_PORT_REMOVE	2
+#define	VIRTIO_CONSOLE_DEVICE_ADD	1
+#define	VIRTIO_CONSOLE_DEVICE_REMOVE	2
  #define	VIRTIO_CONSOLE_PORT_READY	3
  #define	VIRTIO_CONSOLE_CONSOLE_PORT	4
  #define	VIRTIO_CONSOLE_RESIZE		5
@@ -83,7 +101,7 @@ struct virtio_console_control {
  	uint16_t event;

  	uint16_t value;
-};
+} __packed;

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

  #define	BUFSIZE		128

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

  struct viocon_port {
  	struct viocon_softc	*vp_sc;
@@ -104,19 +122,24 @@ struct viocon_port {
  	struct virtqueue	*vp_tx;
  	void			*vp_si;
  	struct tty		*vp_tty;
-	const char 		*vp_name;
+	char 			*vp_name;
  	bus_dma_segment_t	 vp_dmaseg;
  	bus_dmamap_t		 vp_dmamap;
-#ifdef NOTYET
-	unsigned int		 vp_host_open:1;	/* XXX needs F_MULTIPORT */
-	unsigned int		 vp_guest_open:1;	/* XXX needs F_MULTIPORT */
-	unsigned int		 vp_is_console:1;	/* XXX needs F_MULTIPORT */
-#endif
+
+	unsigned int		 vp_host_open:1;
+	unsigned int		 vp_guest_open:1;
+	unsigned int		 vp_is_console:1;
+
  	unsigned int		 vp_iflow:1;		/* rx flow control */
  	uint16_t		 vp_rows;
  	uint16_t		 vp_cols;
  	u_char			*vp_rx_buf;
  	u_char			*vp_tx_buf;
+
+	struct consdev		 vp_cntab;
+	unsigned int		 vp_pollpos;
+	unsigned int		 vp_polllen;
+	bool			 vp_polling;
  };

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

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

  int	viocon_match(struct device *, struct cfdata *, void *);
  void	viocon_attach(struct device *, struct device *, void *);
+int	viocon_control_rx_intr(struct virtqueue *);
+int	viocon_control_tx_intr(struct virtqueue *);
  int	viocon_tx_intr(struct virtqueue *);
  int	viocon_tx_drain(struct viocon_port *, struct virtqueue *vq);
  int	viocon_rx_intr(struct virtqueue *);
@@ -151,7 +190,28 @@ 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);
+#if NVIRTIO_MMIO > 0
+static void free_viocon_mmio_vaddr(void);;
+static void viocon_early_putc(dev_t, int);
+
+static uint32_t *early_console;
+static vaddr_t viocon_mmio_vaddr = 0;
+
+int (*enumerate_mmio_devices)(struct mmio_args *) = NULL;
+#endif

  CFATTACH_DECL_NEW(viocon, sizeof(struct viocon_softc),
      viocon_match, viocon_attach, /*detach*/NULL, /*activate*/NULL);
@@ -164,7 +224,7 @@ const struct cdevsw viocon_cdevsw = {
  	.d_ioctl = vioconioctl,
  	.d_stop = vioconstop,
  	.d_tty = viocontty,
-	.d_poll = nopoll,	/* XXX */
+	.d_poll = vioconpoll,
  	.d_mmap = nommap,
  	.d_kqfilter = ttykqfilter,
  	.d_discard = nodiscard,
@@ -182,8 +242,110 @@ 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;
+}
+/* XXX only on pvbus / VirtIO MMIO for now */
+#if NVIRTIO_MMIO > 0
+static void
+free_viocon_mmio_vaddr(void)
+{
+	if (!viocon_mmio_vaddr)
+		return;

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

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

  	sc->sc_vqs = kmem_zalloc(nvqs * sizeof(sc->sc_vqs[0]),
  	    KM_SLEEP);
  	sc->sc_ports = kmem_zalloc(maxports * sizeof(sc->sc_ports[0]),
  	    KM_SLEEP);

-	virtio_child_attach_start(vsc, self, IPL_TTY,
-	    /*req_features*/VIRTIO_CONSOLE_F_SIZE, VIRTIO_CONSOLE_FLAG_BITS);
-
-	DPRINTF("%s: softc: %p\n", __func__, sc);
-	if (viocon_port_create(sc, 0) != 0) {
-		printf("\n%s: viocon_port_create failed\n", __func__);
+	if (viocon_ports_vq_alloc(sc, maxports) != 0)
  		goto err;
+
+	if (!sc->has_multiport) {
+		DPRINTF("%s: softc: %p\n", __func__, sc);
+		/* virtqueue index handled in port creation */
+		if (viocon_port_create(sc, 0) != 0) {
+			printf("\n%s: viocon_port_create failed\n", __func__);
+			goto err;
+		}
+		viocon_console(sc, 0);
  	}

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

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

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

-int
-viocon_port_create(struct viocon_softc *sc, int portidx)
+static void
+viocon_control_dmamap(struct viocon_softc *sc)
  {
  	struct virtio_softc *vsc = sc->sc_virtio;
-	int rxidx, txidx, allocsize, nsegs;
+	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, i;
  	char name[6];
  	struct viocon_port *vp;
-	void *kva;
-	struct tty *tp;

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

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

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

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

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

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

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

  	return 1;
  }
@@ -388,7 +864,7 @@ viocon_rx_intr(struct virtqueue *vq)
  {
  	struct virtio_softc *vsc = vq->vq_owner;
  	struct viocon_softc *sc = device_private(virtio_child(vsc));
-	int portidx = (vq->vq_index - 1) / 2;
+	int portidx = viocon_vqidx2portidx(vq->vq_index);
  	struct viocon_port *vp = sc->sc_ports[portidx];

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

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

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

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

+	if (virtio_features(vsc) & VIRTIO_F_RING_EVENT_IDX)
+		virtio_start_vq_intr(vsc, vp->vp_tx);
+
  	while (tp->t_outq.c_cc > 0) {
  		ret = virtio_enqueue_prep(vsc, vq, &slot);
  		if (ret == EAGAIN) {
@@ -466,6 +951,7 @@ vioconstart(struct tty *tp)
  		    vp->vp_tx_buf - vp->vp_rx_buf + slot * BUFSIZE, cnt, 1);
  		virtio_enqueue_commit(vsc, vq, slot, 0);
  		ndone++;
+		ttypull(tp);
  	}
  	if (ndone > 0)
  		virtio_notify(vsc, vq);
@@ -526,9 +1012,8 @@ vioconopen(dev_t dev, int flag, int mode, struct lwp *l)
  	}
  	vp = sc->sc_ports[port];
  	tp = vp->vp_tty;
-#ifdef NOTYET
  	vp->vp_guest_open = 1;
-#endif
+
  	splx(s);

  	if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp))
@@ -553,7 +1038,18 @@ vioconopen(dev_t dev, int flag, int mode, struct lwp *l)
  	}
  	splx(s);

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

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

  	(*tp->t_linesw->l_close)(tp, flag);
  	s = spltty();
-#ifdef NOTYET
  	vp->vp_guest_open = 0;
-#endif
+
  	CLR(tp->t_state, TS_BUSY | TS_FLUSH);
  	ttyclose(tp);
  	splx(s);

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

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

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

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

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

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

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

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

  };

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

+extern int (*enumerate_mmio_devices)(struct mmio_args *);
+
  #endif /* _VIRTIO_MMIOVAR_H_ */


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