powerpc/kexec_file: Use current CPU info while setting up FDT
authorSourabh Jain <sourabhjain@linux.ibm.com>
Thu, 29 Apr 2021 06:02:56 +0000 (11:32 +0530)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 4 May 2021 12:26:57 +0000 (22:26 +1000)
kexec_file_load() uses initial_boot_params in setting up the device tree
for the kernel to be loaded. Though initial_boot_params holds info about
CPUs at the time of boot, it doesn't account for hot added CPUs.

So, kexec'ing with kexec_file_load() syscall leaves the kexec'ed kernel
with inaccurate CPU info.

If kdump kernel is loaded with kexec_file_load() syscall and the system
crashes on a hot added CPU, the capture kernel hangs failing to identify
the boot CPU, with no output.

To avoid this from happening, extract current CPU info from of_root
device node and use it for setting up the fdt in kexec_file_load case.

Fixes: 6ecd0163d360 ("powerpc/kexec_file: Add appropriate regions for memory reserve map")
Cc: stable@vger.kernel.org # v5.9+
Signed-off-by: Sourabh Jain <sourabhjain@linux.ibm.com>
Reviewed-by: Hari Bathini <hbathini@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20210429060256.199714-1-sourabhjain@linux.ibm.com
arch/powerpc/kexec/file_load_64.c

index f9eb49e23ec82db6a16afb81e8c206b04728c63c..5056e175ca2c4f57f9e9fb13017fd48bd4d4ce55 100644 (file)
@@ -950,6 +950,93 @@ unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image)
        return (unsigned int)(usm_entries * sizeof(u64));
 }
 
+/**
+ * add_node_props - Reads node properties from device node structure and add
+ *                  them to fdt.
+ * @fdt:            Flattened device tree of the kernel
+ * @node_offset:    offset of the node to add a property at
+ * @dn:             device node pointer
+ *
+ * Returns 0 on success, negative errno on error.
+ */
+static int add_node_props(void *fdt, int node_offset, const struct device_node *dn)
+{
+       int ret = 0;
+       struct property *pp;
+
+       if (!dn)
+               return -EINVAL;
+
+       for_each_property_of_node(dn, pp) {
+               ret = fdt_setprop(fdt, node_offset, pp->name, pp->value, pp->length);
+               if (ret < 0) {
+                       pr_err("Unable to add %s property: %s\n", pp->name, fdt_strerror(ret));
+                       return ret;
+               }
+       }
+       return ret;
+}
+
+/**
+ * update_cpus_node - Update cpus node of flattened device tree using of_root
+ *                    device node.
+ * @fdt:              Flattened device tree of the kernel.
+ *
+ * Returns 0 on success, negative errno on error.
+ */
+static int update_cpus_node(void *fdt)
+{
+       struct device_node *cpus_node, *dn;
+       int cpus_offset, cpus_subnode_offset, ret = 0;
+
+       cpus_offset = fdt_path_offset(fdt, "/cpus");
+       if (cpus_offset < 0 && cpus_offset != -FDT_ERR_NOTFOUND) {
+               pr_err("Malformed device tree: error reading /cpus node: %s\n",
+                      fdt_strerror(cpus_offset));
+               return cpus_offset;
+       }
+
+       if (cpus_offset > 0) {
+               ret = fdt_del_node(fdt, cpus_offset);
+               if (ret < 0) {
+                       pr_err("Error deleting /cpus node: %s\n", fdt_strerror(ret));
+                       return -EINVAL;
+               }
+       }
+
+       /* Add cpus node to fdt */
+       cpus_offset = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), "cpus");
+       if (cpus_offset < 0) {
+               pr_err("Error creating /cpus node: %s\n", fdt_strerror(cpus_offset));
+               return -EINVAL;
+       }
+
+       /* Add cpus node properties */
+       cpus_node = of_find_node_by_path("/cpus");
+       ret = add_node_props(fdt, cpus_offset, cpus_node);
+       of_node_put(cpus_node);
+       if (ret < 0)
+               return ret;
+
+       /* Loop through all subnodes of cpus and add them to fdt */
+       for_each_node_by_type(dn, "cpu") {
+               cpus_subnode_offset = fdt_add_subnode(fdt, cpus_offset, dn->full_name);
+               if (cpus_subnode_offset < 0) {
+                       pr_err("Unable to add %s subnode: %s\n", dn->full_name,
+                              fdt_strerror(cpus_subnode_offset));
+                       ret = cpus_subnode_offset;
+                       goto out;
+               }
+
+               ret = add_node_props(fdt, cpus_subnode_offset, dn);
+               if (ret < 0)
+                       goto out;
+       }
+out:
+       of_node_put(dn);
+       return ret;
+}
+
 /**
  * setup_new_fdt_ppc64 - Update the flattend device-tree of the kernel
  *                       being loaded.
@@ -1006,6 +1093,11 @@ int setup_new_fdt_ppc64(const struct kimage *image, void *fdt,
                }
        }
 
+       /* Update cpus nodes information to account hotplug CPUs. */
+       ret =  update_cpus_node(fdt);
+       if (ret < 0)
+               goto out;
+
        /* Update memory reserve map */
        ret = get_reserved_memory_ranges(&rmem);
        if (ret)