[RFC PATCH 3/3] platform: virt: add QEMU virt WorldGuard hwiso mechanism

Raymond Mao raymondmaoca at gmail.com
Fri May 1 11:33:46 PDT 2026


From: Raymond Mao <raymond.mao at riscstar.com>

Implement the QEMU virt WorldGuard HWISO mechanism. Parse checker
subordinates and resource permissions from the FDT, program wgChecker
MMIO state at boot, parse per-hart CPU defaults plus per-domain
WorldGuard metadata, and switch MLWID, MWIDDELEG and SLWID on domain
transitions.

Signed-off-by: Raymond Mao <raymond.mao at riscstar.com>
---
 platform/generic/include/qemu_virt_wg.h     |   60 ++
 platform/generic/objects.mk                 |    1 +
 platform/generic/platform.c                 |   11 +
 platform/generic/virt/qemu_virt_wgchecker.c | 1050 +++++++++++++++++++
 4 files changed, 1122 insertions(+)
 create mode 100644 platform/generic/include/qemu_virt_wg.h
 create mode 100644 platform/generic/virt/qemu_virt_wgchecker.c

diff --git a/platform/generic/include/qemu_virt_wg.h b/platform/generic/include/qemu_virt_wg.h
new file mode 100644
index 00000000..c1685c0c
--- /dev/null
+++ b/platform/generic/include/qemu_virt_wg.h
@@ -0,0 +1,60 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao at riscstar.com>
+ */
+
+#ifndef __QEMU_VIRT_WG_H__
+#define __QEMU_VIRT_WG_H__
+
+#include <sbi/sbi_types.h>
+
+/*
+ * QEMU virt WorldGuard model definitions
+ */
+#define QEMU_VIRT_WG_COMPAT			"sifive,wgchecker2"
+#define QEMU_VIRT_WG_CPU_COMPAT			"riscv,wgcpu"
+#define QEMU_VIRT_WG_CPU_NODE			"worldguard"
+#define QEMU_VIRT_WG_CFG_NODE			"worldguard_cfg"
+
+#define QEMU_VIRT_WG_PROP_SLOT_COUNT		"sifive,slot-count"
+#define QEMU_VIRT_WG_PROP_SUBORDINATES		"sifive,subordinates"
+#define QEMU_VIRT_WG_PROP_WID			"worldguard,wid"
+#define QEMU_VIRT_WG_PROP_WIDLIST		"worldguard,widlist"
+#define QEMU_VIRT_WG_PROP_MWID			"mwid"
+#define QEMU_VIRT_WG_PROP_MWIDLIST		"mwidlist"
+#define QEMU_VIRT_WG_PROP_PERMS			"perms"
+
+/*
+ * The current QEMU wgChecker model uses a 64-bit permission register with
+ * 2 bits per world, so the current software model tracks at most 32 WIDs.
+ */
+#define QEMU_VIRT_WG_MAX_WIDS			32
+
+/* The current QEMU wgChecker model requires 4 KiB slot alignment. */
+#define QEMU_VIRT_WG_MIN_ALIGN			0x1000ULL
+
+/* Current QEMU wgChecker MMIO register layout. */
+#define QEMU_VIRT_WG_MMIO_NSLOTS		0x008
+#define QEMU_VIRT_WG_MMIO_ERRCAUSE		0x010
+#define QEMU_VIRT_WG_MMIO_ERRADDR		0x018
+#define QEMU_VIRT_WG_MMIO_SLOT_BASE		0x020
+#define QEMU_VIRT_WG_MMIO_SLOT_STRIDE		0x020
+#define QEMU_VIRT_WG_MMIO_SLOT_ADDR		0x000
+#define QEMU_VIRT_WG_MMIO_SLOT_PERM		0x008
+#define QEMU_VIRT_WG_MMIO_SLOT_CFG		0x010
+
+/* Current QEMU wgChecker slot cfg.A[1:0] encoding. */
+#define QEMU_VIRT_WG_SLOT_CFG_A_MASK		0x3
+#define QEMU_VIRT_WG_SLOT_CFG_A_OFF		0x0
+#define QEMU_VIRT_WG_SLOT_CFG_A_TOR		0x1
+
+struct qemu_virt_wg_range {
+	u64 base;
+	u64 size;
+	u64 perm;
+};
+
+#endif
diff --git a/platform/generic/objects.mk b/platform/generic/objects.mk
index 85aa723a..ebca6940 100644
--- a/platform/generic/objects.mk
+++ b/platform/generic/objects.mk
@@ -20,6 +20,7 @@ platform-runcmd = qemu-system-riscv$(PLATFORM_RISCV_XLEN) -M virt -m 256M \
 # Objects to build
 platform-objs-y += platform.o
 platform-objs-y += platform_override_modules.o
+platform-objs-y += virt/qemu_virt_wgchecker.o
 
 # Blobs to build
 FW_TEXT_START=0x80000000
diff --git a/platform/generic/platform.c b/platform/generic/platform.c
index b76c2a2f..5f2bc1e4 100644
--- a/platform/generic/platform.c
+++ b/platform/generic/platform.c
@@ -11,6 +11,7 @@
 #include <platform_override.h>
 #include <sbi/riscv_asm.h>
 #include <sbi/sbi_bitops.h>
+#include <sbi/sbi_error.h>
 #include <sbi/sbi_hartmask.h>
 #include <sbi/sbi_heap.h>
 #include <sbi/sbi_platform.h>
@@ -30,6 +31,8 @@
 #include <sbi_utils/rpxy/fdt_rpxy.h>
 #include <sbi_utils/serial/semihosting.h>
 
+extern int qemu_virt_hwiso_register(void *fdt);
+
 /* List of platform override modules generated at compile time */
 extern const struct platform_override *platform_override_modules[];
 extern unsigned long platform_override_modules_size;
@@ -222,9 +225,17 @@ static int generic_nascent_init(void)
 
 static int generic_early_init(bool cold_boot)
 {
+	int rc;
+
 	if (cold_boot)
 		fdt_reset_init();
 
+	if (cold_boot) {
+		rc = qemu_virt_hwiso_register(fdt_get_address());
+		if (rc && rc != SBI_EALREADY)
+			return rc;
+	}
+
 	if (!generic_plat || !generic_plat->early_init)
 		return 0;
 
diff --git a/platform/generic/virt/qemu_virt_wgchecker.c b/platform/generic/virt/qemu_virt_wgchecker.c
new file mode 100644
index 00000000..063fcecb
--- /dev/null
+++ b/platform/generic/virt/qemu_virt_wgchecker.c
@@ -0,0 +1,1050 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * QEMU virt WorldGuard hardware isolation support
+ *
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao at riscstar.com>
+ */
+
+#include <libfdt.h>
+#include <sbi/riscv_asm.h>
+#include <sbi/riscv_encoding.h>
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hart.h>
+#include <sbi/sbi_hartmask.h>
+#include <sbi/sbi_heap.h>
+#include <sbi/sbi_hwiso.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_string.h>
+#include <qemu_virt_wg.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+
+struct wg_checker {
+	char name[32];
+	u64 mmio_base;
+	u64 mmio_size;
+	u32 slot_count;
+	u32 subordinate_count;
+	bool full_checker_rule;
+	u64 full_checker_perm;
+	u32 range_count;
+	struct qemu_virt_wg_range *ranges;
+};
+
+struct wg_cpu_defaults {
+	u32 trusted_wid;
+	u32 nworlds;
+	u32 valid_wid_mask;
+};
+
+struct wg_platform_ctx {
+	u32 checker_count;
+	u32 hart_count;
+	bool checker_enabled;
+	bool runtime_enabled;
+	struct wg_checker *checkers;
+	struct wg_cpu_defaults *hart_defaults;
+};
+
+struct wg_domain_ctx {
+	bool has_wid;
+	u32 wid;
+	u32 widlist_count;
+	u32 widlist_mask;
+	u32 widlist[QEMU_VIRT_WG_MAX_WIDS];
+};
+
+static struct wg_platform_ctx *wg_platform;
+
+static void wg_free_platform_ctx(struct wg_platform_ctx *platform)
+{
+	u32 i;
+
+	if (!platform)
+		return;
+
+	for (i = 0; i < platform->checker_count; i++)
+		sbi_free(platform->checkers[i].ranges);
+
+	sbi_free(platform->checkers);
+	sbi_free(platform->hart_defaults);
+	sbi_free(platform);
+}
+
+static bool wg_runtime_enabled(void)
+{
+	return wg_platform && wg_platform->runtime_enabled;
+}
+
+static u64 wg_read_cells(const fdt32_t *cells, int count)
+{
+	u64 val = 0;
+	int i;
+
+	for (i = 0; i < count; i++)
+		val = (val << 32) | fdt32_to_cpu(cells[i]);
+
+	return val;
+}
+
+static void wg_write64(u64 addr, u64 val)
+{
+#if __riscv_xlen != 32
+	writeq(val, (void *)(unsigned long)addr);
+#else
+	writel((u32)val, (void *)(unsigned long)addr);
+	writel((u32)(val >> 32), (void *)(unsigned long)(addr + 4));
+#endif
+}
+
+static void wg_write32(u64 addr, u32 val)
+{
+	writel(val, (void *)(unsigned long)addr);
+}
+
+static u64 wg_slot_addr_encode(u64 addr)
+{
+	return addr >> 2;
+}
+
+static u64 wg_wid_mask(u32 wid)
+{
+	return (wid < 32) ? (1ULL << wid) : 0;
+}
+
+static bool wg_range_is_aligned(u64 base, u64 size)
+{
+	if (!size)
+		return false;
+
+	if (base & (QEMU_VIRT_WG_MIN_ALIGN - 1))
+		return false;
+	if (size & (QEMU_VIRT_WG_MIN_ALIGN - 1))
+		return false;
+
+	return true;
+}
+
+static void wg_sort_ranges(struct wg_checker *checker)
+{
+	struct qemu_virt_wg_range tmp;
+	u32 i, j;
+
+	for (i = 1; i < checker->range_count; i++) {
+		tmp = checker->ranges[i];
+		j = i;
+		while (j > 0 && checker->ranges[j - 1].base > tmp.base) {
+			checker->ranges[j] = checker->ranges[j - 1];
+			j--;
+		}
+		checker->ranges[j] = tmp;
+	}
+}
+
+static int wg_compact_ranges(struct wg_checker *checker)
+{
+	struct qemu_virt_wg_range *prev, *cur;
+	u64 prev_end, cur_end;
+	u32 i, out = 0;
+
+	if (!checker->range_count)
+		return 0;
+
+	wg_sort_ranges(checker);
+
+	for (i = 0; i < checker->range_count; i++) {
+		cur = &checker->ranges[i];
+		cur_end = cur->base + cur->size;
+		if (cur_end <= cur->base)
+			return SBI_EINVAL;
+
+		if (!out) {
+			checker->ranges[out++] = *cur;
+			continue;
+		}
+
+		prev = &checker->ranges[out - 1];
+		prev_end = prev->base + prev->size;
+		if (cur->base < prev_end)
+			return SBI_EINVAL;
+
+		if (cur->base == prev_end && cur->perm == prev->perm) {
+			prev->size += cur->size;
+			continue;
+		}
+
+		checker->ranges[out++] = *cur;
+	}
+
+	checker->range_count = out;
+	return 0;
+}
+
+static int wg_get_reg_cells(void *fdt, int resource_node,
+			      int *addr_cells, int *size_cells)
+{
+	int parent;
+
+	parent = fdt_parent_offset(fdt, resource_node);
+	if (parent < 0)
+		return SBI_EINVAL;
+
+	*addr_cells = fdt_address_cells(fdt, parent);
+	*size_cells = fdt_size_cells(fdt, parent);
+	if (*addr_cells <= 0 || *addr_cells > 2 || *size_cells <= 0 ||
+	    *size_cells > 2)
+		return SBI_EINVAL;
+
+	return 0;
+}
+
+static int wg_count_reg_entries(void *fdt, int resource_node, int reg_node)
+{
+	const fdt32_t *reg;
+	int addr_cells, size_cells, entry_cells, len, rc;
+
+	rc = wg_get_reg_cells(fdt, resource_node, &addr_cells, &size_cells);
+	if (rc)
+		return rc;
+
+	reg = fdt_getprop(fdt, reg_node, "reg", &len);
+	if (!reg || len <= 0)
+		return 0;
+
+	entry_cells = addr_cells + size_cells;
+	if (len % (entry_cells * (int)sizeof(fdt32_t)))
+		return SBI_EINVAL;
+
+	return len / (entry_cells * (int)sizeof(fdt32_t));
+}
+
+static int wg_parse_perms(void *fdt, int cfg_node, u64 **out_perms,
+			   u32 *out_count)
+{
+	const fdt32_t *perms;
+	u64 *vals;
+	int len, i, count;
+
+	*out_perms = NULL;
+	*out_count = 0;
+
+	perms = fdt_getprop(fdt, cfg_node, QEMU_VIRT_WG_PROP_PERMS, &len);
+	if (!perms || len <= 0)
+		return 0;
+
+	/* QEMU virt WG permissions are always encoded as 64-bit <hi lo> cells. */
+	if (len % (2 * (int)sizeof(fdt32_t)))
+		return SBI_EINVAL;
+
+	count = len / (2 * (int)sizeof(fdt32_t));
+	vals = sbi_calloc(sizeof(*vals), count);
+	if (!vals)
+		return SBI_ENOMEM;
+
+	for (i = 0; i < count; i++, perms += 2)
+		vals[i] = wg_read_cells(perms, 2);
+
+	*out_perms = vals;
+	*out_count = count;
+	return 0;
+}
+
+static int wg_fill_ranges(void *fdt, int resource_node, int reg_node,
+			  const u64 *perms, u32 perm_count,
+			  struct qemu_virt_wg_range *ranges, u32 range_count)
+{
+	const fdt32_t *reg;
+	u64 base, size;
+	int addr_cells, size_cells, entry_cells, len, i, rc;
+
+	rc = wg_get_reg_cells(fdt, resource_node, &addr_cells, &size_cells);
+	if (rc)
+		return rc;
+
+	reg = fdt_getprop(fdt, reg_node, "reg", &len);
+	if (!reg || len <= 0)
+		return SBI_EINVAL;
+
+	entry_cells = addr_cells + size_cells;
+	for (i = 0; i < (int)range_count; i++, reg += entry_cells) {
+		base = wg_read_cells(reg, addr_cells);
+		size = wg_read_cells(reg + addr_cells, size_cells);
+		if (!wg_range_is_aligned(base, size))
+			return SBI_EINVAL;
+
+		ranges[i].base = base;
+		ranges[i].size = size;
+		ranges[i].perm = perms[(perm_count == 1) ? 0 : i];
+	}
+
+	return 0;
+}
+
+static int wg_parse_checker_rules(void *fdt, int checker_node,
+				  struct wg_checker *checker)
+{
+	const fdt32_t *subs;
+	u64 *perms = NULL;
+	int cfg_node, len, i, rc = 0, reg_count;
+	u32 perm_count = 0;
+	int child;
+
+	subs = fdt_getprop(fdt, checker_node,
+			   QEMU_VIRT_WG_PROP_SUBORDINATES, &len);
+	if (!subs || len <= 0)
+		return 0;
+	if (len % (int)sizeof(fdt32_t))
+		goto err;
+
+	checker->subordinate_count = len / sizeof(fdt32_t);
+	if (!checker->slot_count)
+		goto err;
+
+	checker->ranges = sbi_calloc(sizeof(*checker->ranges),
+				     checker->slot_count);
+	if (!checker->ranges)
+		return SBI_ENOMEM;
+
+	for (i = 0; i < checker->subordinate_count; i++) {
+		child = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(subs[i]));
+		if (child < 0) {
+			sbi_printf("[WG] checker %s has invalid subordinate"
+				   " phandle[%d]=0x%x err=%d\n",
+				   checker->name, i, fdt32_to_cpu(subs[i]),
+				   child);
+			rc = child;
+			goto err;
+		}
+
+		cfg_node = fdt_subnode_offset(fdt, child,
+					      QEMU_VIRT_WG_CFG_NODE);
+		if (cfg_node < 0)
+			continue;
+
+		rc = wg_parse_perms(fdt, cfg_node, &perms, &perm_count);
+		if (rc)
+			goto err;
+		if (!perm_count)
+			continue;
+
+		reg_count = wg_count_reg_entries(fdt, child, cfg_node);
+		if (reg_count < 0)
+			goto err;
+
+		if (!reg_count && checker->subordinate_count == 1 &&
+		    perm_count == 1) {
+			if (checker->range_count)
+				goto err;
+			checker->full_checker_rule = true;
+			checker->full_checker_perm = perms[0];
+			sbi_free(perms);
+			perms = NULL;
+			continue;
+		}
+
+		if (!reg_count)
+			reg_count = wg_count_reg_entries(fdt, child, child);
+		if (reg_count <= 0)
+			goto err;
+
+		if (perm_count != 1 && perm_count != (u32)reg_count)
+			goto err;
+		if (checker->full_checker_rule)
+			goto err;
+		if (checker->range_count + reg_count > checker->slot_count)
+			goto err;
+
+		rc = wg_fill_ranges(fdt, child,
+				     (fdt_getprop(fdt, cfg_node, "reg", NULL) ?
+				      cfg_node : child),
+				     perms, perm_count,
+				     &checker->ranges[checker->range_count],
+				     reg_count);
+		sbi_free(perms);
+		perms = NULL;
+		if (rc)
+			goto err;
+
+		checker->range_count += reg_count;
+	}
+
+	if (checker->full_checker_rule)
+		return 0;
+
+	return wg_compact_ranges(checker);
+
+err:
+	sbi_free(perms);
+	return rc ? rc : SBI_EINVAL;
+}
+
+static int wg_parse_checker(void *fdt, int checker_node,
+			    struct wg_checker *checker)
+{
+	const fdt32_t *val;
+	u64 base = 0, size = 0;
+	int len, rc;
+
+	rc = fdt_get_node_addr_size(fdt, checker_node, 0, &base, &size);
+	if (rc)
+		return rc;
+
+	val = fdt_getprop(fdt, checker_node,
+			  QEMU_VIRT_WG_PROP_SLOT_COUNT, &len);
+	if (!val || len < (int)sizeof(fdt32_t))
+		return SBI_EINVAL;
+
+	checker->mmio_base = base;
+	checker->mmio_size = size;
+	checker->slot_count = fdt32_to_cpu(val[0]);
+	sbi_snprintf(checker->name, sizeof(checker->name), "%s",
+		     fdt_get_name(fdt, checker_node, NULL));
+
+	return wg_parse_checker_rules(fdt, checker_node, checker);
+}
+
+static void wg_program_clear_slots(const struct wg_checker *checker)
+{
+	u32 slot;
+
+	for (slot = 1; slot < checker->slot_count; slot++) {
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_ADDR, 0);
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_PERM, 0);
+		wg_write32(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_CFG, 0);
+	}
+
+}
+
+static void wg_program_clear_last_slot(const struct wg_checker *checker)
+{
+	wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+		   checker->slot_count * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+		   QEMU_VIRT_WG_MMIO_SLOT_PERM, 0);
+	wg_write32(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+		   checker->slot_count * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+		   QEMU_VIRT_WG_MMIO_SLOT_CFG, 0);
+}
+
+static void wg_program_clear_slots_from(const struct wg_checker *checker,
+					u32 first_slot)
+{
+	u32 slot;
+
+	if (first_slot >= checker->slot_count)
+		return;
+
+	for (slot = first_slot; slot < checker->slot_count; slot++) {
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_ADDR, 0);
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_PERM, 0);
+		wg_write32(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_CFG, 0);
+	}
+}
+
+static int wg_program_checker(const struct wg_checker *checker)
+{
+	u64 prev_end = 0;
+	u32 required_slots = 0, slot = 1, i;
+
+	wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_ERRCAUSE, 0);
+	wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_ERRADDR, 0);
+
+	if (checker->full_checker_rule) {
+		wg_program_clear_slots(checker);
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   checker->slot_count * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_PERM,
+			   checker->full_checker_perm);
+		wg_write32(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   checker->slot_count * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_CFG,
+			   QEMU_VIRT_WG_SLOT_CFG_A_TOR);
+		return 0;
+	}
+
+	for (i = 0; i < checker->range_count; i++) {
+		if (!i || checker->ranges[i].base != prev_end)
+			required_slots++;
+		required_slots++;
+		prev_end = checker->ranges[i].base + checker->ranges[i].size;
+	}
+
+	if (required_slots > checker->slot_count - 1)
+		return SBI_EINVAL;
+
+	prev_end = 0;
+	for (i = 0; i < checker->range_count; i++) {
+		const struct qemu_virt_wg_range *range = &checker->ranges[i];
+		u64 end = range->base + range->size;
+
+		if (!i || range->base != prev_end) {
+			wg_write64(checker->mmio_base +
+				   QEMU_VIRT_WG_MMIO_SLOT_BASE +
+				   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+				   QEMU_VIRT_WG_MMIO_SLOT_ADDR,
+				   wg_slot_addr_encode(range->base));
+			wg_write64(checker->mmio_base +
+				   QEMU_VIRT_WG_MMIO_SLOT_BASE +
+				   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+				   QEMU_VIRT_WG_MMIO_SLOT_PERM, 0);
+			wg_write32(checker->mmio_base +
+				   QEMU_VIRT_WG_MMIO_SLOT_BASE +
+				   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+				   QEMU_VIRT_WG_MMIO_SLOT_CFG,
+				   QEMU_VIRT_WG_SLOT_CFG_A_OFF);
+			slot++;
+		}
+
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_ADDR,
+			   wg_slot_addr_encode(end));
+		wg_write64(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_PERM,
+			   range->perm);
+		wg_write32(checker->mmio_base + QEMU_VIRT_WG_MMIO_SLOT_BASE +
+			   slot * QEMU_VIRT_WG_MMIO_SLOT_STRIDE +
+			   QEMU_VIRT_WG_MMIO_SLOT_CFG,
+			   QEMU_VIRT_WG_SLOT_CFG_A_TOR);
+		prev_end = end;
+		slot++;
+	}
+
+	/*
+	 * Keep the reset-time trusted-WID bypass slot alive until the new
+	 * rule set is fully programmed, otherwise the DRAM checker can deny
+	 * OpenSBI's own RAM accesses mid-update.
+	 */
+	wg_program_clear_slots_from(checker, slot);
+	wg_program_clear_last_slot(checker);
+
+	return 0;
+}
+
+static void wg_free_platform(void)
+{
+	if (!wg_platform)
+		return;
+
+	wg_free_platform_ctx(wg_platform);
+	wg_platform = NULL;
+}
+
+static void wg_init_cpu_defaults(struct wg_platform_ctx *platform)
+{
+	u32 i;
+
+	if (!platform || !platform->hart_defaults)
+		return;
+
+	for (i = 0; i < platform->hart_count; i++) {
+		platform->hart_defaults[i].trusted_wid = 0;
+		platform->hart_defaults[i].nworlds = 1;
+		platform->hart_defaults[i].valid_wid_mask = 0x1;
+	}
+}
+
+static int wg_parse_wid_prop(void *fdt, int node, const char *prop_name,
+			     u32 *out_wid)
+{
+	const fdt32_t *prop;
+	int len;
+
+	if (!out_wid)
+		return SBI_EINVAL;
+
+	prop = fdt_getprop(fdt, node, prop_name, &len);
+	if (!prop)
+		return SBI_ENOENT;
+	if (len != (int)sizeof(fdt32_t))
+		return SBI_EINVAL;
+
+	*out_wid = fdt32_to_cpu(prop[0]);
+	if (*out_wid >= QEMU_VIRT_WG_MAX_WIDS)
+		return SBI_EINVAL;
+
+	return 0;
+}
+
+static int wg_parse_widlist(void *fdt, int node, const char *prop_name,
+			    u32 *out_mask, u32 *out_wids, u32 *out_count)
+{
+	const fdt32_t *prop;
+	u32 mask = 0, count = 0, wid;
+	int len, i;
+
+	if (!out_mask || !out_count)
+		return SBI_EINVAL;
+
+	*out_mask = 0;
+	*out_count = 0;
+
+	prop = fdt_getprop(fdt, node, prop_name, &len);
+	if (!prop)
+		return 0;
+	if (len < 0 || (len % (int)sizeof(fdt32_t)))
+		return SBI_EINVAL;
+
+	count = len / sizeof(fdt32_t);
+	if (count > QEMU_VIRT_WG_MAX_WIDS)
+		return SBI_EINVAL;
+
+	for (i = 0; i < (int)count; i++) {
+		wid = fdt32_to_cpu(prop[i]);
+		if (wid >= QEMU_VIRT_WG_MAX_WIDS)
+			return SBI_EINVAL;
+		if (mask & wg_wid_mask(wid))
+			return SBI_EINVAL;
+
+		mask |= wg_wid_mask(wid);
+		if (out_wids)
+			out_wids[i] = wid;
+	}
+
+	*out_mask = mask;
+	*out_count = count;
+	return 0;
+}
+
+static int wg_parse_cpu_defaults(void *fdt, struct wg_platform_ctx *platform)
+{
+	struct wg_cpu_defaults *cpu_defaults;
+	u32 hartid, hartindex, max_wid, widlist_count;
+	int cpus_offset, cpu_offset, wgcpu, rc;
+
+	if (!fdt || !platform || !platform->hart_defaults)
+		return 0;
+
+	cpus_offset = fdt_path_offset(fdt, "/cpus");
+	if (cpus_offset < 0)
+		return 0;
+
+	fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
+		if (fdt_parse_hart_id(fdt, cpu_offset, &hartid))
+			continue;
+
+		hartindex = sbi_hartid_to_hartindex(hartid);
+		if (!sbi_hartindex_valid(hartindex) ||
+		    hartindex >= platform->hart_count)
+			continue;
+
+		wgcpu = fdt_subnode_offset(fdt, cpu_offset,
+					   QEMU_VIRT_WG_CPU_NODE);
+		if (wgcpu < 0 || fdt_node_check_compatible(
+					 fdt, wgcpu, QEMU_VIRT_WG_CPU_COMPAT))
+			continue;
+
+		cpu_defaults = &platform->hart_defaults[hartindex];
+		rc = wg_parse_wid_prop(fdt, wgcpu, QEMU_VIRT_WG_PROP_MWID,
+				       &cpu_defaults->trusted_wid);
+		if (rc)
+			return rc;
+
+		max_wid = cpu_defaults->trusted_wid;
+		rc = wg_parse_widlist(fdt, wgcpu, QEMU_VIRT_WG_PROP_MWIDLIST,
+				      &cpu_defaults->valid_wid_mask, NULL,
+				      &widlist_count);
+		if (rc)
+			return rc;
+
+		cpu_defaults->valid_wid_mask |=
+			wg_wid_mask(cpu_defaults->trusted_wid);
+		if (cpu_defaults->valid_wid_mask) {
+			u32 wid;
+
+			for (wid = 0; wid < QEMU_VIRT_WG_MAX_WIDS; wid++) {
+				if (cpu_defaults->valid_wid_mask & (1U << wid))
+					max_wid = wid;
+			}
+		}
+
+		cpu_defaults->nworlds = max_wid + 1;
+	}
+
+	return 0;
+}
+
+static bool wg_has_cpu_runtime(void *fdt)
+{
+	u32 hartid;
+	int cpus_offset, cpu_offset, wgcpu;
+
+	if (!fdt)
+		return false;
+
+	cpus_offset = fdt_path_offset(fdt, "/cpus");
+	if (cpus_offset < 0)
+		return false;
+
+	fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
+		if (fdt_parse_hart_id(fdt, cpu_offset, &hartid))
+			continue;
+
+		wgcpu = fdt_subnode_offset(fdt, cpu_offset,
+					   QEMU_VIRT_WG_CPU_NODE);
+		if (wgcpu < 0)
+			continue;
+		if (fdt_node_check_compatible(fdt, wgcpu,
+					      QEMU_VIRT_WG_CPU_COMPAT))
+			continue;
+
+		return true;
+	}
+
+	return false;
+}
+
+static u32 wg_count_platform_checkers(void *fdt)
+{
+	int checker_node;
+	u32 count = 0;
+
+	if (!fdt)
+		return 0;
+
+	checker_node = -1;
+	while (true) {
+		checker_node = fdt_node_offset_by_compatible(
+			fdt, checker_node, QEMU_VIRT_WG_COMPAT);
+		if (checker_node < 0)
+			break;
+		if (fdt_getprop(fdt, checker_node,
+				QEMU_VIRT_WG_PROP_SUBORDINATES, NULL))
+			count++;
+	}
+
+	return count;
+}
+
+static int wg_validate_domain_ctx(const struct sbi_domain *dom,
+				  const struct wg_domain_ctx *ctx)
+{
+	const struct wg_cpu_defaults *cpu_defaults;
+	u32 hartindex;
+
+	if (!wg_platform || !dom || !ctx || dom == &root || !dom->possible_harts)
+		return 0;
+
+	for (hartindex = 0; hartindex < wg_platform->hart_count; hartindex++) {
+		if (!sbi_hartmask_test_hartindex(hartindex, dom->possible_harts))
+			continue;
+
+		cpu_defaults = &wg_platform->hart_defaults[hartindex];
+		if (!(cpu_defaults->valid_wid_mask & wg_wid_mask(ctx->wid)))
+			return SBI_EINVAL;
+		if (ctx->widlist_mask & ~cpu_defaults->valid_wid_mask)
+			return SBI_EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct wg_cpu_defaults *wg_current_cpu_defaults(void)
+{
+	u32 hartindex;
+
+	if (!wg_platform || !wg_platform->hart_defaults)
+		return NULL;
+
+	hartindex = sbi_hartid_to_hartindex(current_hartid());
+	if (!sbi_hartindex_valid(hartindex) ||
+	    hartindex >= wg_platform->hart_count)
+		return NULL;
+
+	return &wg_platform->hart_defaults[hartindex];
+}
+
+static void wg_program_wid_state(u32 mlwid, u32 mwiddeleg, u32 slwid)
+{
+	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+
+	if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SMWG))
+		return;
+
+	if (!sbi_hart_has_extension(scratch, SBI_HART_EXT_SSWG)) {
+		csr_write(CSR_MLWID, mlwid);
+		return;
+	}
+
+	csr_write(CSR_MWIDDELEG, 0);
+	csr_write(CSR_MLWID, mlwid);
+	if (mwiddeleg) {
+		csr_write(CSR_MWIDDELEG, mwiddeleg);
+		csr_write(CSR_SLWID, slwid);
+	}
+}
+
+static int wg_init(void *fdt)
+{
+	struct wg_platform_ctx *platform;
+	int checker_node, rc;
+	u32 count, idx = 0;
+	bool has_runtime;
+
+	wg_free_platform();
+
+	if (!fdt)
+		return 0;
+
+	count = wg_count_platform_checkers(fdt);
+	has_runtime = wg_has_cpu_runtime(fdt);
+	if (!count && !has_runtime)
+		return 0;
+
+	platform = sbi_zalloc(sizeof(*platform));
+	if (!platform)
+		return SBI_ENOMEM;
+
+	platform->hart_count = sbi_scratch_last_hartindex() + 1;
+	platform->checker_count = count;
+	platform->checker_enabled = !!count;
+	platform->runtime_enabled = has_runtime;
+	platform->checkers = sbi_calloc(sizeof(*platform->checkers), count);
+	if (count && !platform->checkers) {
+		sbi_free(platform);
+		return SBI_ENOMEM;
+	}
+
+	platform->hart_defaults = sbi_calloc(sizeof(*platform->hart_defaults),
+					      platform->hart_count);
+	if (!platform->hart_defaults) {
+		wg_free_platform_ctx(platform);
+		return SBI_ENOMEM;
+	}
+
+	wg_init_cpu_defaults(platform);
+	rc = wg_parse_cpu_defaults(fdt, platform);
+	if (rc) {
+		wg_free_platform_ctx(platform);
+		return rc;
+	}
+
+	if (platform->checker_enabled) {
+		checker_node = -1;
+		while (true) {
+			checker_node = fdt_node_offset_by_compatible(
+				fdt, checker_node, QEMU_VIRT_WG_COMPAT);
+			if (checker_node < 0)
+				break;
+			if (!fdt_getprop(fdt, checker_node,
+					 QEMU_VIRT_WG_PROP_SUBORDINATES, NULL))
+				continue;
+
+			rc = wg_parse_checker(fdt, checker_node,
+					      &platform->checkers[idx]);
+			if (rc) {
+				sbi_printf("[WG] failed to parse checker %s err=%d\n",
+					   fdt_get_name(fdt, checker_node, NULL),
+					   rc);
+				wg_free_platform_ctx(platform);
+				return rc;
+			}
+
+			rc = wg_program_checker(&platform->checkers[idx]);
+			if (rc) {
+				sbi_printf("[WG] failed to program checker %s err=%d\n",
+					   platform->checkers[idx].name, rc);
+				wg_free_platform_ctx(platform);
+				return rc;
+			}
+
+			sbi_printf("[WG] checker %s base=0x%llx slots=%u rules=%u%s\n",
+				   platform->checkers[idx].name,
+				   (unsigned long long)platform->checkers[idx].mmio_base,
+				   platform->checkers[idx].slot_count,
+				   platform->checkers[idx].range_count,
+				   platform->checkers[idx].full_checker_rule ?
+					" full-checker" : "");
+			idx++;
+		}
+	}
+
+	wg_platform = platform;
+	return 0;
+}
+
+static int wg_domain_init(void *fdt, int domain_offset,
+			  struct sbi_domain *dom, void **out_ctx)
+{
+	struct wg_domain_ctx *ctx;
+	int hoff, child, rc;
+	bool found = false;
+
+	if (!out_ctx)
+		return SBI_EINVAL;
+
+	*out_ctx = NULL;
+	if (!wg_runtime_enabled())
+		return 0;
+	if (!fdt || domain_offset < 0)
+		return 0;
+
+	hoff = fdt_subnode_offset(fdt, domain_offset, "hw-isolation");
+	if (hoff < 0)
+		return (dom == &root) ? 0 : SBI_EINVAL;
+
+	fdt_for_each_subnode(child, fdt, hoff) {
+		if (fdt_node_check_compatible(
+				fdt, child, QEMU_VIRT_WG_COMPAT))
+			continue;
+		found = true;
+		break;
+	}
+
+	if (!found)
+		return (dom == &root) ? 0 : SBI_EINVAL;
+
+	ctx = sbi_zalloc(sizeof(*ctx));
+	if (!ctx)
+		return SBI_ENOMEM;
+
+	rc = wg_parse_wid_prop(fdt, child, QEMU_VIRT_WG_PROP_WID, &ctx->wid);
+	if (rc)
+		goto err_free_ctx;
+	ctx->has_wid = true;
+
+	rc = wg_parse_widlist(fdt, child, QEMU_VIRT_WG_PROP_WIDLIST,
+			      &ctx->widlist_mask, ctx->widlist,
+			      &ctx->widlist_count);
+	if (rc)
+		goto err_free_ctx;
+
+	rc = wg_validate_domain_ctx(dom, ctx);
+	if (rc)
+		goto err_free_ctx;
+
+	*out_ctx = ctx;
+	return 0;
+
+err_free_ctx:
+	sbi_free(ctx);
+	return rc;
+}
+
+static u32 wg_fallback_wid(void)
+{
+	const struct wg_cpu_defaults *cpu_defaults = wg_current_cpu_defaults();
+
+	return cpu_defaults ? cpu_defaults->trusted_wid : 0;
+}
+
+static u32 wg_valid_wid_mask(void)
+{
+	const struct wg_cpu_defaults *cpu_defaults = wg_current_cpu_defaults();
+
+	return cpu_defaults ? cpu_defaults->valid_wid_mask :
+			      (u32)wg_wid_mask(wg_fallback_wid());
+}
+
+static u32 wg_select_slwid(u32 widlist_mask, bool has_wid, u32 wid, u32 fallback)
+{
+	u32 i;
+
+	if (!widlist_mask)
+		return fallback;
+
+	if (has_wid && (wg_wid_mask(wid) & widlist_mask))
+		return wid;
+
+	for (i = 0; i < 32; i++) {
+		if (widlist_mask & (1U << i))
+			return i;
+	}
+
+	return fallback;
+}
+
+static void wg_domain_exit(const struct sbi_domain *src,
+			   const struct sbi_domain *dst, void *ctx)
+{
+	u32 mlwid = wg_fallback_wid();
+
+	(void)ctx;
+	if (!wg_runtime_enabled())
+		return;
+
+	wg_program_wid_state(mlwid, 0, mlwid);
+
+	sbi_printf("[WG] domain_exit src=%s dst=%s mlwid=%u mwiddeleg=0x0\n",
+		   src ? src->name : "<null>",
+		   dst ? dst->name : "<null>", mlwid);
+}
+
+static void wg_domain_enter(const struct sbi_domain *dst,
+			    const struct sbi_domain *src, void *ctx)
+{
+	struct wg_domain_ctx *dctx = ctx;
+	u32 valid_mask = wg_valid_wid_mask();
+	u32 mlwid = wg_fallback_wid();
+	u32 mwiddeleg = 0;
+	u32 slwid = mlwid;
+
+	(void)src;
+	if (!wg_runtime_enabled())
+		return;
+
+	if (dctx && dctx->has_wid && (wg_wid_mask(dctx->wid) & valid_mask))
+		mlwid = dctx->wid;
+
+	if (dctx)
+		mwiddeleg = dctx->widlist_mask & valid_mask;
+	slwid = wg_select_slwid(mwiddeleg, dctx && dctx->has_wid,
+				dctx ? dctx->wid : 0, mlwid);
+
+	wg_program_wid_state(mlwid, mwiddeleg, slwid);
+
+	sbi_printf("[WG] domain_enter dst=%s mlwid=%u mwiddeleg=0x%x",
+		   dst ? dst->name : "<null>", mlwid, mwiddeleg);
+	sbi_printf(" slwid=%u\n", slwid);
+}
+
+static void wg_domain_cleanup(struct sbi_domain *dom, void *ctx)
+{
+	(void)dom;
+	sbi_free(ctx);
+}
+
+static const struct sbi_hwiso_ops wg_ops = {
+	.name = QEMU_VIRT_WG_COMPAT,
+	.init = wg_init,
+	.domain_init = wg_domain_init,
+	.domain_exit = wg_domain_exit,
+	.domain_enter = wg_domain_enter,
+	.domain_cleanup = wg_domain_cleanup,
+};
+
+int qemu_virt_hwiso_register(void *fdt)
+{
+	int rc;
+
+	if (!fdt)
+		return 0;
+
+	if (fdt_node_check_compatible(fdt, 0, "riscv-virtio") &&
+	    fdt_node_check_compatible(fdt, 0, "qemu,virt"))
+		return 0;
+
+	rc = sbi_hwiso_register(&wg_ops);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
-- 
2.25.1




More information about the opensbi mailing list