Index: lib/Makefile
===================================================================
--- lib/Makefile
+++ lib/Makefile
@@ -206,7 +206,8 @@
 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
 
Index: lib/libvmmapi/riscv/Makefile.inc
===================================================================
--- /dev/null
+++ lib/libvmmapi/riscv/Makefile.inc
@@ -0,0 +1 @@
+SRCS+=	vmmapi_machdep.c
Index: lib/libvmmapi/riscv/vmmapi_machdep.c
===================================================================
--- /dev/null
+++ 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));
+}
Index: lib/libvmmapi/vmmapi.h
===================================================================
--- lib/libvmmapi/vmmapi.h
+++ lib/libvmmapi/vmmapi.h
@@ -161,12 +161,17 @@
 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);
Index: usr.sbin/Makefile.riscv
===================================================================
--- /dev/null
+++ usr.sbin/Makefile.riscv
@@ -0,0 +1,2 @@
+SUBDIR+=	bhyve
+SUBDIR+=	bhyvectl
Index: usr.sbin/bhyve/pci_emul.c
===================================================================
--- usr.sbin/bhyve/pci_emul.c
+++ usr.sbin/bhyve/pci_emul.c
@@ -135,7 +135,7 @@
  * 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
Index: usr.sbin/bhyve/pci_irq.h
===================================================================
--- usr.sbin/bhyve/pci_irq.h
+++ usr.sbin/bhyve/pci_irq.h
@@ -36,6 +36,8 @@
 #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
Index: usr.sbin/bhyve/riscv/Makefile.inc
===================================================================
--- /dev/null
+++ 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=
Index: usr.sbin/bhyve/riscv/bhyverun_machdep.c
===================================================================
--- /dev/null
+++ usr.sbin/bhyve/riscv/bhyverun_machdep.c
@@ -0,0 +1,363 @@
+/*-
+ * 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"
+
+/* Start of mem + 32M */
+#define	FDT_BASE	0x2000000
+#define	FDT_SIZE	(64 * 1024)
+
+/* 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)
+{
+	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;
+}
+
+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);
+}
+
+static vm_paddr_t
+fdt_gpa(struct vmctx *ctx)
+{
+	return (vm_get_highmem_base(ctx) + FDT_BASE);
+}
+
+int
+bhyve_init_platform(struct vmctx *ctx, struct vcpu *bsp)
+{
+	const char *bootrom;
+	uint64_t elr;
+	int error;
+	int pcie_intrs[4] = {PCIE_INTA, PCIE_INTB, PCIE_INTC, PCIE_INTD};
+
+	bootrom = get_config_value("bootrom");
+	if (bootrom == NULL) {
+		warnx("no bootrom specified");
+		return (ENOENT);
+	}
+	load_bootrom(ctx, bootrom, &elr);
+	error = vm_set_register(bsp, VM_REG_GUEST_SEPC, elr);
+	if (error != 0) {
+		warn("vm_set_register(GUEST_SEPC)");
+		return (error);
+	}
+
+	error = fdt_init(ctx, guest_ncpus, fdt_gpa(ctx), FDT_SIZE);
+	if (error != 0)
+		return (error);
+
+	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);
+
+	return (0);
+}
+
+int
+bhyve_init_platform_late(struct vmctx *ctx, struct vcpu *bsp)
+{
+	int error;
+
+	fdt_finalize();
+
+	/* Set hart ID. */
+	error = vm_set_register(bsp, VM_REG_GUEST_A0, 0);
+	assert(error == 0);
+
+	/* Set FDT base address. */
+	error = vm_set_register(bsp, VM_REG_GUEST_A1, fdt_gpa(ctx));
+	assert(error == 0);
+
+	return (0);
+}
Index: usr.sbin/bhyve/riscv/fdt.h
===================================================================
--- /dev/null
+++ 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_
+
+#include <sys/types.h>
+
+struct vmctx;
+
+int	fdt_init(struct vmctx *ctx, int ncpu, vm_paddr_t addrp,
+	    vm_size_t size);
+void	fdt_add_aplic(uint64_t dist_base, uint64_t dist_size);
+void	fdt_add_pcie(int intrs[static 4]);
+void	fdt_add_uart(uint64_t uart_base, uint64_t uart_size, int intr);
+void	fdt_finalize(void);
+
+#endif	/* _FDT_H_ */
Index: usr.sbin/bhyve/riscv/fdt.c
===================================================================
--- /dev/null
+++ 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", "rv64imafdch_zicntr_zihpm_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);
+}
Index: usr.sbin/bhyve/riscv/pci_irq.c
===================================================================
--- /dev/null
+++ usr.sbin/bhyve/riscv/pci_irq.c
@@ -0,0 +1,66 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Jessica Clarke <jrtc27@FreeBSD.org>
+ *
+ * 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 <vmmapi.h>
+
+#include "pci_emul.h"
+#include "pci_irq.h"
+
+static int aplic_irqs[4];
+
+void
+pci_irq_init(int intrs[static 4])
+{
+	int i;
+
+	for (i = 0; i < 4; ++i)
+		aplic_irqs[i] = intrs[i];
+}
+
+void
+pci_irq_assert(struct pci_devinst *pi)
+{
+	vm_assert_irq(pi->pi_vmctx, pi->pi_lintr.irq.aplic_irq);
+}
+
+void
+pci_irq_deassert(struct pci_devinst *pi)
+{
+	vm_deassert_irq(pi->pi_vmctx, pi->pi_lintr.irq.aplic_irq);
+}
+
+void
+pci_irq_route(struct pci_devinst *pi, struct pci_irq *irq)
+{
+	/*
+	 * Assign swizzled IRQ for this INTx if one is not yet assigned. Must
+	 * match fdt_add_pcie().
+	 */
+	if (irq->aplic_irq == 0)
+		irq->aplic_irq =
+		    aplic_irqs[(pi->pi_slot + pi->pi_lintr.pin - 1) % 4];
+}
Index: usr.sbin/bhyve/riscv/pci_irq_machdep.h
===================================================================
--- /dev/null
+++ usr.sbin/bhyve/riscv/pci_irq_machdep.h
@@ -0,0 +1,49 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Jessica Clarke <jrtc27@FreeBSD.org>
+ *
+ * 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 __PCI_IRQ_MD_H__
+#define	__PCI_IRQ_MD_H__
+
+struct pci_irq {
+	int	aplic_irq;
+};
+
+void	pci_irq_init(int intrs[static 4]);
+
+static inline void
+pci_irq_init_irq(struct pci_irq *irq)
+{
+	irq->aplic_irq = 0;
+}
+
+static inline uint8_t
+pci_irq_intline(struct pci_irq *irq __unused)
+{
+	return (255);
+}
+
+#endif
Index: usr.sbin/bhyve/riscv/vmexit.c
===================================================================
--- /dev/null
+++ usr.sbin/bhyve/riscv/vmexit.c
@@ -0,0 +1,345 @@
+/*-
+ * 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/types.h>
+#include <sys/cpuset.h>
+
+#include <machine/riscvreg.h>
+#include <machine/cpu.h>
+#include <machine/sbi.h>
+#include <machine/vmm.h>
+#include <machine/vmm_dev.h>
+#include <machine/vmm_instruction_emul.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <vmmapi.h>
+
+#include "bhyverun.h"
+#include "config.h"
+#include "debug.h"
+#include "mem.h"
+#include "vmexit.h"
+
+#define	BHYVE_VERSION	((uint64_t)__FreeBSD_version)
+
+static cpuset_t running_cpumask;
+
+static int
+vmexit_inst_emul(struct vmctx *ctx __unused, struct vcpu *vcpu,
+    struct vm_run *vmrun)
+{
+	struct vm_exit *vme;
+	struct vie *vie;
+	int err;
+
+	vme = vmrun->vm_exit;
+	vie = &vme->u.inst_emul.vie;
+
+	err = emulate_mem(vcpu, vme->u.inst_emul.gpa, vie,
+	    &vme->u.inst_emul.paging);
+	if (err) {
+		if (err == ESRCH) {
+			EPRINTLN("Unhandled memory access to 0x%lx\n",
+			    vme->u.inst_emul.gpa);
+		}
+		goto fail;
+	}
+
+	return (VMEXIT_CONTINUE);
+
+fail:
+	fprintf(stderr, "Failed to emulate instruction ");
+	FPRINTLN(stderr, "at 0x%lx", vme->pc);
+	return (VMEXIT_ABORT);
+}
+
+static int
+vmexit_suspend(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
+{
+	struct vm_exit *vme;
+	enum vm_suspend_how how;
+	int vcpuid = vcpu_id(vcpu);
+
+	vme = vmrun->vm_exit;
+	how = vme->u.suspended.how;
+
+	fbsdrun_deletecpu(vcpuid);
+
+	switch (how) {
+	case VM_SUSPEND_RESET:
+		exit(0);
+	case VM_SUSPEND_POWEROFF:
+		if (get_config_bool_default("destroy_on_poweroff", false))
+			vm_destroy(ctx);
+		exit(1);
+	case VM_SUSPEND_HALT:
+		exit(2);
+	default:
+		fprintf(stderr, "vmexit_suspend: invalid reason %d\n", how);
+		exit(100);
+	}
+
+	/* NOT REACHED. */
+
+	return (0);
+}
+
+static int
+vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
+    struct vm_run *vmrun __unused)
+{
+
+	return (VMEXIT_CONTINUE);
+}
+
+static int
+vmexit_bogus(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
+    struct vm_run *vmrun __unused)
+{
+
+	return (VMEXIT_CONTINUE);
+}
+
+static int
+vmm_sbi_probe_extension(int ext_id)
+{
+
+	switch (ext_id) {
+	case SBI_EXT_ID_HSM:
+	case SBI_EXT_ID_TIME:
+	case SBI_EXT_ID_IPI:
+	case SBI_EXT_ID_RFNC:
+	case SBI_EXT_ID_SRST:
+	case SBI_CONSOLE_PUTCHAR:
+	case SBI_CONSOLE_GETCHAR:
+		break;
+	default:
+		return (0);
+	}
+
+	return (1);
+}
+
+static int
+vmexit_ecall_time(struct vmctx *ctx __unused, struct vm_exit *vme __unused)
+{
+
+	return (0);
+}
+
+static int
+vmexit_ecall_hsm(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
+    struct vm_exit *vme)
+{
+	struct vcpu *newvcpu;
+	uint64_t hart_id;
+	int func_id;
+	int error;
+	int ret;
+
+	hart_id = vme->u.ecall.args[0];
+	func_id = vme->u.ecall.args[6];
+
+	ret = -1;
+
+	if (hart_id > (uint64_t)guest_ncpus)
+		return (ret);
+
+	newvcpu = fbsdrun_vcpu(hart_id);
+	assert(newvcpu != NULL);
+
+	switch (func_id) {
+	case SBI_HSM_HART_START:
+		if (CPU_ISSET(hart_id, &running_cpumask))
+			break;
+
+		/* Set hart ID. */
+		error = vm_set_register(newvcpu, VM_REG_GUEST_A0, hart_id);
+		assert(error == 0);
+
+		/* Set PC. */
+		error = vm_set_register(newvcpu, VM_REG_GUEST_SEPC,
+		    vme->u.ecall.args[1]);
+		assert(error == 0);
+
+		vm_resume_cpu(newvcpu);
+		CPU_SET_ATOMIC(hart_id, &running_cpumask);
+
+		ret = 0;
+		break;
+	case SBI_HSM_HART_STOP:
+		if (!CPU_ISSET(hart_id, &running_cpumask))
+			break;
+		CPU_CLR_ATOMIC(hart_id, &running_cpumask);
+		vm_suspend_cpu(newvcpu);
+		ret = 0;
+		break;
+	case SBI_HSM_HART_STATUS:
+		/* TODO. */
+		break;
+	default:
+		break;
+	}
+
+	error = vm_set_register(vcpu, VM_REG_GUEST_A0, ret);
+	assert(error == 0);
+
+	return (0);
+}
+
+static int
+vmexit_ecall_base(struct vmctx *ctx __unused, struct vcpu *vcpu,
+    struct vm_exit *vme)
+{
+	int sbi_function_id;
+	int ext_id;
+	int error;
+	uint32_t val;
+	int ret;
+
+	sbi_function_id = vme->u.ecall.args[6];
+
+	ret = 0;
+
+	switch (sbi_function_id) {
+	case SBI_BASE_GET_SPEC_VERSION:
+		val = 2 << SBI_SPEC_VERS_MAJOR_OFFSET;
+		val |= 0 << SBI_SPEC_VERS_MINOR_OFFSET;
+		break;
+	case SBI_BASE_GET_IMPL_ID:
+		val = SBI_IMPL_ID_BHYVE;
+		break;
+	case SBI_BASE_GET_IMPL_VERSION:
+		val = BHYVE_VERSION;
+		break;
+	case SBI_BASE_PROBE_EXTENSION:
+		ext_id = vme->u.ecall.args[0];
+		val = vmm_sbi_probe_extension(ext_id);
+		break;
+	case SBI_BASE_GET_MVENDORID:
+		val = MVENDORID_UNIMPL;
+		break;
+	case SBI_BASE_GET_MARCHID:
+		val = MARCHID_UNIMPL;
+		break;
+	case SBI_BASE_GET_MIMPID:
+		val = 0;
+		break;
+	default:
+		ret = 1;
+		break;
+	}
+
+	error = vm_set_register(vcpu, VM_REG_GUEST_A0, ret);
+	assert(error == 0);
+
+	if (ret == 0) {
+		error = vm_set_register(vcpu, VM_REG_GUEST_A1, val);
+		assert(error == 0);
+	}
+
+	return (0);
+}
+
+static void
+vmexit_ecall_srst(struct vmctx *ctx, struct vm_exit *vme)
+{
+	enum vm_suspend_how how;
+	int func_id;
+	int type;
+
+	func_id = vme->u.ecall.args[6];
+	type = vme->u.ecall.args[0];
+
+	switch (func_id) {
+	case SBI_SRST_SYSTEM_RESET:
+		switch (type) {
+		case SBI_SRST_TYPE_SHUTDOWN:
+		case SBI_SRST_TYPE_COLD_REBOOT:
+		case SBI_SRST_TYPE_WARM_REBOOT:
+			how = VM_SUSPEND_POWEROFF;
+			vm_suspend(ctx, how);
+			break;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+}
+
+static int
+vmexit_ecall(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
+{
+	int sbi_extension_id;
+	struct vm_exit *vme;
+
+	vme = vmrun->vm_exit;
+
+	sbi_extension_id = vme->u.ecall.args[7];
+	switch (sbi_extension_id) {
+	case SBI_EXT_ID_SRST:
+		vmexit_ecall_srst(ctx, vme);
+		break;
+	case SBI_EXT_ID_BASE:
+		vmexit_ecall_base(ctx, vcpu, vme);
+		break;
+	case SBI_EXT_ID_TIME:
+		vmexit_ecall_time(ctx, vme);
+		break;
+	case SBI_EXT_ID_HSM:
+		vmexit_ecall_hsm(ctx, vcpu, vme);
+		break;
+	case SBI_CONSOLE_PUTCHAR:
+	case SBI_CONSOLE_GETCHAR:
+	default:
+		/* Unknown SBI extension. */
+		break;
+	}
+
+	return (VMEXIT_CONTINUE);
+}
+
+const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
+	[VM_EXITCODE_BOGUS]  = vmexit_bogus,
+	[VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
+	[VM_EXITCODE_SUSPENDED] = vmexit_suspend,
+	[VM_EXITCODE_DEBUG] = vmexit_debug,
+	[VM_EXITCODE_ECALL] = vmexit_ecall,
+};
Index: usr.sbin/bhyvectl/riscv/Makefile.inc
===================================================================
--- /dev/null
+++ usr.sbin/bhyvectl/riscv/Makefile.inc
@@ -0,0 +1 @@
+SRCS+=	bhyvectl_machdep.c
Index: usr.sbin/bhyvectl/riscv/bhyvectl_machdep.c
===================================================================
--- /dev/null
+++ usr.sbin/bhyvectl/riscv/bhyvectl_machdep.c
@@ -0,0 +1,82 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Mark Johnston <markj@FreeBSD.org>
+ *
+ * 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/types.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <vmmapi.h>
+
+#include "bhyvectl.h"
+
+void
+bhyvectl_dump_vm_run_exitcode(struct vm_exit *vmexit __unused,
+    int vcpu __unused)
+{
+}
+
+struct option *
+bhyvectl_opts(const struct option *options, size_t count)
+{
+	struct option *all_opts;
+
+	all_opts = calloc(count + 1, sizeof(struct option));
+	if (all_opts == NULL)
+		err(1, "calloc");
+	memcpy(all_opts, options, count * sizeof(struct option));
+	return (all_opts);
+}
+
+void
+bhyvectl_handle_opt(const struct option *opts __unused, int opt __unused)
+{
+}
+
+const char *
+bhyvectl_opt_desc(int opt __unused)
+{
+	/* No riscv-specific options yet. */
+	return ("???");
+}
+
+void
+bhyvectl_md_main(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
+    int vcpuid __unused, bool get_all __unused)
+{
+}