[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