mirror of
https://github.com/riscv-software-src/opensbi.git
synced 2026-09-29 19:01:47 +01:00
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:
+97
-40
@@ -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",
|
||||||
|
"disabled");
|
||||||
/*
|
|
||||||
* 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");
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user