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

Raymond Mao <[email protected]>
Newsgroups org.infradead.lists.opensbi
Message-ID <[email protected]>
From: Raymond Mao <[email protected]>

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 <[email protected]>
---
 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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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


-- 
opensbi mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/opensbi
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.