[PATCH 2/2] powerpc: Take in account addition CPU node when building kexec FDT

From: Laurent Dufour
Date: Fri Oct 28 2022 - 12:08:20 EST


On a system with a large number of CPUs, the creation of the FDT for a
kexec kernel may fail because the allocated FDT is not large enough.

When this happens, such a message is displayed on the console:

Unable to add ibm,processor-vadd-size property: FDT_ERR_NOSPACE

The property's name may change depending when the buffer overwrite is
detected.

Obviously the created FDT is missing information, and it is expected that
system dump or kexec kernel failed to run properly.

When the FDT is allocated, the size of the FDT the kernel received at boot
time is used and an extra size can be applied. Currently, only memory added
after boot time is taken in account, not the CPU nodes.

The extra size should take in account these additional CPU nodes and
compute the required extra space. To achieve that, the size of a CPU node,
including its subnode is computed once and multiplied by the number of
additional CPU nodes.

The assumption is that the size of the CPU node is _same_ for all the node,
the only variable part should be the name "PowerPC,POWERxx@##" where "##"
may vary a bit.

Signed-off-by: Laurent Dufour <ldufour@xxxxxxxxxxxxx>
---
arch/powerpc/kexec/file_load_64.c | 59 ++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)

diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c
index 349a781cea0b..1476922cd7c5 100644
--- a/arch/powerpc/kexec/file_load_64.c
+++ b/arch/powerpc/kexec/file_load_64.c
@@ -928,6 +928,46 @@ int setup_purgatory_ppc64(struct kimage *image, const void *slave_code,
return ret;
}

+/**
+ * get_cpu_node_size - Compute the size of a CPU node in the FDT.
+ * This should be done only once and the value is stored in
+ * a static variable.
+ * Returns the max size of a CPU node in the FDT.
+ */
+static unsigned int cpu_node_size(void)
+{
+ static unsigned int cpu_node_size;
+ struct device_node *dn;
+ struct property *pp;
+
+ /*
+ * Don't compute it twice, we are assuming that the per CPU node size
+ * doesn't change during the system's life.
+ */
+ if (cpu_node_size)
+ return cpu_node_size;
+
+ dn = of_find_node_by_type(NULL, "cpu");
+ if (!dn) {
+ /* Unlikely to happen */
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ /*
+ * We compute the sub node size for a CPU node, assuming it
+ * will be the same for all.
+ */
+ cpu_node_size += strlen(dn->name) + 5;
+ for_each_property_of_node(dn, pp) {
+ cpu_node_size += strlen(pp->name);
+ cpu_node_size += pp->length;
+ }
+
+ of_node_put(dn);
+ return cpu_node_size;
+}
+
/**
* kexec_extra_fdt_size_ppc64 - Return the estimated additional size needed to
* setup FDT for kexec/kdump kernel.
@@ -937,7 +977,10 @@ int setup_purgatory_ppc64(struct kimage *image, const void *slave_code,
*/
unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image)
{
+ struct device_node *dn;
u64 usm_entries;
+ unsigned int cpu_nodes = 0;
+ unsigned int extra_size;

if (image->type != KEXEC_TYPE_CRASH)
return 0;
@@ -949,7 +992,21 @@ unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image)
*/
usm_entries = ((memblock_end_of_DRAM() / drmem_lmb_size()) +
(2 * (resource_size(&crashk_res) / drmem_lmb_size())));
- return (unsigned int)(usm_entries * sizeof(u64));
+
+ extra_size = (unsigned int)(usm_entries * sizeof(u64));
+
+ /*
+ * Get the number of CPU nodes in the current DT. This allows to
+ * reserve places for CPU nodes added since the boot time.
+ */
+ for_each_node_by_type(dn, "cpu") {
+ cpu_nodes++;
+ }
+
+ if (cpu_nodes > boot_cpu_node_count)
+ extra_size += (cpu_nodes - boot_cpu_node_count) * cpu_node_size();
+
+ return extra_size;
}

/**
--
2.38.1