[PATCH 1/5] lib: sbi: Add per-hart feature-configuration platform operation

Leo Yu-Chi Liang leo.liang at sifive.com
Tue Sep 29 22:42:06 PDT 2026


Some RISC-V implementations expose per-hart CSRs that select
microarchitectural behaviour (branch prediction, prefetcher, LSU
options, etc.) and must be set on every hart.

Add a feature_config_init platform operation plus a per-hart cache of
pending CSR writes. sbi_hart_feature_config_set() lets a hart cache a
CSR write. Passing write_back=true writes the CSR immediately.

sbi_hart_feature_config_init() calls the platform's feature_config_init
hook, then (re)writes every cached CSR. It only calls the hook once
per hart, so a hart hotplugged back in or resume from suspend,
skips the hook and just reapplies its already-cached values.
A failure anywhere in this path hangs the hart, since an unconfigured
hart may run with unsafe microarchitectural defaults. Wire it into
sbi_init.c after platform final init on cold/warm boot, and again
directly on warm resume, since a non-retentive suspend can reset
these CSRs.

All matching policy lives in the platform operation; this only adds
the hook, the cache, and the call sites.

Signed-off-by: Leo Yu-Chi Liang <leo.liang at sifive.com>
Assisted-by: LLM
---
 include/sbi/sbi_hart_feature_config.h |  16 +++
 include/sbi/sbi_platform.h            |   7 ++
 lib/sbi/Kconfig                       |  23 ++++
 lib/sbi/objects.mk                    |   1 +
 lib/sbi/sbi_hart_feature_config.c     | 169 ++++++++++++++++++++++++++
 lib/sbi/sbi_init.c                    |  21 ++++
 6 files changed, 237 insertions(+)
 create mode 100644 include/sbi/sbi_hart_feature_config.h
 create mode 100644 lib/sbi/sbi_hart_feature_config.c

diff --git a/include/sbi/sbi_hart_feature_config.h b/include/sbi/sbi_hart_feature_config.h
new file mode 100644
index 00000000..a1649da5
--- /dev/null
+++ b/include/sbi/sbi_hart_feature_config.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (c) 2026 SiFive Inc.
+ */
+
+#ifndef __SBI_HART_FEATURE_CONFIG_H__
+#define __SBI_HART_FEATURE_CONFIG_H__
+
+#include <sbi/sbi_types.h>
+
+struct sbi_scratch;
+
+int sbi_hart_feature_config_init(struct sbi_scratch *scratch, bool cold_boot);
+int sbi_hart_feature_config_set(unsigned long hart_index, unsigned long csr_num, unsigned long value, bool write_back);
+
+#endif
diff --git a/include/sbi/sbi_platform.h b/include/sbi/sbi_platform.h
index 1e9a23c1..76d0b822 100644
--- a/include/sbi/sbi_platform.h
+++ b/include/sbi/sbi_platform.h
@@ -83,6 +83,13 @@ struct sbi_platform_operations {
 
 	/** Platform early initialization */
 	int (*early_init)(bool cold_boot);
+
+	/**
+	 * Platform feature-configuration hook. Caches per-hart CSRs
+	 * via sbi_hart_feature_config_set().
+	 */
+	int (*feature_config_init)(bool cold_boot);
+
 	/** Platform final initialization */
 	int (*final_init)(bool cold_boot);
 
diff --git a/lib/sbi/Kconfig b/lib/sbi/Kconfig
index 24132eaa..d72a480d 100644
--- a/lib/sbi/Kconfig
+++ b/lib/sbi/Kconfig
@@ -88,6 +88,29 @@ config SBIUNIT
 	bool "Enable SBIUNIT tests"
 	default n
 
+config FEATURE_CONFIG
+	bool "Per-hart feature-configuration CSR setup"
+	default n
+	help
+	  Some RISC-V implementations expose per-hart machine-mode CSRs
+	  that select microarchitectural behaviour, such as branch
+	  prediction policy or hardware prefetcher configuration, that
+	  must be set on every hart.
+
+	  Enables the platform's optional feature_config_init hook,
+	  which matches the hart against a registered vendor driver and
+	  caches the CSRs it owns. sbi_hart_feature_config_init() calls
+	  this hook on cold/warm boot of every hart, then writes the
+	  cache.
+
+	  A vendor driver must also be enabled, see "Feature
+	  Configuration Support" under utils. Harts with no matching
+	  driver values are left untouched.
+
+	  On hart resume, sbi_hart_feature_config_init() rewrites the
+	  cached values directly without calling the hook again, since
+	  a non-retentive suspend can reset these CSRs.
+
 config SBI_ECALL_SSE
 	bool "SSE extension"
 	default y
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index ae27d0c5..13afe0bc 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -75,6 +75,7 @@ libsbi-objs-y += sbi_emulate_csr.o
 libsbi-objs-y += sbi_fifo.o
 libsbi-objs-y += sbi_fwft.o
 libsbi-objs-y += sbi_hart.o
+libsbi-objs-y += sbi_hart_feature_config.o
 libsbi-objs-y += sbi_hart_pmp.o
 libsbi-objs-y += sbi_hart_protection.o
 libsbi-objs-y += sbi_heap.o
diff --git a/lib/sbi/sbi_hart_feature_config.c b/lib/sbi/sbi_hart_feature_config.c
new file mode 100644
index 00000000..b401a00a
--- /dev/null
+++ b/lib/sbi/sbi_hart_feature_config.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (c) 2026 SiFive Inc.
+ *
+ * Per-hart cache of feature-configuration CSR writes. A platform's
+ * feature_config_init hook caches CSRs via
+ * sbi_hart_feature_config_set() instead of writing them immediately.
+ * sbi_hart_feature_config_init() writes the cache and re-applies it on
+ * hart resume.
+ */
+
+#include <sbi/riscv_asm.h>
+#include <sbi/riscv_encoding.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hart_feature_config.h>
+#include <sbi/sbi_hartmask.h>
+#include <sbi/sbi_platform.h>
+#include <sbi/sbi_scratch.h>
+
+/** Max number of cached feature-configuration CSRs per hart */
+#define SBI_HART_FEATURE_CONFIG_CSR_MAX	8
+
+/**
+ * A single cached feature-configuration CSR write, keyed by csr_num.
+ * Populated by sbi_hart_feature_config_set() and replayed by
+ * sbi_hart_feature_config_init().
+ */
+struct sbi_hart_feature_config_csr {
+	unsigned long csr_num;
+	unsigned long value;
+};
+
+/**
+ * Per-hart cache of pending feature-configuration CSR writes
+ *
+ * Own scratch allocation so platforms without CONFIG_FEATURE_CONFIG
+ * pay nothing for it.
+ */
+struct sbi_hart_feature_config_cache {
+	bool probed;
+	unsigned int count;
+	struct sbi_hart_feature_config_csr csrs[SBI_HART_FEATURE_CONFIG_CSR_MAX];
+};
+
+static unsigned long feature_config_offset;
+
+static struct sbi_hart_feature_config_cache *
+sbi_hart_feature_config_cache_ptr(struct sbi_scratch *scratch)
+{
+	if (!feature_config_offset)
+		return NULL;
+
+	return sbi_scratch_offset_ptr(scratch, feature_config_offset);
+}
+
+/**
+ * Check that a CSR is one of the machine-mode custom read/write CSRs
+ *
+ * Confirms @csr_num falls in a range the privileged spec reserves for custom
+ * machine-mode read/write use.
+ */
+static bool sbi_hart_feature_config_csr_valid(unsigned long csr_num)
+{
+	return (csr_num >= CSR_CUSTOM8_M_RW_BASE &&
+		csr_num < CSR_CUSTOM8_M_RW_BASE + CSR_CUSTOM8_M_RW_COUNT) ||
+	       (csr_num >= CSR_CUSTOM9_M_RW_BASE &&
+		csr_num < CSR_CUSTOM9_M_RW_BASE + CSR_CUSTOM9_M_RW_COUNT);
+}
+
+/**
+ * Cache a feature-configuration CSR write for a HART
+ *
+ * @hart_index is the target hart of the CSR write
+ *
+ * @write_back writes the CSR immediately. This is only valid
+ * when @hart_index is the caller's own hart. The value is cached either
+ * way, so a later resume still replays it.
+ *
+ * @return 0 on success, or other negative error codes on failure
+ */
+int sbi_hart_feature_config_set(unsigned long hart_index, unsigned long csr_num,
+				 unsigned long value, bool write_back)
+{
+	struct sbi_hart_feature_config_cache *cache =
+		sbi_hart_feature_config_cache_ptr(sbi_hartindex_to_scratch(hart_index));
+	unsigned int i;
+
+	if (!cache)
+		return SBI_EINVALID_STATE;
+
+	if (!sbi_hart_feature_config_csr_valid(csr_num))
+		return SBI_EINVAL;
+
+	/*
+	 * write_back only makes sense for the caller's own hart.
+	 */
+	if (write_back && hart_index != current_hartindex())
+		return SBI_EINVAL;
+
+	for (i = 0; i < cache->count; i++) {
+		if (cache->csrs[i].csr_num == csr_num) {
+			cache->csrs[i].value = value;
+			break;
+		}
+	}
+
+	if (i == cache->count) {
+		if (cache->count >= SBI_HART_FEATURE_CONFIG_CSR_MAX)
+			return SBI_ENOSPC;
+
+		cache->csrs[cache->count].csr_num = csr_num;
+		cache->csrs[cache->count].value = value;
+		cache->count++;
+	}
+
+	if (write_back) {
+		csr_write_num(csr_num, value);
+		sbi_dprintf("hart%lu: feature-config: csr 0x%03lx = 0x%lx\n",
+			     hart_index, csr_num, csr_read_num(csr_num));
+	}
+
+	return 0;
+}
+
+/**
+ * Feature-configuration initialization for current HART
+ *
+ * On cold boot, allocates the per-hart CSR cache. Calls the platform's
+ * feature_config_init hook, if not already called for this hart, then
+ * (re)writes every cached CSR.
+ *
+ * @return 0 on success or a negative error code on failure; the
+ * caller is expected to treat failure as fatal, since it can leave a
+ * hart running with unsafe microarchitectural defaults
+ */
+int sbi_hart_feature_config_init(struct sbi_scratch *scratch, bool cold_boot)
+{
+	const struct sbi_platform *plat = sbi_platform_ptr(scratch);
+	struct sbi_hart_feature_config_cache *cache;
+	unsigned int i;
+	int rc;
+
+	if (cold_boot) {
+		feature_config_offset = sbi_scratch_alloc_offset(sizeof(*cache));
+		if (!feature_config_offset)
+			return SBI_ENOMEM;
+	}
+
+	cache = sbi_hart_feature_config_cache_ptr(scratch);
+	if (!cache)
+		return SBI_EINVALID_STATE;
+
+	if (!cache->probed && plat && sbi_platform_ops(plat)->feature_config_init) {
+		rc = sbi_platform_ops(plat)->feature_config_init(cold_boot);
+		if (rc)
+			return rc;
+		cache->probed = true;
+	}
+
+	for (i = 0; i < cache->count; i++) {
+		csr_write_num(cache->csrs[i].csr_num, cache->csrs[i].value);
+		sbi_dprintf("hart%lu: feature-config: csr 0x%03lx = 0x%lx\n",
+			     current_hartindex(), cache->csrs[i].csr_num,
+			     csr_read_num(cache->csrs[i].csr_num));
+	}
+
+	return 0;
+}
diff --git a/lib/sbi/sbi_init.c b/lib/sbi/sbi_init.c
index acd2f8b6..49dec1a9 100644
--- a/lib/sbi/sbi_init.c
+++ b/lib/sbi/sbi_init.c
@@ -17,6 +17,7 @@
 #include <sbi/sbi_ecall.h>
 #include <sbi/sbi_fwft.h>
 #include <sbi/sbi_hart.h>
+#include <sbi/sbi_hart_feature_config.h>
 #include <sbi/sbi_hartmask.h>
 #include <sbi/sbi_hart_pmp.h>
 #include <sbi/sbi_hart_protection.h>
@@ -389,6 +390,14 @@ static void __noreturn init_coldboot(struct sbi_scratch *scratch, u32 hartid)
 		sbi_hart_hang();
 	}
 
+	/* After final init, so it can adjust PMP or other state first */
+	rc = sbi_hart_feature_config_init(scratch, true);
+	if (rc) {
+		sbi_printf("%s: feature config init failed (error %d)\n",
+			   __func__, rc);
+		sbi_hart_hang();
+	}
+
 	/*
 	 * Note: SSE events callbacks can be registered by other drivers so
 	 * sbi_sse_init() needs to be called after all drivers have been probed.
@@ -508,6 +517,10 @@ static void __noreturn init_warm_startup(struct sbi_scratch *scratch,
 	if (rc)
 		sbi_hart_hang();
 
+	rc = sbi_hart_feature_config_init(scratch, false);
+	if (rc)
+		sbi_hart_hang();
+
 	rc = sbi_sse_init(scratch, false);
 	if (rc)
 		sbi_hart_hang();
@@ -541,6 +554,14 @@ static void __noreturn init_warm_resume(struct sbi_scratch *scratch,
 	if (rc)
 		sbi_hart_hang();
 
+	/*
+	 * A non-retentive suspend can reset these CSRs, so rewrite them
+	 * from the cache.
+	 */
+	rc = sbi_hart_feature_config_init(scratch, false);
+	if (rc)
+		sbi_hart_hang();
+
 	sbi_hsm_hart_resume_finish(scratch, hartid);
 }
 
-- 
2.43.7




More information about the opensbi mailing list