git: 7ab1a32cd43c - main - bhyve/riscv: Initial import.

From: Ruslan Bukin <br_at_FreeBSD.org>
Date: Thu, 31 Oct 2024 20:25:34 UTC
The branch main has been updated by br:

URL: https://cgit.FreeBSD.org/src/commit/?id=7ab1a32cd43cbae61ad4dd435d6a482bbf61cb52

commit 7ab1a32cd43cbae61ad4dd435d6a482bbf61cb52
Author:     Ruslan Bukin <br@FreeBSD.org>
AuthorDate: 2024-10-31 16:14:32 +0000
Commit:     Ruslan Bukin <br@FreeBSD.org>
CommitDate: 2024-10-31 20:24:12 +0000

    bhyve/riscv: Initial import.
    
    Add machine-dependent parts for bhyve hypervisor to support
    virtualization on RISC-V ISA.
    
    No objection:   markj
    Sponsored by: UK Research and Innovation
    Differential Revision: https://reviews.freebsd.org/D45512
---
 lib/Makefile                               |   3 +-
 lib/libvmmapi/riscv/Makefile.inc           |   1 +
 lib/libvmmapi/riscv/vmmapi_machdep.c       | 117 +++++++++
 lib/libvmmapi/vmmapi.h                     |   9 +-
 usr.sbin/Makefile.riscv                    |   2 +
 usr.sbin/bhyve/pci_emul.c                  |   2 +-
 usr.sbin/bhyve/pci_irq.h                   |   2 +
 usr.sbin/bhyve/riscv/Makefile.inc          |   7 +
 usr.sbin/bhyve/riscv/bhyverun_machdep.c    | 357 ++++++++++++++++++++++++++++
 usr.sbin/bhyve/riscv/fdt.c                 | 326 +++++++++++++++++++++++++
 usr.sbin/bhyve/riscv/fdt.h                 |  45 ++++
 usr.sbin/bhyve/riscv/pci_irq.c             |  66 ++++++
 usr.sbin/bhyve/riscv/pci_irq_machdep.h     |  49 ++++
 usr.sbin/bhyve/riscv/riscv.h               |  41 ++++
 usr.sbin/bhyve/riscv/vmexit.c              | 366 +++++++++++++++++++++++++++++
 usr.sbin/bhyvectl/riscv/Makefile.inc       |   1 +
 usr.sbin/bhyvectl/riscv/bhyvectl_machdep.c |  82 +++++++
 17 files changed, 1472 insertions(+), 4 deletions(-)

diff --git a/lib/Makefile b/lib/Makefile
index fdfe198bea10..af0079978075 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -202,7 +202,8 @@ SUBDIR.${MK_PMC}+=	libopencsd
 SUBDIR.${MK_PMC}+=	libipt
 .endif
 
-.if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "aarch64"
+.if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "aarch64" || \
+    ${MACHINE_CPUARCH} == "riscv"
 SUBDIR.${MK_BHYVE}+=	libvmmapi
 .endif
 
diff --git a/lib/libvmmapi/riscv/Makefile.inc b/lib/libvmmapi/riscv/Makefile.inc
new file mode 100644
index 000000000000..663ea0ab90a3
--- /dev/null
+++ b/lib/libvmmapi/riscv/Makefile.inc
@@ -0,0 +1 @@
+SRCS+=	vmmapi_machdep.c
diff --git a/lib/libvmmapi/riscv/vmmapi_machdep.c b/lib/libvmmapi/riscv/vmmapi_machdep.c
new file mode 100644
index 000000000000..9c70185942c9
--- /dev/null
+++ b/lib/libvmmapi/riscv/vmmapi_machdep.c
@@ -0,0 +1,117 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011 NetApp, 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 NETAPP, INC ``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 NETAPP, INC 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/types.h>
+#include <sys/ioctl.h>
+
+#include <machine/vmm.h>
+#include <machine/vmm_dev.h>
+#include <machine/vmm_snapshot.h>
+
+#include <assert.h>
+#include <string.h>
+
+#include "vmmapi.h"
+#include "internal.h"
+
+const char *vm_capstrmap[] = {
+	[VM_CAP_MAX] = NULL,
+};
+
+#define	VM_MD_IOCTLS		\
+	VM_ATTACH_APLIC,	\
+	VM_ASSERT_IRQ,		\
+	VM_DEASSERT_IRQ,	\
+	VM_RAISE_MSI
+
+const cap_ioctl_t vm_ioctl_cmds[] = {
+	VM_COMMON_IOCTLS,
+	VM_MD_IOCTLS,
+};
+size_t vm_ioctl_ncmds = nitems(vm_ioctl_cmds);
+
+int
+vm_attach_aplic(struct vmctx *ctx, uint64_t mem_start, size_t mem_size)
+{
+	struct vm_aplic_descr aplic;
+
+	bzero(&aplic, sizeof(aplic));
+	aplic.mem_start = mem_start;
+	aplic.mem_size = mem_size;
+
+	return (ioctl(ctx->fd, VM_ATTACH_APLIC, &aplic));
+}
+
+int
+vm_assert_irq(struct vmctx *ctx, uint32_t irq)
+{
+	struct vm_irq vi;
+
+	bzero(&vi, sizeof(vi));
+	vi.irq = irq;
+
+	return (ioctl(ctx->fd, VM_ASSERT_IRQ, &vi));
+}
+
+int
+vm_deassert_irq(struct vmctx *ctx, uint32_t irq)
+{
+	struct vm_irq vi;
+
+	bzero(&vi, sizeof(vi));
+	vi.irq = irq;
+
+	return (ioctl(ctx->fd, VM_DEASSERT_IRQ, &vi));
+}
+
+int
+vm_raise_msi(struct vmctx *ctx, uint64_t addr, uint64_t msg,
+    int bus, int slot, int func)
+{
+	struct vm_msi vmsi;
+
+	bzero(&vmsi, sizeof(vmsi));
+	vmsi.addr = addr;
+	vmsi.msg = msg;
+	vmsi.bus = bus;
+	vmsi.slot = slot;
+	vmsi.func = func;
+
+	return (ioctl(ctx->fd, VM_RAISE_MSI, &vmsi));
+}
+
+int
+vm_inject_exception(struct vcpu *vcpu, uint64_t scause)
+{
+	struct vm_exception vmexc;
+
+	bzero(&vmexc, sizeof(vmexc));
+	vmexc.scause = scause;
+
+	return (vcpu_ioctl(vcpu, VM_INJECT_EXCEPTION, &vmexc));
+}
diff --git a/lib/libvmmapi/vmmapi.h b/lib/libvmmapi/vmmapi.h
index d2a217c4d2e9..0ea1d5824271 100644
--- a/lib/libvmmapi/vmmapi.h
+++ b/lib/libvmmapi/vmmapi.h
@@ -161,12 +161,17 @@ int	vm_suspend(struct vmctx *ctx, enum vm_suspend_how how);
 int	vm_reinit(struct vmctx *ctx);
 int	vm_raise_msi(struct vmctx *ctx, uint64_t addr, uint64_t msg,
     int bus, int slot, int func);
-#ifdef __aarch64__
+#if defined(__aarch64__)
 int	vm_attach_vgic(struct vmctx *ctx, uint64_t dist_start, size_t dist_size,
     uint64_t redist_start, size_t redist_size);
+int	vm_inject_exception(struct vcpu *vcpu, uint64_t esr, uint64_t far);
+#elif defined(__riscv)
+int	vm_attach_aplic(struct vmctx *ctx, uint64_t mem_start, size_t mem_size);
+int	vm_inject_exception(struct vcpu *vcpu, uint64_t scause);
+#endif
+#if defined(__aarch64__) || defined(__riscv)
 int	vm_assert_irq(struct vmctx *ctx, uint32_t irq);
 int	vm_deassert_irq(struct vmctx *ctx, uint32_t irq);
-int	vm_inject_exception(struct vcpu *vcpu, uint64_t esr, uint64_t far);
 #endif
 #ifdef __amd64__
 int	vm_apicid2vcpu(struct vmctx *ctx, int apicid);
diff --git a/usr.sbin/Makefile.riscv b/usr.sbin/Makefile.riscv
new file mode 100644
index 000000000000..a8897983059c
--- /dev/null
+++ b/usr.sbin/Makefile.riscv
@@ -0,0 +1,2 @@
+SUBDIR+=	bhyve
+SUBDIR+=	bhyvectl
diff --git a/usr.sbin/bhyve/pci_emul.c b/usr.sbin/bhyve/pci_emul.c
index b61f29aa6830..2f04a488d9c1 100644
--- a/usr.sbin/bhyve/pci_emul.c
+++ b/usr.sbin/bhyve/pci_emul.c
@@ -136,7 +136,7 @@ static TAILQ_HEAD(boot_list, boot_device) boot_devices = TAILQ_HEAD_INITIALIZER(
  * change this address without changing it in OVMF.
  */
 #define	PCI_EMUL_MEMBASE32	0xc0000000
-#elif defined(__aarch64__)
+#elif defined(__aarch64__) || defined(__riscv)
 #define	PCI_EMUL_IOBASE		0xdf000000UL
 #define	PCI_EMUL_IOLIMIT	0xe0000000UL
 #define	PCI_EMUL_MEMBASE32	0xa0000000UL
diff --git a/usr.sbin/bhyve/pci_irq.h b/usr.sbin/bhyve/pci_irq.h
index 8b556ddc91a2..8078583add52 100644
--- a/usr.sbin/bhyve/pci_irq.h
+++ b/usr.sbin/bhyve/pci_irq.h
@@ -36,6 +36,8 @@ struct pci_devinst;
 #include "amd64/pci_irq_machdep.h"
 #elif defined(__aarch64__)
 #include "aarch64/pci_irq_machdep.h"
+#elif defined(__riscv)
+#include "riscv/pci_irq_machdep.h"
 #else
 #error Unsupported platform
 #endif
diff --git a/usr.sbin/bhyve/riscv/Makefile.inc b/usr.sbin/bhyve/riscv/Makefile.inc
new file mode 100644
index 000000000000..2b57201c6259
--- /dev/null
+++ b/usr.sbin/bhyve/riscv/Makefile.inc
@@ -0,0 +1,7 @@
+SRCS+=			\
+	fdt.c
+
+.PATH:  ${BHYVE_SYSDIR}/sys/riscv/vmm
+SRCS+=	vmm_instruction_emul.c
+
+BHYVE_FDT_SUPPORT=
diff --git a/usr.sbin/bhyve/riscv/bhyverun_machdep.c b/usr.sbin/bhyve/riscv/bhyverun_machdep.c
new file mode 100644
index 000000000000..39d6a7cdf231
--- /dev/null
+++ b/usr.sbin/bhyve/riscv/bhyverun_machdep.c
@@ -0,0 +1,357 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011 NetApp, Inc.
+ * All rights reserved.
+ * Copyright (c) 2024 Ruslan Bukin <br@bsdpad.com>
+ *
+ * This software was developed by the University of Cambridge Computer
+ * Laboratory (Department of Computer Science and Technology) under Innovate
+ * UK project 105694, "Digital Security by Design (DSbD) Technology Platform
+ * Prototype".
+ *
+ * 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 NETAPP, INC ``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 NETAPP, INC 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/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <vmmapi.h>
+
+#include "bhyverun.h"
+#include "config.h"
+#include "debug.h"
+#include "fdt.h"
+#include "mem.h"
+#include "pci_emul.h"
+#include "pci_irq.h"
+#include "uart_emul.h"
+#include "riscv.h"
+
+#define	FDT_SIZE	(64 * 1024)
+#define	FDT_DTB_ALIGN	8
+
+/* Start of lowmem + 64K */
+#define	UART_MMIO_BASE	0x10000
+#define	UART_MMIO_SIZE	0x1000
+#define	UART_INTR	1
+
+#define	APLIC_MEM_BASE		0x2f000000
+#define	APLIC_MEM_SIZE		0x10000
+
+#define	PCIE_INTA	2
+#define	PCIE_INTB	3
+#define	PCIE_INTC	4
+#define	PCIE_INTD	5
+
+void
+bhyve_init_config(void)
+{
+	init_config();
+
+	/* Set default values prior to option parsing. */
+	set_config_bool("acpi_tables", false);
+	set_config_bool("acpi_tables_in_memory", false);
+	set_config_value("memory.size", "256M");
+}
+
+void
+bhyve_usage(int code)
+{
+	const char *progname;
+
+	progname = getprogname();
+
+	fprintf(stderr,
+	    "Usage: %s [-CDHhSW]\n"
+	    "       %*s [-c [[cpus=]numcpus][,sockets=n][,cores=n][,threads=n]]\n"
+	    "       %*s [-k config_file] [-m mem] [-o var=value]\n"
+	    "       %*s [-p vcpu:hostcpu] [-r file] [-s pci] [-U uuid] vmname\n"
+	    "       -C: include guest memory in core file\n"
+	    "       -c: number of CPUs and/or topology specification\n"
+	    "       -D: destroy on power-off\n"
+	    "       -h: help\n"
+	    "       -k: key=value flat config file\n"
+	    "       -m: memory size\n"
+	    "       -o: set config 'var' to 'value'\n"
+	    "       -p: pin 'vcpu' to 'hostcpu'\n"
+	    "       -S: guest memory cannot be swapped\n"
+	    "       -s: <slot,driver,configinfo> PCI slot config\n"
+	    "       -U: UUID\n"
+	    "       -W: force virtio to use single-vector MSI\n",
+	    progname, (int)strlen(progname), "", (int)strlen(progname), "",
+	    (int)strlen(progname), "");
+	exit(code);
+}
+
+void
+bhyve_optparse(int argc, char **argv)
+{
+	const char *optstr;
+	int c;
+
+	optstr = "hCDSWk:f:o:p:c:s:m:U:";
+	while ((c = getopt(argc, argv, optstr)) != -1) {
+		switch (c) {
+		case 'c':
+			if (bhyve_topology_parse(optarg) != 0) {
+				errx(EX_USAGE, "invalid cpu topology '%s'",
+				    optarg);
+			}
+			break;
+		case 'C':
+			set_config_bool("memory.guest_in_core", true);
+			break;
+		case 'D':
+			set_config_bool("destroy_on_poweroff", true);
+			break;
+		case 'k':
+			bhyve_parse_simple_config_file(optarg);
+			break;
+		case 'm':
+			set_config_value("memory.size", optarg);
+			break;
+		case 'o':
+			if (!bhyve_parse_config_option(optarg)) {
+				errx(EX_USAGE,
+				    "invalid configuration option '%s'",
+				    optarg);
+			}
+			break;
+		case 'p':
+			if (bhyve_pincpu_parse(optarg) != 0) {
+				errx(EX_USAGE,
+				    "invalid vcpu pinning configuration '%s'",
+				    optarg);
+			}
+			break;
+		case 's':
+			if (strncmp(optarg, "help", strlen(optarg)) == 0) {
+				pci_print_supported_devices();
+				exit(0);
+			} else if (pci_parse_slot(optarg) != 0)
+				exit(4);
+			else
+				break;
+		case 'S':
+			set_config_bool("memory.wired", true);
+			break;
+		case 'U':
+			set_config_value("uuid", optarg);
+			break;
+		case 'W':
+			set_config_bool("virtio_msix", false);
+			break;
+		case 'h':
+			bhyve_usage(0);
+		default:
+			bhyve_usage(1);
+		}
+	}
+}
+
+void
+bhyve_init_vcpu(struct vcpu *vcpu __unused)
+{
+}
+
+void
+bhyve_start_vcpu(struct vcpu *vcpu, bool bsp __unused)
+{
+	int error;
+
+	/* Set hart ID. */
+	error = vm_set_register(vcpu, VM_REG_GUEST_A0, vcpu_id(vcpu));
+	assert(error == 0);
+
+	fbsdrun_addcpu(vcpu_id(vcpu));
+}
+
+/*
+ * Load the specified boot code at the beginning of high memory.
+ */
+static void
+load_bootrom(struct vmctx *ctx, const char *path, uint64_t *elrp,
+    uint64_t *lenp)
+{
+	struct stat sb;
+	void *data, *gptr;
+	vm_paddr_t loadaddr;
+	off_t size;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		err(1, "open(%s)", path);
+	if (fstat(fd, &sb) != 0)
+		err(1, "fstat(%s)", path);
+
+	size = sb.st_size;
+
+	loadaddr = vm_get_highmem_base(ctx);
+	gptr = vm_map_gpa(ctx, loadaddr, round_page(size));
+
+	data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (data == MAP_FAILED)
+		err(1, "mmap(%s)", path);
+	(void)close(fd);
+	memcpy(gptr, data, size);
+
+	if (munmap(data, size) != 0)
+		err(1, "munmap(%s)", path);
+
+	*elrp = loadaddr;
+	*lenp = size;
+}
+
+static void
+mmio_uart_intr_assert(void *arg)
+{
+	struct vmctx *ctx = arg;
+
+	vm_assert_irq(ctx, UART_INTR);
+}
+
+static void
+mmio_uart_intr_deassert(void *arg)
+{
+	struct vmctx *ctx = arg;
+
+	vm_deassert_irq(ctx, UART_INTR);
+}
+
+static int
+mmio_uart_mem_handler(struct vcpu *vcpu __unused, int dir, uint64_t addr,
+    int size __unused, uint64_t *val, void *arg1, long arg2)
+{
+	struct uart_ns16550_softc *sc = arg1;
+	long reg;
+
+	reg = addr - arg2;
+	if (dir == MEM_F_WRITE)
+		uart_ns16550_write(sc, reg, *val);
+	else
+		*val = uart_ns16550_read(sc, reg);
+
+	return (0);
+}
+
+static bool
+init_mmio_uart(struct vmctx *ctx)
+{
+	struct uart_ns16550_softc *sc;
+	struct mem_range mr;
+	const char *path;
+	int error;
+
+	path = get_config_value("console");
+	if (path == NULL)
+		return (false);
+
+	sc = uart_ns16550_init(mmio_uart_intr_assert, mmio_uart_intr_deassert,
+	    ctx);
+	if (uart_ns16550_tty_open(sc, path) != 0) {
+		EPRINTLN("Unable to initialize backend '%s' for mmio uart",
+		    path);
+		assert(0);
+	}
+
+	bzero(&mr, sizeof(struct mem_range));
+	mr.name = "uart";
+	mr.base = UART_MMIO_BASE;
+	mr.size = UART_MMIO_SIZE;
+	mr.flags = MEM_F_RW;
+	mr.handler = mmio_uart_mem_handler;
+	mr.arg1 = sc;
+	mr.arg2 = mr.base;
+	error = register_mem(&mr);
+	assert(error == 0);
+
+	return (true);
+}
+
+int
+bhyve_init_platform(struct vmctx *ctx, struct vcpu *bsp)
+{
+	const char *bootrom;
+	uint64_t elr;
+	uint64_t len;
+	int error;
+	int pcie_intrs[4] = {PCIE_INTA, PCIE_INTB, PCIE_INTC, PCIE_INTD};
+	vm_paddr_t fdt_gpa;
+
+	bootrom = get_config_value("bootrom");
+	if (bootrom == NULL) {
+		warnx("no bootrom specified");
+		return (ENOENT);
+	}
+	load_bootrom(ctx, bootrom, &elr, &len);
+	error = vm_set_register(bsp, VM_REG_GUEST_SEPC, elr);
+	if (error != 0) {
+		warn("vm_set_register(GUEST_SEPC)");
+		return (error);
+	}
+
+	fdt_gpa = vm_get_highmem_base(ctx) + roundup2(len, FDT_DTB_ALIGN);
+	error = fdt_init(ctx, guest_ncpus, fdt_gpa, FDT_SIZE);
+	if (error != 0)
+		return (error);
+
+	/* Set FDT base address to the bootable hart. */
+	error = vm_set_register(bsp, VM_REG_GUEST_A1, fdt_gpa);
+	assert(error == 0);
+
+	fdt_add_aplic(APLIC_MEM_BASE, APLIC_MEM_SIZE);
+	error = vm_attach_aplic(ctx, APLIC_MEM_BASE, APLIC_MEM_SIZE);
+	if (error != 0) {
+		warn("vm_attach_aplic()");
+		return (error);
+	}
+
+	if (init_mmio_uart(ctx))
+		fdt_add_uart(UART_MMIO_BASE, UART_MMIO_SIZE, UART_INTR);
+
+	pci_irq_init(pcie_intrs);
+	fdt_add_pcie(pcie_intrs);
+	vmexit_set_bsp(vcpu_id(bsp));
+
+	return (0);
+}
+
+int
+bhyve_init_platform_late(struct vmctx *ctx __unused, struct vcpu *bsp __unused)
+{
+
+	fdt_finalize();
+
+	return (0);
+}
diff --git a/usr.sbin/bhyve/riscv/fdt.c b/usr.sbin/bhyve/riscv/fdt.c
new file mode 100644
index 000000000000..54b75c68ea76
--- /dev/null
+++ b/usr.sbin/bhyve/riscv/fdt.c
@@ -0,0 +1,326 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 The FreeBSD Foundation
+ * Copyright (c) 2024 Ruslan Bukin <br@bsdpad.com>
+ *
+ * This software was developed by Andrew Turner under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * This software was developed by the University of Cambridge Computer
+ * Laboratory (Department of Computer Science and Technology) under Innovate
+ * UK project 105694, "Digital Security by Design (DSbD) Technology Platform
+ * Prototype".
+ *
+ * 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 <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <libfdt.h>
+#include <vmmapi.h>
+
+#include "config.h"
+#include "bhyverun.h"
+#include "fdt.h"
+
+#define	SET_PROP_U32(prop, idx, val)	\
+    ((uint32_t *)(prop))[(idx)] = cpu_to_fdt32(val)
+#define	SET_PROP_U64(prop, idx, val)	\
+    ((uint64_t *)(prop))[(idx)] = cpu_to_fdt64(val)
+
+#define	IRQ_TYPE_LEVEL_HIGH	4
+#define	IRQ_TYPE_LEVEL_LOW	8
+
+static void *fdtroot;
+static uint32_t aplic_phandle = 0;
+static uint32_t intc0_phandle = 0;
+
+static uint32_t
+assign_phandle(void *fdt)
+{
+	static uint32_t next_phandle = 1;
+	uint32_t phandle;
+
+	phandle = next_phandle;
+	next_phandle++;
+	fdt_property_u32(fdt, "phandle", phandle);
+
+	return (phandle);
+}
+
+static void
+set_single_reg(void *fdt, uint64_t start, uint64_t len)
+{
+	void *reg;
+
+	fdt_property_placeholder(fdt, "reg", 2 * sizeof(uint64_t), &reg);
+	SET_PROP_U64(reg, 0, start);
+	SET_PROP_U64(reg, 1, len);
+}
+
+static void
+add_cpu(void *fdt, int cpuid)
+{
+	char node_name[16];
+
+	snprintf(node_name, sizeof(node_name), "cpu@%d", cpuid);
+
+	fdt_begin_node(fdt, node_name);
+	fdt_property_string(fdt, "device_type", "cpu");
+	fdt_property_string(fdt, "compatible", "riscv");
+	fdt_property_u32(fdt, "reg", cpuid);
+	fdt_property_string(fdt, "riscv,isa", "rv64imafdc_sstc");
+	fdt_property_string(fdt, "mmu-type", "riscv,sv39");
+	fdt_property_string(fdt, "clock-frequency", "1000000000");
+
+	fdt_begin_node(fdt, "interrupt-controller");
+	intc0_phandle = assign_phandle(fdt);
+	fdt_property_u32(fdt, "#address-cells", 2);
+	fdt_property_u32(fdt, "#interrupt-cells", 1);
+	fdt_property(fdt, "interrupt-controller", NULL, 0);
+	fdt_property_string(fdt, "compatible", "riscv,cpu-intc");
+	fdt_end_node(fdt);
+
+	fdt_end_node(fdt);
+}
+
+static void
+add_cpus(void *fdt, int ncpu)
+{
+	int cpuid;
+
+	fdt_begin_node(fdt, "cpus");
+	/* XXX: Needed given the root #address-cells? */
+	fdt_property_u32(fdt, "#address-cells", 1);
+	fdt_property_u32(fdt, "#size-cells", 0);
+	fdt_property_u32(fdt, "timebase-frequency", 10000000);
+
+	for (cpuid = 0; cpuid < ncpu; cpuid++) {
+		add_cpu(fdt, cpuid);
+	}
+	fdt_end_node(fdt);
+}
+
+int
+fdt_init(struct vmctx *ctx, int ncpu, vm_paddr_t fdtaddr, vm_size_t fdtsize)
+{
+	void *fdt;
+	const char *bootargs;
+
+	fdt = paddr_guest2host(ctx, fdtaddr, fdtsize);
+	if (fdt == NULL)
+		return (EFAULT);
+
+	fdt_create(fdt, (int)fdtsize);
+
+	/* Add the memory reserve map (needed even if none is reserved) */
+	fdt_finish_reservemap(fdt);
+
+	/* Create the root node */
+	fdt_begin_node(fdt, "");
+
+	fdt_property_string(fdt, "compatible", "freebsd,bhyve");
+	fdt_property_u32(fdt, "#address-cells", 2);
+	fdt_property_u32(fdt, "#size-cells", 2);
+
+	fdt_begin_node(fdt, "chosen");
+	fdt_property_string(fdt, "stdout-path", "serial0:115200n8");
+	bootargs = get_config_value("fdt.bootargs");
+	if (bootargs != NULL)
+		fdt_property_string(fdt, "bootargs", bootargs);
+	fdt_end_node(fdt);
+
+	fdt_begin_node(fdt, "memory");
+	fdt_property_string(fdt, "device_type", "memory");
+	/* There is no lowmem on riscv. */
+	assert(vm_get_lowmem_size(ctx) == 0);
+	set_single_reg(fdt, vm_get_highmem_base(ctx), vm_get_highmem_size(ctx));
+	fdt_end_node(fdt);
+
+	add_cpus(fdt, ncpu);
+
+	/* Finalized by fdt_finalized(). */
+	fdtroot = fdt;
+
+	return (0);
+}
+
+void
+fdt_add_aplic(uint64_t mem_base, uint64_t mem_size)
+{
+	char node_name[32];
+	void *fdt, *prop;
+
+	fdt = fdtroot;
+
+	snprintf(node_name, sizeof(node_name), "interrupt-controller@%lx",
+	    (unsigned long)mem_base);
+	fdt_begin_node(fdt, node_name);
+
+	aplic_phandle = assign_phandle(fdt);
+	fdt_property_string(fdt, "compatible", "riscv,aplic");
+	fdt_property(fdt, "interrupt-controller", NULL, 0);
+#if notyet
+	fdt_property(fdt, "msi-controller", NULL, 0);
+#endif
+	/* XXX: Needed given the root #address-cells? */
+	fdt_property_u32(fdt, "#address-cells", 2);
+	fdt_property_u32(fdt, "#interrupt-cells", 2);
+	fdt_property_placeholder(fdt, "reg", 2 * sizeof(uint64_t), &prop);
+	SET_PROP_U64(prop, 0, mem_base);
+	SET_PROP_U64(prop, 1, mem_size);
+
+	fdt_property_placeholder(fdt, "interrupts-extended",
+	    2 * sizeof(uint32_t), &prop);
+	SET_PROP_U32(prop, 0, intc0_phandle);
+	SET_PROP_U32(prop, 1, 9);
+	fdt_property_u32(fdt, "riscv,num-sources", 63);
+
+	fdt_end_node(fdt);
+
+	fdt_property_u32(fdt, "interrupt-parent", aplic_phandle);
+}
+
+void
+fdt_add_uart(uint64_t uart_base, uint64_t uart_size, int intr)
+{
+	void *fdt, *interrupts;
+	char node_name[32];
+
+	assert(aplic_phandle != 0);
+
+	fdt = fdtroot;
+
+	snprintf(node_name, sizeof(node_name), "serial@%lx", uart_base);
+	fdt_begin_node(fdt, node_name);
+	fdt_property_string(fdt, "compatible", "ns16550");
+	set_single_reg(fdt, uart_base, uart_size);
+	fdt_property_u32(fdt, "interrupt-parent", aplic_phandle);
+	fdt_property_placeholder(fdt, "interrupts", 2 * sizeof(uint32_t),
+	    &interrupts);
+	SET_PROP_U32(interrupts, 0, intr);
+	SET_PROP_U32(interrupts, 1, IRQ_TYPE_LEVEL_HIGH);
+
+	fdt_end_node(fdt);
+
+	snprintf(node_name, sizeof(node_name), "/serial@%lx", uart_base);
+	fdt_begin_node(fdt, "aliases");
+	fdt_property_string(fdt, "serial0", node_name);
+	fdt_end_node(fdt);
+}
+
+void
+fdt_add_pcie(int intrs[static 4])
+{
+	void *fdt, *prop;
+	int slot, pin, intr, i;
+
+	assert(aplic_phandle != 0);
+
+	fdt = fdtroot;
+
+	fdt_begin_node(fdt, "pcie@1f0000000");
+	fdt_property_string(fdt, "compatible", "pci-host-ecam-generic");
+	fdt_property_u32(fdt, "#address-cells", 3);
+	fdt_property_u32(fdt, "#size-cells", 2);
+	fdt_property_string(fdt, "device_type", "pci");
+	fdt_property_u64(fdt, "bus-range", (0ul << 32) | 1);
+	set_single_reg(fdt, 0xe0000000, 0x10000000);
+	fdt_property_placeholder(fdt, "ranges",
+	    2 * 7 * sizeof(uint32_t), &prop);
+	SET_PROP_U32(prop, 0, 0x01000000);
+
+	SET_PROP_U32(prop, 1, 0);
+	SET_PROP_U32(prop, 2, 0xdf000000);
+
+	SET_PROP_U32(prop, 3, 0);
+	SET_PROP_U32(prop, 4, 0xdf000000);
+
+	SET_PROP_U32(prop, 5, 0);
+	SET_PROP_U32(prop, 6, 0x01000000);
+
+	SET_PROP_U32(prop, 7, 0x02000000);
+
+	SET_PROP_U32(prop, 8, 0);
+	SET_PROP_U32(prop, 9, 0xa0000000);
+
+	SET_PROP_U32(prop, 10, 0);
+	SET_PROP_U32(prop, 11, 0xa0000000);
+
+	SET_PROP_U32(prop, 12, 0);
+	SET_PROP_U32(prop, 13, 0x3f000000);
+
+#if notyet
+	fdt_property_placeholder(fdt, "msi-map", 4 * sizeof(uint32_t), &prop);
+	SET_PROP_U32(prop, 0, 0);		/* RID base */
+	SET_PROP_U32(prop, 1, aplic_phandle);	/* MSI parent */
+	SET_PROP_U32(prop, 2, 0);		/* MSI base */
+	SET_PROP_U32(prop, 3, 0x10000);		/* RID length */
+	fdt_property_u32(fdt, "msi-parent", aplic_phandle);
+#endif
+
+	fdt_property_u32(fdt, "#interrupt-cells", 1);
+	fdt_property_u32(fdt, "interrupt-parent", aplic_phandle);
+
+	/*
+	 * Describe standard swizzled interrupts routing (pins rotated by one
+	 * for each consecutive slot). Must match pci_irq_route().
+	 */
+	fdt_property_placeholder(fdt, "interrupt-map-mask",
+	    4 * sizeof(uint32_t), &prop);
+	SET_PROP_U32(prop, 0, 3 << 11);
+	SET_PROP_U32(prop, 1, 0);
+	SET_PROP_U32(prop, 2, 0);
+	SET_PROP_U32(prop, 3, 7);
+	fdt_property_placeholder(fdt, "interrupt-map",
+	    16 * 9 * sizeof(uint32_t), &prop);
+	for (i = 0; i < 16; ++i) {
+		pin = i % 4;
+		slot = i / 4;
+		intr = intrs[(pin + slot) % 4];
+		SET_PROP_U32(prop, 10 * i + 0, slot << 11);
+		SET_PROP_U32(prop, 10 * i + 1, 0);
+		SET_PROP_U32(prop, 10 * i + 2, 0);
+		SET_PROP_U32(prop, 10 * i + 3, pin + 1);
+		SET_PROP_U32(prop, 10 * i + 4, aplic_phandle);
+		SET_PROP_U32(prop, 10 * i + 5, 0);
+		SET_PROP_U32(prop, 10 * i + 6, 0);
+		SET_PROP_U32(prop, 10 * i + 7, intr);
+		SET_PROP_U32(prop, 10 * i + 8, IRQ_TYPE_LEVEL_HIGH);
+	}
+
+	fdt_end_node(fdt);
+}
+
+void
+fdt_finalize(void)
+{
+	fdt_end_node(fdtroot);
+
+	fdt_finish(fdtroot);
+}
diff --git a/usr.sbin/bhyve/riscv/fdt.h b/usr.sbin/bhyve/riscv/fdt.h
new file mode 100644
index 000000000000..9bebe6ffa29d
--- /dev/null
+++ b/usr.sbin/bhyve/riscv/fdt.h
@@ -0,0 +1,45 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 The FreeBSD Foundation
+ *
+ * This software was developed by Andrew Turner under sponsorship from
+ * the FreeBSD Foundation.
+ *
+ * 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.
+ */
+
+#ifndef _FDT_H_
+#define	_FDT_H_
+
*** 653 LINES SKIPPED ***