lib/utils/fdt: Reserve exactly enough FDT space for fdt_cpu_fixup

fdt_cpu_fixup() reserves a hardcoded 32 extra bytes, plus 16 more per
HART inside the "zicntr" loop. Those 32 bytes also have to cover every
"status" property rewritten to "disabled", so once more than a handful
of HARTs are disabled the fixups fail with FDT_ERR_NOSPACE (-3).

Bumping the reservation to a fixed worst case is not safe either:
fdt_open_into() cannot know how much space the caller's buffer really
has, it just trusts the requested size. As OpenSBI does not own the
memory following a device tree passed in by the previous booting stage,
over-declaring the blob risks corrupting whatever follows it.

Walk the HART nodes once without modifying them instead, and reserve
exactly what the fixups need. The read-only decision logic is factored
into fdt_cpu_fixup_needed() so the sizing and fixup passes cannot drift
apart, which also makes the per-HART fdt_open_into() redundant. Check
the return values of fdt_setprop_string() and fdt_appendprop_string()
as well, as running out of space used to fail silently.

Fixes: dd9439fbac ("lib: utils: Add a fdt_cpu_fixup() helper")
Signed-off-by: Chen Pei <cp0613@linux.alibaba.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Link: https://lore.kernel.org/r/20260817084303.1232-1-cp0613@linux.alibaba.com
Signed-off-by: Anup Patel <anup@brainfault.org>
This commit is contained in:
Chen Pei
2026-09-28 11:41:25 +05:30
committed by Anup Patel
parent 5b1e739428
commit 0ffaa1bc65
+96 -39
View File
@@ -106,14 +106,65 @@ int fdt_add_cpu_idle_states(void *fdt, const struct sbi_cpu_idle_state *state)
return 0; return 0;
} }
/**
* Report which fixups a HART DT node needs. Read-only, so the sizing pass
* and the fixup pass below stay in sync by construction.
*/
static void fdt_cpu_fixup_needed(const void *fdt, int cpu_offset,
bool emulated_zicntr, bool *disable,
bool *add_zicntr)
{
struct sbi_domain *dom = sbi_domain_thishart_ptr();
const char *mmu_type, *extensions;
u32 hartid, hartindex;
int len;
*disable = false;
*add_zicntr = false;
if (fdt_parse_hart_id(fdt, cpu_offset, &hartid))
return;
if (!fdt_node_is_enabled(fdt, cpu_offset))
return;
/*
* Disable a HART DT node if one of the following is true:
* 1. The HART is not assigned to the current domain
* 2. MMU is not available for the HART
*/
hartindex = sbi_hartid_to_hartindex(hartid);
mmu_type = fdt_getprop(fdt, cpu_offset, "mmu-type", &len);
if (!sbi_domain_is_assigned_hart(dom, hartindex) || !mmu_type || !len)
*disable = true;
if (!emulated_zicntr)
return;
extensions = fdt_getprop(fdt, cpu_offset, "riscv,isa-extensions", &len);
/*
* For legacy devicetrees, don't create riscv,isa-extensions
* property if there hasn't been already one.
*/
if (extensions && !fdt_stringlist_contains(extensions, len, "zicntr"))
*add_zicntr = true;
}
void fdt_cpu_fixup(void *fdt) void fdt_cpu_fixup(void *fdt)
{ {
struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
struct sbi_domain *dom = sbi_domain_thishart_ptr(); bool emulated_zicntr, disable, add_zicntr;
int err, cpu_offset, cpus_offset, len; int err, cpu_offset, cpus_offset;
const char *mmu_type, *extensions; int reserve = 0;
u32 hartid, hartindex; /*
bool emulated_zicntr; * A new "status" property costs a header, its tag-aligned value and its
* name; appending "zicntr" only grows an existing tag-aligned value.
*/
const int status_size = sizeof(struct fdt_property) +
ROUNDUP(sizeof("disabled"), FDT_TAGSIZE) +
sizeof("status");
const int zicntr_size = ROUNDUP(sizeof("zicntr"), FDT_TAGSIZE);
/* /*
* Claim Zicntr extension in riscv,isa-extensions if * Claim Zicntr extension in riscv,isa-extensions if
@@ -124,52 +175,58 @@ void fdt_cpu_fixup(void *fdt)
sbi_hart_has_csr(scratch, SBI_HART_CSR_CYCLE) && sbi_hart_has_csr(scratch, SBI_HART_CSR_CYCLE) &&
sbi_hart_has_csr(scratch, SBI_HART_CSR_INSTRET); sbi_hart_has_csr(scratch, SBI_HART_CSR_INSTRET);
err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 32); cpus_offset = fdt_path_offset(fdt, "/cpus");
if (cpus_offset < 0)
return;
/*
* fdt_open_into() trusts the size it is given, so reserve only what is
* really needed instead of a fixed worst case for every possible HART.
*/
fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
fdt_cpu_fixup_needed(fdt, cpu_offset, emulated_zicntr,
&disable, &add_zicntr);
if (disable)
reserve += status_size;
if (add_zicntr)
reserve += zicntr_size;
}
if (!reserve)
return;
err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + reserve);
if (err < 0) if (err < 0)
return; return;
/* fdt_open_into() may have reordered the blocks, so look up again. */
cpus_offset = fdt_path_offset(fdt, "/cpus"); cpus_offset = fdt_path_offset(fdt, "/cpus");
if (cpus_offset < 0) if (cpus_offset < 0)
return; return;
fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) { fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
err = fdt_parse_hart_id(fdt, cpu_offset, &hartid); fdt_cpu_fixup_needed(fdt, cpu_offset, emulated_zicntr,
if (err) &disable, &add_zicntr);
continue;
if (!fdt_node_is_enabled(fdt, cpu_offset)) if (disable) {
continue; err = fdt_setprop_string(fdt, cpu_offset, "status",
/*
* Disable a HART DT node if one of the following is true:
* 1. The HART is not assigned to the current domain
* 2. MMU is not available for the HART
*/
hartindex = sbi_hartid_to_hartindex(hartid);
mmu_type = fdt_getprop(fdt, cpu_offset, "mmu-type", &len);
if (!sbi_domain_is_assigned_hart(dom, hartindex) ||
!mmu_type || !len)
fdt_setprop_string(fdt, cpu_offset, "status",
"disabled"); "disabled");
if (!emulated_zicntr)
continue;
extensions = fdt_getprop(fdt, cpu_offset,
"riscv,isa-extensions", &len);
/*
* For legacy devicetrees, don't create riscv,isa-extensions
* property if there hasn't been already one.
*/
if (extensions &&
!fdt_stringlist_contains(extensions, len, "zicntr")) {
err = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + 16);
if (err) if (err)
continue; sbi_printf("%s: failed to disable %s (%d)\n",
__func__,
fdt_get_name(fdt, cpu_offset, NULL),
err);
}
fdt_appendprop_string(fdt, cpu_offset, if (add_zicntr) {
"riscv,isa-extensions", "zicntr"); err = fdt_appendprop_string(fdt, cpu_offset,
"riscv,isa-extensions",
"zicntr");
if (err)
sbi_printf("%s: failed to add zicntr to %s (%d)\n",
__func__,
fdt_get_name(fdt, cpu_offset, NULL),
err);
} }
} }
} }