[PATCH v3 4/7] lib: utils/fdt: Cache CPU information from the DT
Samuel Holland
samuel.holland at sifive.com
Wed Oct 14 04:30:21 PDT 2026
Looking up and parsing the CPU nodes in the DT is extremely expensive.
Functions like fdt_node_offset_by_phandle() and fdt_parent_offset() must
walk the entire FDT structure because they are a backward lookup. Since
the offsets and phandles of the CPU and interrupt controller DT nodes
are used in several places in OpenSBI, we can significantly speed up
cold boot by caching the information in the scratch area. On the HiFive
Premier P550 board, using this cache reduced boot time by 33%.
Any platform that wants to use the cached information (for example in
fdt_parse_isa_extensions_all_harts()) must first call fdt_cpu_init().
Signed-off-by: Samuel Holland <samuel.holland at sifive.com>
---
Changes in v3:
- Check fdt_cached_offsets_valid() before returning cached FDT offsets
- Document that fdt_cpu_hartindex_to_offset() cannot be used in fixups
- Move fdt_cpu_init() call to generic_extensions_init()
Changes in v2:
- Rename for_each_hartindex() to sbi_for_each_hartindex()
include/sbi_utils/fdt/fdt_cpu.h | 60 ++++++++++++++++
lib/utils/fdt/fdt_cpu.c | 118 ++++++++++++++++++++++++++++++++
lib/utils/fdt/objects.mk | 1 +
platform/generic/platform.c | 10 ++-
4 files changed, 188 insertions(+), 1 deletion(-)
create mode 100644 include/sbi_utils/fdt/fdt_cpu.h
create mode 100644 lib/utils/fdt/fdt_cpu.c
diff --git a/include/sbi_utils/fdt/fdt_cpu.h b/include/sbi_utils/fdt/fdt_cpu.h
new file mode 100644
index 00000000..84ab45b3
--- /dev/null
+++ b/include/sbi_utils/fdt/fdt_cpu.h
@@ -0,0 +1,60 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * fdt_cpu.h - Efficient access to CPU information from the devicetree.
+ *
+ * Copyright (c) 2025-2026 SiFive
+ */
+
+#ifndef __FDT_CPU_H__
+#define __FDT_CPU_H__
+
+#include <sbi/sbi_types.h>
+
+/**
+ * Look up the DT node offset for a given hart
+ *
+ * This function cannot be called from DT fixup code, as the cached node offset
+ * may not be valid at that time.
+ *
+ * @param hartindex a valid hart index
+ *
+ * @return the node offset in the FDT, or a negative error code on failure
+ */
+int fdt_cpu_hartindex_to_offset(u32 hartindex);
+
+/**
+ * Look up the hart index for a given CPU DT node phandle
+ *
+ * @param phandle the phandle of the CPU node
+ *
+ * @return the hart index, or an invalid hart index on failure
+ */
+u32 fdt_cpu_phandle_to_hartindex(u32 phandle);
+
+/**
+ * Look up the hart index for a given CPU interrupt controller DT node phandle
+ *
+ * @param phandle the phandle of the CPU interrupt controller node
+ *
+ * @return the hart index, or an invalid hart index on failure
+ */
+u32 fdt_cpu_intc_phandle_to_hartindex(u32 phandle);
+
+/**
+ * Get the CPU timebase frequency
+ *
+ * @return the timebase frequency in Hertz if known, or else 0
+ */
+u32 fdt_cpu_get_timebase_frequency(void);
+
+/**
+ * Initialize the cache of CPU node information
+ *
+ * @param fdt devicetree blob
+ *
+ * @return 0 on success, or a negative error code on failure
+ */
+int fdt_cpu_init(const void *fdt);
+
+#endif /* __FDT_CPU_H__ */
diff --git a/lib/utils/fdt/fdt_cpu.c b/lib/utils/fdt/fdt_cpu.c
new file mode 100644
index 00000000..e1dafe89
--- /dev/null
+++ b/lib/utils/fdt/fdt_cpu.c
@@ -0,0 +1,118 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024-2026 SiFive
+ */
+
+#include <libfdt.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_hartmask.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi_utils/fdt/fdt_cpu.h>
+#include <sbi_utils/fdt/fdt_helper.h>
+
+struct fdt_cpu_info {
+ int cpu_offset;
+ u32 cpu_phandle;
+ u32 intc_phandle;
+};
+
+static unsigned long fdt_cpu_info_offset;
+static u32 fdt_cpu_timebase_frequency;
+
+int fdt_cpu_hartindex_to_offset(u32 hartindex)
+{
+ struct fdt_cpu_info *info = sbi_scratch_offset_ptr(
+ sbi_hartindex_to_scratch(hartindex), fdt_cpu_info_offset);
+
+ if (!fdt_cached_offsets_valid())
+ return -FDT_ERR_NOTFOUND;
+
+ return info->cpu_offset;
+}
+
+u32 fdt_cpu_phandle_to_hartindex(u32 phandle)
+{
+ /* Assume phandles do not change, so do not check fdt_cached_offsets_valid() */
+
+ sbi_for_each_hartindex(i) {
+ struct fdt_cpu_info *info = sbi_scratch_offset_ptr(
+ sbi_hartindex_to_scratch(i), fdt_cpu_info_offset);
+
+ if (info->cpu_phandle == phandle)
+ return i;
+ }
+
+ return -1U;
+}
+
+u32 fdt_cpu_intc_phandle_to_hartindex(u32 phandle)
+{
+ /* Assume phandles do not change, so do not check fdt_cached_offsets_valid() */
+
+ sbi_for_each_hartindex(i) {
+ struct fdt_cpu_info *info = sbi_scratch_offset_ptr(
+ sbi_hartindex_to_scratch(i), fdt_cpu_info_offset);
+
+ if (info->intc_phandle == phandle)
+ return i;
+ }
+
+ return -1U;
+}
+
+u32 fdt_cpu_get_timebase_frequency(void)
+{
+ return fdt_cpu_timebase_frequency;
+}
+
+int fdt_cpu_init(const void *fdt)
+{
+ int cpu_offset, cpus_offset, err, intc_offset, len;
+ struct sbi_scratch *scratch;
+ struct fdt_cpu_info *info;
+ const fdt32_t *val;
+ u32 hartid;
+
+ fdt_cpu_info_offset = sbi_scratch_alloc_type_offset(struct fdt_cpu_info);
+ if (!fdt_cpu_info_offset)
+ return SBI_ENOMEM;
+
+ cpus_offset = fdt_path_offset(fdt, "/cpus");
+ if (cpus_offset < 0)
+ return SBI_ENOENT;
+
+ val = fdt_getprop(fdt, cpus_offset, "timebase-frequency", &len);
+ if (val && len == sizeof(*val))
+ fdt_cpu_timebase_frequency = fdt32_to_cpu(*val);
+
+ fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
+ err = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
+ if (err)
+ continue;
+
+ /*
+ * There is no need to apply any filters (e.g. disabled nodes)
+ * because those harts will not have an associated hart index.
+ */
+ scratch = sbi_hartid_to_scratch(hartid);
+ if (!scratch)
+ continue;
+
+ info = sbi_scratch_offset_ptr(scratch, fdt_cpu_info_offset);
+ info->cpu_offset = cpu_offset;
+ info->cpu_phandle = fdt_get_phandle(fdt, cpu_offset);
+
+ fdt_for_each_subnode(intc_offset, fdt, cpu_offset) {
+ err = fdt_node_check_compatible(fdt, intc_offset, "riscv,cpu-intc");
+ if (err)
+ continue;
+
+ info->intc_phandle = fdt_get_phandle(fdt, intc_offset);
+ break;
+ }
+ }
+
+ return 0;
+
+}
diff --git a/lib/utils/fdt/objects.mk b/lib/utils/fdt/objects.mk
index 31cf1c5a..f540f53b 100644
--- a/lib/utils/fdt/objects.mk
+++ b/lib/utils/fdt/objects.mk
@@ -9,5 +9,6 @@ libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_early_drivers.carray.o
libsbiutils-objs-$(CONFIG_FDT_DOMAIN) += fdt/fdt_domain.o
libsbiutils-objs-$(CONFIG_FDT_PMU) += fdt/fdt_pmu.o
libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_helper.o
+libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_cpu.o
libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_driver.o
libsbiutils-objs-$(CONFIG_FDT) += fdt/fdt_fixup.o
diff --git a/platform/generic/platform.c b/platform/generic/platform.c
index 1df0280d..34e6c638 100644
--- a/platform/generic/platform.c
+++ b/platform/generic/platform.c
@@ -18,6 +18,7 @@
#include <sbi/sbi_system.h>
#include <sbi/sbi_tlb.h>
#include <sbi_utils/cache/fdt_cmo_helper.h>
+#include <sbi_utils/fdt/fdt_cpu.h>
#include <sbi_utils/fdt/fdt_domain.h>
#include <sbi_utils/fdt/fdt_driver.h>
#include <sbi_utils/fdt/fdt_fixup.h>
@@ -255,11 +256,18 @@ int generic_final_init(bool cold_boot)
int generic_extensions_init(bool cold_boot)
{
+ const void *fdt = fdt_get_address();
+ int rc;
+
if (!cold_boot)
return 0;
+ rc = fdt_cpu_init(fdt);
+ if (rc)
+ return rc;
+
/* Parse the ISA string from FDT and enable the listed extensions */
- return fdt_parse_isa_extensions_all_harts(fdt_get_address());
+ return fdt_parse_isa_extensions_all_harts(fdt);
}
int generic_domains_init(void)
--
2.52.0
More information about the opensbi
mailing list