diff --git a/share/man/man4/pv.4 b/share/man/man4/pv.4
new file mode 100644
index 00000000000..f25ba4a2475
--- /dev/null
+++ b/share/man/man4/pv.4
@@ -0,0 +1,44 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2024 The NetBSD Foundation, Inc.
+.\" 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.\" ported from OpenBSD
+.Dd January 2024
+.Dt PV 4
+.Os
+.Sh NAME
+.Nm pv
+.Nd paravirtual device tree root
+.Sh SYNOPSIS
+.Cd "pv* at pvbus?"
+.Sh DESCRIPTION
+.Nm
+driver is used on virtual machines that are running on hypervisors.
+It provides a pseudo-bus for all paravirtual devices that do not
+attach to a well-known bus like
+.Xr pci 4 .
+.Sh SEE ALSO
+.Xr virtio 4 ,
+.Xr mmio_virtio 4
diff --git a/share/man/man4/virtio_mmio.4 b/share/man/man4/virtio_mmio.4
new file mode 100644
index 00000000000..6f92cb3fdc4
--- /dev/null
+++ b/share/man/man4/virtio_mmio.4
@@ -0,0 +1,75 @@
+.\"	$NetBSD$
+.\"
+.\" Copyright (c) 2024 The NetBSD Foundation, Inc.
+.\" 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd January 2024
+.Dt VIRTIO_MMIO 4
+.Os NetBSD
+.Sh NAME
+.Nm virtio_mmio
+.Nd VirtIO over memory mapped device.
+.Sh SYNOPSIS
+.Cd "pv* at pvbus?"
+.Cd "virtio* at pv?"
+.Pp
+.Cd "acpi0 at mainbus0"
+.Cd "virtio* at acpi?"
+.Sh DESCRIPTION
+.Nm
+can be used in virtual environments without
+.Xr pci 4
+support (a common situation in embedded devices models) might use simple
+memory mapped device (
+.Nm
+) instead of the
+.Xr pci 4
+device.
+.Pp
+The memory mapped
+.Xr virtio 4
+device behaviour is based on the
+.Xr pci 4
+device specification. Therefore most operations including device initialization,
+queues configuration and buffer transfers are nearly identical.
+.Pp
+Unlike
+.Xr pci 4
+,
+.Nm
+provides no generic device discovery mechanism. For each device, the guest OS will
+need to know the location of the registers and interrupt(s) used.
+.Pp
+Device location can be read from either
+.Xr acpi 4
+or via kernel command line parameters, implemented as a
+.Xr pv 4
+virtual device.
+.Sh SEE ALSO
+.Xr virtio 4
+.Pp
+.Rs
+.%T Virtual I/O Device (VIRTIO) Version 1.2
+.%U https://docs.oasis-open.org/virtio/virtio/v1.2/virtio-v1.2.html
+.Re
diff --git a/sys/arch/amd64/amd64/amd64_mainbus.c b/sys/arch/amd64/amd64/amd64_mainbus.c
index 8a1bb0c55c6..f9d1118a54f 100644
--- a/sys/arch/amd64/amd64/amd64_mainbus.c
+++ b/sys/arch/amd64/amd64/amd64_mainbus.c
@@ -50,6 +50,7 @@ __KERNEL_RCSID(0, "$NetBSD: amd64_mainbus.c,v 1.7 2021/08/07 16:18:41 thorpej Ex
 #include "isadma.h"
 #include "acpica.h"
 #include "ipmi.h"
+#include "pvbus.h"
 
 #include "opt_acpi.h"
 #include "opt_mpbios.h"
@@ -79,6 +80,9 @@ __KERNEL_RCSID(0, "$NetBSD: amd64_mainbus.c,v 1.7 2021/08/07 16:18:41 thorpej Ex
 #include <arch/x86/pci/msipic.h>
 #endif /* __HAVE_PCI_MSI_MSIX */
 #endif
+#if NPVBUS > 0
+#include <dev/pv/pvvar.h>
+#endif
 
 /*
  * XXXfvdl ACPI
@@ -100,6 +104,9 @@ union amd64_mainbus_attach_args {
 #if NIPMI > 0
 	struct ipmi_attach_args mba_ipmi;
 #endif
+#if NPVBUS > 0
+	struct pvbus_attach_args mba_pvba;
+#endif
 };
 
 /*
@@ -155,7 +162,7 @@ amd64_mainbus_match(device_t parent, cfdata_t match, void *aux)
 void
 amd64_mainbus_attach(device_t parent, device_t self, void *aux)
 {
-#if NISA > 0 || NPCI > 0 || NACPICA > 0 || NIPMI > 0
+#if NISA > 0 || NPCI > 0 || NACPICA > 0 || NIPMI > 0 || NPVBUS > 0
 	union amd64_mainbus_attach_args mba;
 #endif
 
@@ -237,6 +244,12 @@ amd64_mainbus_attach(device_t parent, device_t self, void *aux)
 	}
 #endif
 
+#if NPVBUS > 0
+	mba.mba_pvba.pvba_busname = "pvbus";
+	config_found(self, &mba.mba_pvba.pvba_busname, NULL,
+		CFARGS(.iattr = "pvbus"));
+#endif
+
 	if (!pmf_device_register(self, NULL, NULL))
 		aprint_error_dev(self, "couldn't establish power handler\n");
 }
diff --git a/sys/arch/amd64/amd64/genassym.cf b/sys/arch/amd64/amd64/genassym.cf
index b5c7330a229..facd9216f75 100644
--- a/sys/arch/amd64/amd64/genassym.cf
+++ b/sys/arch/amd64/amd64/genassym.cf
@@ -373,6 +373,7 @@ define	BST_TYPE		offsetof(struct bus_space_tag, bst_type)
 
 define	VM_GUEST_XENPV		VM_GUEST_XENPV
 define	VM_GUEST_XENPVH		VM_GUEST_XENPVH
+define	VM_GUEST_GENPVH		VM_GUEST_GENPVH
 
 ifdef XEN
 define CPU_INFO_VCPU		offsetof(struct cpu_info, ci_vcpu)
@@ -381,6 +382,12 @@ define SIR_XENIPL_VM		SIR_XENIPL_VM
 define SIR_XENIPL_SCHED		SIR_XENIPL_SCHED
 define SIR_XENIPL_HIGH		SIR_XENIPL_HIGH
 define EVTCHN_UPCALL_MASK	offsetof(struct vcpu_info, evtchn_upcall_mask)
+define HVM_START_INFO_SIZE	sizeof(struct hvm_start_info)
+define START_INFO_VERSION	offsetof(struct hvm_start_info, version)
+define MMAP_PADDR		offsetof(struct hvm_start_info, memmap_paddr)
+define MMAP_ENTRIES		offsetof(struct hvm_start_info, memmap_entries)
+define MMAP_ENTRY_SIZE		sizeof(struct hvm_memmap_table_entry)
+define CMDLINE_PADDR		offsetof(struct hvm_start_info, cmdline_paddr)
 ifdef XENPV
 define XEN_PT_BASE		offsetof(struct start_info, pt_base)    
 define XEN_NR_PT_FRAMES		offsetof(struct start_info, nr_pt_frames)
diff --git a/sys/arch/amd64/amd64/locore.S b/sys/arch/amd64/amd64/locore.S
index 089c7388e18..97519bcb6a1 100644
--- a/sys/arch/amd64/amd64/locore.S
+++ b/sys/arch/amd64/amd64/locore.S
@@ -278,11 +278,12 @@
 
 #ifdef XEN
 #define __ASSEMBLY__
+#include <xen/include/public/arch-x86/cpuid.h>
 #include <xen/include/public/elfnote.h>
 #include <xen/include/public/xen.h>
 
 #define ELFNOTE(name, type, desctype, descdata...) \
-.pushsection .note.name			;	\
+.pushsection .note.name, "a", @note	;	\
   .align 4				;	\
   .long 2f - 1f		/* namesz */	;	\
   .long 4f - 3f		/* descsz */	;	\
@@ -306,7 +307,7 @@
 	ELFNOTE(Xen, XEN_ELFNOTE_ENTRY,          .quad,  start)
 #else
 	ELFNOTE(Xen, XEN_ELFNOTE_PADDR_OFFSET,   .quad,  0)
-	ELFNOTE(Xen, XEN_ELFNOTE_PHYS32_ENTRY,   .long,  RELOC(start_xen32))
+	ELFNOTE(Xen, XEN_ELFNOTE_PHYS32_ENTRY,   .long,  RELOC(start_genpvh))
 #endif /* XENPV */
 	ELFNOTE(Xen, XEN_ELFNOTE_HYPERCALL_PAGE, .quad,  hypercall_page)
 	ELFNOTE(Xen, XEN_ELFNOTE_HV_START_LOW,   .quad,  HYPERVISOR_VIRT_START)
@@ -973,7 +974,6 @@ longmode_hi:
 	movl	%eax,_C_LABEL(cpuid_level)
 
 	movl	$VM_GUEST_XENPV, _C_LABEL(vm_guest)
-
 	/*
 	 * Initialize cpu_info_primary.ci_self := &cpu_info_primary,
 	 * and initialize some MSRs with
@@ -1035,7 +1035,7 @@ END(start)
 # if !defined(XENPV)
 /* entry point for Xen PVH */
 	.code32
-ENTRY(start_xen32)
+ENTRY(start_genpvh)
 	/* Xen doesn't start us with a valid gdt */
 	movl    $RELOC(gdtdesc32), %eax
 	lgdt    (%eax)
@@ -1051,13 +1051,98 @@ ENTRY(start_xen32)
 	movl	$RELOC(tmpstk),%esp
 
 	/* clear BSS */
-        xorl    %eax,%eax
+	xorl    %eax,%eax
 	movl    $RELOC(__bss_start),%edi
 	movl    $RELOC(_end),%ecx
 	subl    %edi,%ecx
 	rep
 	stosb
 
+	/*
+	 * Here, we have 2 cases :
+	 *
+	 *  1) We have been started by Xen
+	 *  2) We have been started by another VMM (Qemu, Firecracker, ...)
+	 *
+	 * The main difference is that, when we are started by Xen,
+	 * %ebx (addr of the hvm_start_info structure) is pointing to a
+	 * location that will be mapped correctly later.
+	 *
+	 * In the second case, we have to copy this structure (and all
+	 * the information contained in it) to a location that will be
+	 * mapped later : __kernel_end
+	 *
+	 * To distinguish between the 2 cases, we'll use the 'cpuid' instruction
+	 */
+
+	push %ebx
+	xorl %eax, %eax
+	cpuid
+	cmpl $0x1, %eax				/* Check if we can call CPUID with eax=1 */
+	jb .start_genpvh
+	xorl %eax, %eax
+	inc %eax
+	cpuid
+	shr $31, %ecx
+	testb $1, %cl				/* Check if bit 31 of ECX (hypervisor) is set */
+	jz .start_genpvh
+	xorl %eax, %eax
+	inc %eax
+	shl $30, %eax
+	cpuid					/* Calling cpuid with eax=0x40000000 */
+	cmp $XEN_CPUID_SIGNATURE_EBX, %ebx	/* "VneX" */
+	je .start_xen32
+
+	/* We have been started by a VMM that is *not* Xen */
+
+.start_genpvh:
+
+	/* First, copy the hvm_start_info structure to __kernel_end */
+	pop %ebx
+	movl %ebx, %esi
+	movl $RELOC(__kernel_end), %edi
+	movl $HVM_START_INFO_SIZE, %ecx
+	shrl $2, %ecx
+	rep movsl
+
+	/* Copy cmdline_paddr after hvm_start_info */
+	movl CMDLINE_PADDR(%ebx), %esi
+	movl $RELOC(__kernel_end), %ecx
+	movl %edi, CMDLINE_PADDR(%ecx)	/* Set new cmdline_paddr in hvm_start_info */
+	.cmdline_copy:
+	movb (%esi), %al
+	movsb
+	cmp $0, %al
+	jne .cmdline_copy
+
+	/* Copy memmap_paddr after cmdline (only if hvm_start_info->version != 0) */
+	xorl %eax, %eax
+	cmpl START_INFO_VERSION(%ebx), %eax
+	je .reload_ebx
+	movl MMAP_PADDR(%ebx), %esi
+	movl $RELOC(__kernel_end), %ecx
+	movl %edi, MMAP_PADDR(%ecx)	/* Set new memmap_paddr in hvm_start_info */
+	movl MMAP_ENTRIES(%ebx), %eax	/* Get memmap_entries */
+	movl $MMAP_ENTRY_SIZE, %ebx
+	mull %ebx			/* eax * ebx => edx:eax */
+	movl %eax, %ecx
+	shll $2, %ecx
+	rep movsl
+
+.reload_ebx:
+	movl $RELOC(__kernel_end), %ebx
+
+	/* announce ourself */
+	movl	$VM_GUEST_GENPVH, RELOC(vm_guest)
+
+	jmp .save_hvm_start_paddr
+
+.start_xen32:
+	pop %ebx
+	movl	$VM_GUEST_XENPVH, RELOC(vm_guest)
+
+.save_hvm_start_paddr:
+
 	/*
 	 * save addr of the hvm_start_info structure. This is also the end
 	 * of the symbol table
@@ -1069,6 +1154,9 @@ ENTRY(start_xen32)
 	movl	%eax,(%ebp)
 	movl	$KERNBASE_HI,4(%ebp)
 	/* get a page for HYPERVISOR_shared_info */
+	/* this is only needed if we are running on Xen */
+	cmpl	$VM_GUEST_XENPVH, RELOC(vm_guest)
+	jne	.add_hvm_start_info_page
 	addl	$PAGE_SIZE, %ebx
 	addl	$PGOFSET,%ebx
 	andl	$~PGOFSET,%ebx
@@ -1076,6 +1164,7 @@ ENTRY(start_xen32)
 	movl	%ebx,(%ebp)
 	movl	$0,4(%ebp)
 	/* XXX assume hvm_start_info+dependant structure fits in a single page */
+.add_hvm_start_info_page:
 	addl	$PAGE_SIZE, %ebx
 	addl	$PGOFSET,%ebx
 	andl	$~PGOFSET,%ebx
@@ -1084,10 +1173,8 @@ ENTRY(start_xen32)
 	movl	%ebx,(%ebp)
 	movl	$KERNBASE_HI,4(%ebp)
 
-	/* announce ourself */
-	movl	$VM_GUEST_XENPVH, RELOC(vm_guest)
 	jmp .Lbiosbasemem_finished
-END(start_xen32)
+END(start_genpvh)
 	.code64
 # endif /* !XENPV */
 /* space for the hypercall call page */
@@ -1099,6 +1186,7 @@ ENTRY(hypercall_page) /* Returns -1, on HYPERVISOR_xen_version() */
 	retq
 .align HYPERCALL_PAGE_OFFSET, 0x90
 END(hypercall_page)
+
 #endif /* XEN */
 
 /*
diff --git a/sys/arch/amd64/amd64/machdep.c b/sys/arch/amd64/amd64/machdep.c
index 375d7a41e7f..f1c8f5c66a6 100644
--- a/sys/arch/amd64/amd64/machdep.c
+++ b/sys/arch/amd64/amd64/machdep.c
@@ -1525,8 +1525,10 @@ init_x86_64_ksyms(void)
 	} else {
 		uintptr_t endp = (uintptr_t)(void *)&end;
 
-		ksyms_addsyms_elf(*(long *)endp,
-		    ((long *)endp) + 1, esym);
+		if (vm_guest == VM_GUEST_GENPVH)
+			ksyms_addsyms_elf(0, ((long *)endp) + 1, esym);
+		else
+			ksyms_addsyms_elf(*(long *)endp, ((long *)endp) + 1, esym);
 	}
 #endif
 }
@@ -1710,7 +1712,7 @@ init_x86_64(paddr_t first_avail)
 #endif
 
 #ifdef XEN
-	if (vm_guest == VM_GUEST_XENPVH)
+	if (vm_guest == VM_GUEST_XENPVH || vm_guest == VM_GUEST_GENPVH)
 		xen_parse_cmdline(XEN_PARSE_BOOTFLAGS, NULL);
 #endif
 	init_pte();
diff --git a/sys/arch/amd64/conf/files.amd64 b/sys/arch/amd64/conf/files.amd64
index 99e0e512938..d4e2fc5f38b 100644
--- a/sys/arch/amd64/conf/files.amd64
+++ b/sys/arch/amd64/conf/files.amd64
@@ -94,7 +94,7 @@ include	"dev/i2o/files.i2o"
 
 # XXX BIOS32 only if something that uses it is configured!
 device	mainbus: isabus, pcibus, bios32, acpibus, cpubus, ioapicbus,
-	ipmibus, hypervisorbus
+	ipmibus, hypervisorbus, pvbus
 attach	mainbus at root
 file	arch/amd64/amd64/amd64_mainbus.c	mainbus & !xenpv
 file	arch/x86/x86/mainbus.c			mainbus
@@ -200,4 +200,7 @@ file	dev/acpi/vmbus_acpi.c			vmbus_acpi
 # VMEbus support
 include "dev/vme/files.vme"
 
+# PVbus support
+include "dev/pv/files.pv"
+
 include	"arch/amd64/conf/majors.amd64"
diff --git a/sys/arch/x86/acpi/acpi_machdep.c b/sys/arch/x86/acpi/acpi_machdep.c
index 9dbfe49ca5f..b75767fe615 100644
--- a/sys/arch/x86/acpi/acpi_machdep.c
+++ b/sys/arch/x86/acpi/acpi_machdep.c
@@ -158,7 +158,7 @@ out:
 	}
 #else
 #ifdef XEN
-	if (vm_guest == VM_GUEST_XENPVH) {
+	if (vm_guest == VM_GUEST_XENPVH || vm_guest == VM_GUEST_GENPVH) {
 		PhysicalAddress = hvm_start_info->rsdp_paddr;
 		if (PhysicalAddress)
 			return PhysicalAddress;
diff --git a/sys/arch/x86/include/cpu.h b/sys/arch/x86/include/cpu.h
index 0dae029202a..c82da831157 100644
--- a/sys/arch/x86/include/cpu.h
+++ b/sys/arch/x86/include/cpu.h
@@ -516,6 +516,7 @@ typedef enum vm_guest {
 	VM_GUEST_VMWARE,
 	VM_GUEST_KVM,
 	VM_GUEST_VIRTUALBOX,
+	VM_GUEST_GENPVH,
 	VM_LAST
 } vm_guest_t;
 extern vm_guest_t vm_guest;
diff --git a/sys/arch/x86/x86/bus_dma.c b/sys/arch/x86/x86/bus_dma.c
index b47b4b1bc2e..7d2753bb7c2 100644
--- a/sys/arch/x86/x86/bus_dma.c
+++ b/sys/arch/x86/x86/bus_dma.c
@@ -319,7 +319,7 @@ _bus_dmamap_create(bus_dma_tag_t t, bus_size_t size, int nsegments,
 			goto out;
 	}
 
-	if (map->_dm_bounce_thresh != 0)
+	if (map->_dm_bounce_thresh != 0 || map->_dm_segcnt == 1)
 		cookieflags |= X86_DMA_MIGHT_NEED_BOUNCE;
 
 	if ((cookieflags & X86_DMA_MIGHT_NEED_BOUNCE) == 0) {
diff --git a/sys/arch/x86/x86/consinit.c b/sys/arch/x86/x86/consinit.c
index 7a65f1bfa09..f72f872ab54 100644
--- a/sys/arch/x86/x86/consinit.c
+++ b/sys/arch/x86/x86/consinit.c
@@ -171,6 +171,7 @@ consinit(void)
 #if (NCOM > 0)
 	int rv;
 #endif
+	char console_devname[16] = "";
 
 #ifdef XENPVHVM
 	if (vm_guest == VM_GUEST_XENPVH) {
@@ -178,6 +179,13 @@ consinit(void)
 			return;
 		/* fallback to native console selection, usefull for dom0 PVH */
 	}
+	if (vm_guest == VM_GUEST_GENPVH) {
+		union xen_cmdline_parseinfo xcp;
+		/* get console= parameter from generic PVH VMM */
+		xen_parse_cmdline(XEN_PARSE_CONSOLE, &xcp);
+		strncpy(console_devname, xcp.xcp_console,
+			sizeof(console_devname));
+	}
 #endif
 	if (initted)
 		return;
@@ -188,7 +196,10 @@ consinit(void)
 	if (!consinfo)
 #endif
 		consinfo = &default_consinfo;
-
+	/* console= parameter was not passed via a generic PVH VMM */
+	if (!console_devname[0])
+		strncpy(console_devname, consinfo->devname,
+			sizeof(console_devname));
 #if (NGENFB > 0)
 #if defined(XENPVHVM) && defined(DOM0OPS)
 	if (vm_guest == VM_GUEST_XENPVH && xendomain_is_dom0())
@@ -197,8 +208,7 @@ consinit(void)
 #endif /* XENPVHVM */
 		fbinfo = lookup_bootinfo(BTINFO_FRAMEBUFFER);
 #endif
-
-	if (!strcmp(consinfo->devname, "pc")) {
+	if (!strcmp(console_devname, "pc")) {
 		int error;
 #if (NGENFB > 0)
 		if (fbinfo && fbinfo->physaddr > 0) {
@@ -254,7 +264,7 @@ dokbd:
 		return;
 	}
 #if (NCOM > 0)
-	if (!strcmp(consinfo->devname, "com")) {
+	if (!strcmp(console_devname, "com")) {
 		int addr = consinfo->addr;
 		int speed = consinfo->speed;
 
@@ -278,14 +288,14 @@ dokbd:
 	}
 #endif
 #if (NNULLCONS > 0)
-	if (!strcmp(consinfo->devname, "nullcons")) {
+	if (!strcmp(console_devname, "nullcons")) {
 		void nullcninit(struct consdev *cn);
 
 		nullcninit(0);
 		return;
 	}
 #endif
-	panic("invalid console device %s", consinfo->devname);
+	panic("invalid console device %s", console_devname);
 }
 
 #ifdef KGDB
diff --git a/sys/arch/x86/x86/identcpu.c b/sys/arch/x86/x86/identcpu.c
index ec2fa0620b7..9856cf9fb15 100644
--- a/sys/arch/x86/x86/identcpu.c
+++ b/sys/arch/x86/x86/identcpu.c
@@ -1044,6 +1044,7 @@ static const struct vm_name_guest vm_bios_vendors[] = {
 	{ "BHYVE", VM_GUEST_VM },			/* bhyve */
 	{ "Seabios", VM_GUEST_VM },			/* KVM */
 	{ "innotek GmbH", VM_GUEST_VIRTUALBOX },	/* Oracle VirtualBox */
+	{ "Generic PVH", VM_GUEST_GENPVH},		/* Generic PVH */
 };
 
 static const struct vm_name_guest vm_system_products[] = {
@@ -1065,6 +1066,7 @@ identify_hypervisor(void)
 	switch (vm_guest) {
 	case VM_GUEST_XENPV:
 	case VM_GUEST_XENPVH:
+	case VM_GUEST_GENPVH:
 		/* guest type already known, no bios info */
 		return;
 	default:
diff --git a/sys/arch/x86/x86/mpbios.c b/sys/arch/x86/x86/mpbios.c
index a3565a44251..85b3e38e3e2 100644
--- a/sys/arch/x86/x86/mpbios.c
+++ b/sys/arch/x86/x86/mpbios.c
@@ -209,6 +209,9 @@ static void mpbios_int(const uint8_t *, int, struct mp_intr_map *);
 static const void *mpbios_map(paddr_t, int, struct mp_map *);
 static void mpbios_unmap(struct mp_map *);
 
+#ifdef MPTABLE_LINUX_BUG_COMPAT
+static uint16_t compute_entry_count(const uint8_t *, const uint8_t *);
+#endif
 /*
  * globals to help us bounce our way through parsing the config table.
  */
@@ -333,6 +336,17 @@ mpbios_probe(device_t self)
 	if (mp_fps != NULL)
 		goto found;
 
+	/*
+	 * Linux assumes that it always has 640 kB of base memory and
+	 * searches for the MP table at 639k regardless of whether that
+	 * address is present in the system memory map.  Some VM systems
+	 * rely on this buggy behaviour.
+	 */
+	mp_fps = mpbios_search(self, 639 * 1024, 1024 / 4, &mp_fp_map);
+	if (mp_fps != NULL)
+		goto found;
+
+
 	/* nothing found */
 	return 0;
 
@@ -533,6 +547,31 @@ static const uint8_t dflt_lint_tab[2] = {
 };
 
 
+#ifdef MPTABLE_LINUX_BUG_COMPAT
+/* Compute the correct entry_count value. */
+static uint16_t
+compute_entry_count(const uint8_t *entry, const uint8_t *end)
+{
+	size_t nentries = 0;
+
+	while (entry < end) {
+		switch (*entry) {
+		case MPS_MCT_CPU:
+		case MPS_MCT_BUS:
+		case MPS_MCT_IOAPIC:
+		case MPS_MCT_IOINT:
+		case MPS_MCT_LINT:
+			break;
+		default:
+			panic("%s: Unknown MP Config Entry %d\n", __func__,
+				(int)*entry);
+		}
+		entry += mp_conf[*entry].length;;
+		nentries++;
+	}
+	return (uint16_t)(nentries);
+}
+#endif
 /*
  * 1st pass on BIOS's Intel MP specification table.
  *
@@ -557,6 +596,9 @@ mpbios_scan(device_t self, int *ncpup)
 	int		intr_cnt, cur_intr;
 #if NLAPIC > 0
 	paddr_t		lapic_base;
+#endif
+#ifdef MPTABLE_LINUX_BUG_COMPAT
+	uint16_t	countfix = 0;
 #endif
 	const struct dflt_conf_entry *dflt_conf;
 	const int *dflt_bus_irq;
@@ -677,6 +719,13 @@ mpbios_scan(device_t self, int *ncpup)
 		position += sizeof(*mp_cth);
 
 		count = mp_cth->entry_count;
+#ifdef MPTABLE_LINUX_BUG_COMPAT
+		if (count == 0) {
+			/* count the correct entry_count */
+			countfix = compute_entry_count(position, end);
+			count = countfix;
+		}
+#endif
 		intr_cnt = 0;
 
 		while ((count--) && (position < end)) {
@@ -721,6 +770,10 @@ mpbios_scan(device_t self, int *ncpup)
 		/* re-walk the table, recording info of interest */
 		position = (const uint8_t *)mp_cth + sizeof(*mp_cth);
 		count = mp_cth->entry_count;
+#ifdef MPTABLE_LINUX_BUG_COMPAT
+		if (count == 0)
+			count = countfix;
+#endif
 		cur_intr = 0;
 
 		while ((count--) && (position < end)) {
diff --git a/sys/arch/x86/x86/x86_autoconf.c b/sys/arch/x86/x86/x86_autoconf.c
index fd8688fe9cc..ba4b698d067 100644
--- a/sys/arch/x86/x86/x86_autoconf.c
+++ b/sys/arch/x86/x86/x86_autoconf.c
@@ -540,7 +540,7 @@ void
 cpu_bootconf(void)
 {
 #ifdef XEN
-	if (vm_guest == VM_GUEST_XENPVH) {
+	if (vm_guest == VM_GUEST_XENPVH || vm_guest == VM_GUEST_GENPVH) {
 		xen_bootconf();
 		return;
 	}
diff --git a/sys/arch/x86/x86/x86_machdep.c b/sys/arch/x86/x86/x86_machdep.c
index fabd76b1155..8a2364b6ecc 100644
--- a/sys/arch/x86/x86/x86_machdep.c
+++ b/sys/arch/x86/x86/x86_machdep.c
@@ -911,7 +911,7 @@ init_x86_clusters(void)
 	 * the boot program).
 	 */
 #ifdef XEN
-	if (vm_guest == VM_GUEST_XENPVH) {
+	if (vm_guest == VM_GUEST_XENPVH || vm_guest == VM_GUEST_GENPVH) {
 		x86_add_xen_clusters();
 	}
 #endif /* XEN */
diff --git a/sys/arch/xen/xen/hypervisor.c b/sys/arch/xen/xen/hypervisor.c
index 47b7dc3ec0a..7e26b7696a6 100644
--- a/sys/arch/xen/xen/hypervisor.c
+++ b/sys/arch/xen/xen/hypervisor.c
@@ -241,10 +241,25 @@ void
 init_xen_early(void)
 {
 	const char *cmd_line;
+	if (vm_guest != VM_GUEST_XENPVH && vm_guest != VM_GUEST_GENPVH)
+		return;
+
+	hvm_start_info = (void *)((uintptr_t)hvm_start_paddr + KERNBASE);
+
+	if (hvm_start_info->cmdline_paddr != 0) {
+		cmd_line =
+		    (void *)((uintptr_t)hvm_start_info->cmdline_paddr + KERNBASE);
+		strlcpy(xen_start_info.cmd_line, cmd_line,
+		    sizeof(xen_start_info.cmd_line));
+	} else {
+		xen_start_info.cmd_line[0] = '\0';
+	}
+	xen_start_info.flags = hvm_start_info->flags;
+
 	if (vm_guest != VM_GUEST_XENPVH)
 		return;
+
 	xen_init_hypercall_page();
-	hvm_start_info = (void *)((uintptr_t)hvm_start_paddr + KERNBASE);
 
 	HYPERVISOR_shared_info = (void *)((uintptr_t)HYPERVISOR_shared_info_pa + KERNBASE);
 	struct xen_add_to_physmap xmap = {
@@ -262,15 +277,6 @@ init_xen_early(void)
 	}
 	delay_func = x86_delay = xen_delay;
 	x86_initclock_func = xen_initclocks;
-	if (hvm_start_info->cmdline_paddr != 0) {
-		cmd_line =
-		    (void *)((uintptr_t)hvm_start_info->cmdline_paddr + KERNBASE);
-		strlcpy(xen_start_info.cmd_line, cmd_line,
-		    sizeof(xen_start_info.cmd_line));
-	} else {
-		xen_start_info.cmd_line[0] = '\0';
-	}
-	xen_start_info.flags = hvm_start_info->flags;
 }
 
 
diff --git a/sys/dev/acpi/files.acpi b/sys/dev/acpi/files.acpi
index 6763f983669..9f87bb3b638 100644
--- a/sys/dev/acpi/files.acpi
+++ b/sys/dev/acpi/files.acpi
@@ -273,7 +273,7 @@ attach	amdccp at acpinodebus with amdccp_acpi
 file	dev/acpi/amdccp_acpi.c		amdccp_acpi
 
 # QEMU Virtio
-attach	virtio at acpinodebus with virtio_acpi
+attach	virtio at acpinodebus with virtio_acpi: virtio_mmio
 file	dev/acpi/virtio_acpi.c		virtio_acpi
 
 # OHCI-compliant USB controller
diff --git a/sys/dev/ldvar.h b/sys/dev/ldvar.h
index 0aa0c097a74..5b9d8bc2075 100644
--- a/sys/dev/ldvar.h
+++ b/sys/dev/ldvar.h
@@ -56,6 +56,7 @@ struct ld_softc {
 	uint64_t	sc_secperunit;	/* # sectors in total */
 	int		sc_secsize;	/* sector size in bytes */
 	int		sc_maxxfer;	/* max xfer size in bytes */
+	int		sc_maxnsegs;	/* maximum number of segments */
 	int		sc_maxqueuecnt;	/* maximum h/w queue depth */
 	char		*sc_typename;	/* inquiry data */
 
diff --git a/sys/dev/pci/ld_virtio.c b/sys/dev/pci/ld_virtio.c
index 0cea794c9a0..afdc1e48aa8 100644
--- a/sys/dev/pci/ld_virtio.c
+++ b/sys/dev/pci/ld_virtio.c
@@ -196,6 +196,7 @@ ld_virtio_alloc_reqs(struct ld_virtio_softc *sc, int qsize)
 	memset(vaddr, 0, allocsize);
 	for (i = 0; i < qsize; i++) {
 		struct virtio_blk_req *vr = &sc->sc_reqs[i];
+		int nsegs;
 		r = bus_dmamap_create(virtio_dmat(sc->sc_virtio),
 				      offsetof(struct virtio_blk_req, vr_bp),
 				      1,
@@ -219,10 +220,18 @@ ld_virtio_alloc_reqs(struct ld_virtio_softc *sc, int qsize)
 					 "error code %d\n", r);
 			goto err_reqs;
 		}
+		/*
+		 * if d->sc_maxnsegs == VIRTIO_BLK_MIN_SEGMENTS + 1, the
+		 * device only supports a single data segment.
+		 */
+		if (ld->sc_maxnsegs == VIRTIO_BLK_MIN_SEGMENTS + 1)
+			nsegs = ld->sc_maxnsegs - VIRTIO_BLK_MIN_SEGMENTS;
+		else
+			nsegs = (ld->sc_maxxfer / NBPG) + VIRTIO_BLK_MIN_SEGMENTS;
+
 		r = bus_dmamap_create(virtio_dmat(sc->sc_virtio),
 				      ld->sc_maxxfer,
-				      (ld->sc_maxxfer / NBPG) +
-				      VIRTIO_BLK_MIN_SEGMENTS,
+				      nsegs,
 				      ld->sc_maxxfer,
 				      0,
 				      BUS_DMA_WAITOK|BUS_DMA_ALLOCNOW,
@@ -264,7 +273,7 @@ ld_virtio_attach(device_t parent, device_t self, void *aux)
 	struct ld_softc *ld = &sc->sc_ld;
 	struct virtio_softc *vsc = device_private(parent);
 	uint64_t features;
-	int qsize, maxxfersize, maxnsegs;
+	int qsize, maxxfersize;
 
 	if (virtio_child(vsc) != NULL) {
 		aprint_normal(": child already attached for %s; "
@@ -317,30 +326,33 @@ ld_virtio_attach(device_t parent, device_t self, void *aux)
 		maxxfersize = MAXPHYS;
 
 	if (features & VIRTIO_BLK_F_SEG_MAX) {
-		maxnsegs = virtio_read_device_config_4(vsc,
+		ld->sc_maxnsegs = virtio_read_device_config_4(vsc,
 		    VIRTIO_BLK_CONFIG_SEG_MAX);
-		if (maxnsegs < VIRTIO_BLK_MIN_SEGMENTS) {
+		if (ld->sc_maxnsegs < VIRTIO_BLK_MIN_SEGMENTS) {
 			aprint_error_dev(sc->sc_dev,
 			    "Too small SEG_MAX %d minimum is %d\n",
-			    maxnsegs, VIRTIO_BLK_MIN_SEGMENTS);
-			maxnsegs = maxxfersize / NBPG;
+			    ld->sc_maxnsegs, VIRTIO_BLK_MIN_SEGMENTS);
+			ld->sc_maxnsegs = maxxfersize / NBPG;
 			// goto err;
 		}
 	} else
-		maxnsegs = maxxfersize / NBPG;
+	/*
+	 * if there is no VIRTIO_BLK_F_SEG_MAX feature advertised, the
+	 * number of segments can be as low as 1 (i.e. Firecracker)
+	 */
+		ld->sc_maxnsegs = 1;
 
 	/* 2 for the minimum size */
-	maxnsegs += VIRTIO_BLK_MIN_SEGMENTS;
+	ld->sc_maxnsegs += VIRTIO_BLK_MIN_SEGMENTS;
 
 	virtio_init_vq_vqdone(vsc, &sc->sc_vq, 0,
 	    ld_virtio_vq_done);
 
-	if (virtio_alloc_vq(vsc, &sc->sc_vq, maxxfersize, maxnsegs,
+	if (virtio_alloc_vq(vsc, &sc->sc_vq, maxxfersize, ld->sc_maxnsegs,
 	    "I/O request") != 0) {
 		goto err;
 	}
 	qsize = sc->sc_vq.vq_num;
-
 	if (virtio_child_attach_finish(vsc, &sc->sc_vq, 1,
 	    NULL, VIRTIO_F_INTR_MSIX) != 0)
 		goto err;
diff --git a/sys/dev/pv/files.pv b/sys/dev/pv/files.pv
new file mode 100644
index 00000000000..1775b9edf5a
--- /dev/null
+++ b/sys/dev/pv/files.pv
@@ -0,0 +1,8 @@
+define	pvbus {}
+
+device	pv {}
+attach	pv at pvbus
+file	dev/pv/pvbus.c				pvbus	needs-flag
+
+attach	virtio at pv with mmio_cmdline:		virtio_mmio
+file	dev/virtio/virtio_mmio_cmdline.c	mmio_cmdline
diff --git a/sys/dev/pv/pvbus.c b/sys/dev/pv/pvbus.c
new file mode 100644
index 00000000000..f1101d62305
--- /dev/null
+++ b/sys/dev/pv/pvbus.c
@@ -0,0 +1,71 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Emile 'iMil' Heitor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/systm.h>
+
+#include <machine/bus_private.h>
+
+#include <dev/pv/pvvar.h>
+
+struct x86_bus_dma_tag pvbus_bus_dma_tag = {
+	._tag_needs_free	= 0,
+	._bounce_thresh		= 0,
+	._bounce_alloc_lo	= 0,
+	._bounce_alloc_hi	= 0,
+	._may_bounce		= NULL,
+};
+
+static int
+pv_match(device_t parent, cfdata_t match, void *aux)
+{
+	return 1;
+}
+
+static void
+pv_attach(device_t parent, device_t self, void *aux) {
+	struct pv_attach_args pvaa;
+
+	pvaa.pvaa_memt = x86_bus_space_mem;
+	pvaa.pvaa_dmat = &pvbus_bus_dma_tag;
+
+	aprint_naive("\n");
+	aprint_normal("\n");
+
+	config_found(self, &pvaa, NULL, CFARGS_NONE);
+}
+
+CFATTACH_DECL_NEW(pv, sizeof(struct pv_softc),
+		  pv_match, pv_attach, NULL, NULL);
diff --git a/sys/dev/pv/pvvar.h b/sys/dev/pv/pvvar.h
new file mode 100644
index 00000000000..bba44b926dc
--- /dev/null
+++ b/sys/dev/pv/pvvar.h
@@ -0,0 +1,48 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Emile 'iMil' Heitor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PVBUS_PVVAR_H_
+#define _PVBUS_PVVAR_H_
+
+struct pv_softc {
+	device_t		sc_dev;
+};
+
+struct pvbus_attach_args {
+	const char		*pvba_busname;
+};
+
+struct pv_attach_args {
+	bus_space_tag_t		pvaa_memt;
+	bus_dma_tag_t		pvaa_dmat;
+};
+
+#endif
diff --git a/sys/dev/virtio/virtio_mmio_cmdline.c b/sys/dev/virtio/virtio_mmio_cmdline.c
new file mode 100644
index 00000000000..ec2fadc8066
--- /dev/null
+++ b/sys/dev/virtio/virtio_mmio_cmdline.c
@@ -0,0 +1,345 @@
+/* $NetBSD$ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Emile 'iMil' Heitor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*-
+ * Copyright (c) 2022 Colin Percival
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/device.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/systm.h>
+
+#define VIRTIO_PRIVATE
+#include <dev/virtio/virtio_mmiovar.h>
+#include <dev/pv/pvvar.h>
+#include <xen/hypervisor.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;
+};
+
+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 *);
+static void	virtio_mmio_cmdline_free_interrupts(struct virtio_mmio_softc *);
+
+CFATTACH_DECL3_NEW(mmio_cmdline,
+	sizeof(struct virtio_mmio_cmdline_softc),
+	virtio_mmio_cmdline_match, virtio_mmio_cmdline_attach,
+	virtio_mmio_cmdline_detach, NULL,
+	virtio_mmio_cmdline_rescan, (void *)voidop, DVF_DETACH_SHUTDOWN);
+
+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
+parsearg(struct mmio_args *margs, const char *arg)
+{
+	char *p;
+
+	/* <size> */
+	margs->sz = strtoull(arg, (char **)&p, 0);
+	if ((margs->sz == 0) || (margs->sz == ULLONG_MAX))
+		goto bad;
+	switch (*p) {
+	case 'E': case 'e':
+		margs->sz <<= 10;
+		/* FALLTHROUGH */
+	case 'P': case 'p':
+		margs->sz <<= 10;
+		/* FALLTHROUGH */
+	case 'T': case 't':
+		margs->sz <<= 10;
+		/* FALLTHROUGH */
+	case 'G': case 'g':
+		margs->sz <<= 10;
+		/* FALLTHROUGH */
+	case 'M': case 'm':
+		margs->sz <<= 10;
+		/* FALLTHROUGH */
+	case 'K': case 'k':
+		margs->sz <<= 10;
+		p++;
+		break;
+	}
+
+	/* @<baseaddr> */
+	if (*p++ != '@')
+		goto bad;
+	margs->baseaddr = strtoull(p, (char **)&p, 0);
+	if ((margs->baseaddr == 0) || (margs->baseaddr == ULLONG_MAX))
+		goto bad;
+
+	/* :<irq> */
+	if (*p++ != ':')
+		goto bad;
+	margs->irq = strtoull(p, (char **)&p, 0);
+	if ((margs->irq == 0) || (margs->irq == ULLONG_MAX))
+		goto bad;
+
+	/* Optionally, :<id> */
+	if (*p) {
+		if (*p++ != ':')
+			goto bad;
+		margs->id = strtoull(p, (char **)&p, 0);
+		if ((margs->id == 0) || (margs->id == ULLONG_MAX))
+			goto bad;
+	} else {
+		margs->id = 0;
+	}
+
+	/* Should have reached the end of the string. */
+	if (*p)
+		goto bad;
+
+	return;
+
+bad:
+	aprint_error("Error parsing virtio_mmio parameter: %s\n", 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;
+	char *v, *n, cmdline[128];
+	int error;
+	static char *p = NULL;
+	static int idx = 0;
+	bool hasnext;
+
+	aprint_normal("\n");
+	aprint_naive("\n");
+
+	if (idx == 0) {
+		strncpy(cmdline, xen_start_info.cmd_line, sizeof(cmdline));
+		aprint_verbose("kernel parameters: %s\n", cmdline);
+		if ((p = strstr(cmdline, VMMIOSTR)) == NULL)
+			return;
+	}
+
+	while (*p) { /* manual strtok() */
+		hasnext = false;
+		v = p; /* start of VMMIOSTR or next param */
+		while (*p && *p != ' ') /* find end of param */
+			p++;
+		if (*p) {
+			n = p; /* record end of param */
+			*p = '\0'; /* end it */
+			hasnext = true; /* more params to come */
+		}
+		if (strncmp(v, VMMIOSTR, strlen(VMMIOSTR)) != 0)
+			continue; /* this was not an MMIO parameter */
+		p = v; /* start of VMMIOSTR */
+		while (*p && *p != '=') /* point to the value */
+			p++;
+		if (*p) {
+			p++;
+			aprint_normal("viommio: %s\n", p);
+			parsearg(margs, p);
+
+			error = virtio_mmio_cmdline_do_attach(self,
+				pvaa, margs);
+
+			if (error)
+				return;
+		}
+		if (hasnext) {
+			p = n+1; /* previously recorded end of param */
+			idx++;
+			config_found(parent, pvaa, NULL, CFARGS_NONE);
+		}
+	}
+}
+
+static int
+virtio_mmio_cmdline_do_attach(device_t self,
+		struct pv_attach_args *pvaa,
+		struct mmio_args *margs)
+{
+	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;
+
+	msc->sc_iot = pvaa->pvaa_memt;
+	vsc->sc_dmat = pvaa->pvaa_dmat;
+	msc->sc_iosize = margs->sz;
+	vsc->sc_dev = 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",
+			(uint64_t)margs->baseaddr, error
+		);
+		return error;
+	}
+
+	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
+virtio_mmio_cmdline_detach(device_t self, int flags)
+{
+	struct virtio_mmio_cmdline_softc * const sc = device_private(self);
+	struct virtio_mmio_softc * const msc = &sc->sc_msc;
+
+	return virtio_mmio_common_detach(msc, flags);
+}
+
+static int
+virtio_mmio_cmdline_rescan(device_t self, const char *ifattr, const int *locs)
+{
+	struct virtio_mmio_cmdline_softc *const sc = device_private(self);
+	struct virtio_mmio_softc *const msc = &sc->sc_msc;
+	struct virtio_softc *const vsc = &msc->sc_sc;
+	struct virtio_attach_args va;
+
+	if (vsc->sc_child)
+		return 0;
+
+	memset(&va, 0, sizeof(va));
+	va.sc_childdevid = vsc->sc_childdevid;
+
+	config_found(self, &va, NULL, CFARGS_NONE);
+
+	if (virtio_attach_failed(vsc))
+		return 0;
+
+	return 0;
+}
+
+
+static int
+virtio_mmio_cmdline_alloc_interrupts(struct virtio_mmio_softc *msc)
+{
+	struct virtio_mmio_cmdline_softc *const sc =
+		(struct virtio_mmio_cmdline_softc *)msc;
+	struct virtio_softc *const vsc = &msc->sc_sc;
+	struct ioapic_softc *ioapic;
+	struct pic *pic;
+	int irq = sc->margs.irq;
+	int pin = irq;
+	bool mpsafe;
+
+	ioapic = ioapic_find_bybase(irq);
+
+	if (ioapic != NULL) {
+		KASSERT(ioapic->sc_pic.pic_type == PIC_IOAPIC);
+		pic = &ioapic->sc_pic;
+		pin = irq - pic->pic_vecbase;
+		irq = -1;
+	} else
+		pic = &i8259_pic;
+
+	mpsafe = (0 != (vsc->sc_flags & VIRTIO_F_INTR_MPSAFE));
+
+	msc->sc_ih = intr_establish_xname(irq, pic, pin, IST_LEVEL, vsc->sc_ipl,
+		virtio_mmio_intr, msc, mpsafe, device_xname(vsc->sc_dev));
+	if (msc->sc_ih == NULL) {
+		aprint_error_dev(vsc->sc_dev,
+		    "failed to establish interrupt\n");
+		return -1;
+	}
+	aprint_normal_dev(vsc->sc_dev, "interrupting on %d\n", irq);
+
+	return 0;
+}
+
+static void
+virtio_mmio_cmdline_free_interrupts(struct virtio_mmio_softc *msc)
+{
+	if (msc->sc_ih != NULL) {
+		intr_disestablish(msc->sc_ih);
+		msc->sc_ih = NULL;
+	}
+}
+