User-provided FDTs may lack `/memreserve/` sections, leading to hangs during kexec. To fix this, allocate a new FDT buffer and use libfdt to add reserve entries for the initrd, RTAS/OPAL regions (via /proc/device-tree), and the FDT itself.
Signed-off-by: Shivang Upadhyay <[email protected]> --- kexec/arch/ppc64/kexec-elf-ppc64.c | 165 +++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/kexec/arch/ppc64/kexec-elf-ppc64.c b/kexec/arch/ppc64/kexec-elf-ppc64.c index 858c994..e287392 100644 --- a/kexec/arch/ppc64/kexec-elf-ppc64.c +++ b/kexec/arch/ppc64/kexec-elf-ppc64.c @@ -40,6 +40,9 @@ #include <libfdt.h> #include <arch/fdt.h> #include <arch/options.h> +#include <dirent.h> +#include <assert.h> +#include <linux/limits.h> uint64_t initrd_base, initrd_size; unsigned char reuse_initrd = 0; @@ -180,6 +183,162 @@ out: return ret; } +static int read_proc_file(char* filename, char* buf) { + FILE* f; + int len; + + f = fopen(filename, "r"); + if (f == NULL) { + perror("unable to open file"); + } + len = fread(buf, 1, 10, f); + fclose(f); + + return len; +} + + +static void add_reserve_mem(void* dtb, uint64_t where, uint64_t length) +{ + int ret; + + ret = fdt_add_mem_rsv(dtb, where, length); + assert(ret == 0 && "Failed to add memory reservation block to FDT."); + + return; +} + +/* this function is similar to kexec/fs2dt.c's checkprop function, only part + * different here is, this version uses libfdt's function to mark the reserve + * section + */ +static void checkprop(void* dtb, char *name, unsigned *data, int len) +{ + static unsigned long long base, size, end; + + if ((data == NULL) && (base || size || end)) + die("unrecoverable error: no property data"); + else if (!strcmp(name, "linux,rtas-base")) + base = be32_to_cpu(*data); + else if (!strcmp(name, "opal-base-address")) + base = be64_to_cpu(*(unsigned long long *)data); + else if (!strcmp(name, "opal-runtime-size")) + size = be64_to_cpu(*(unsigned long long *)data); + else if (!strcmp(name, "linux,tce-base")) + base = be64_to_cpu(*(unsigned long long *) data); + else if (!strcmp(name, "rtas-size") || + !strcmp(name, "linux,tce-size")) + size = be32_to_cpu(*data); + else if (reuse_initrd && !strcmp(name, "linux,initrd-start")) { + if (len == 8) + base = be64_to_cpu(*(unsigned long long *) data); + else + base = be32_to_cpu(*data); + } else if (reuse_initrd && !strcmp(name, "linux,initrd-end")) { + if (len == 8) + end = be64_to_cpu(*(unsigned long long *) data); + else + end = be32_to_cpu(*data); + } + + if (size && end) + die("unrecoverable error: size and end set at same time\n"); + if (base && size) { + add_reserve_mem(dtb, base, size); + base = size = 0; + } + if (base && end) { + add_reserve_mem(dtb, base, end-base); + base = end = 0; + } +} + +static void file_traversal(void *dtb, const char *pathname) { + DIR *dir = opendir(pathname); + if (!dir) { + perror("opendir failed"); + return; + } + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + // Skip '.' and '..' + if (entry->d_name[0] == '.') + continue; + + char full_path[PATH_MAX]; + snprintf(full_path, sizeof(full_path), "%s/%s", pathname, entry->d_name); + + // Check if entry is a directory + if (entry->d_type == DT_DIR) { + file_traversal(dtb, full_path); + } else { + char file_data[10]; + int readlen = read_proc_file(full_path, file_data); + if (readlen > 0) { + checkprop(dtb, entry->d_name, + (unsigned *)file_data, readlen); + } + } + } + + closedir(dir); +} + +void patch_devicetree_with_initrd_info(char* dtb, uint64_t initrd_base, + uint64_t initrd_size) { + + int ret, offset; + unsigned long initrd_end = initrd_base + initrd_size; + uint64_t base_be = cpu_to_be64(initrd_base); + uint64_t end_be = cpu_to_be64(initrd_end); + + ret = 0; + offset = fdt_path_offset(dtb, "/chosen"); + assert(offset >= 0 && "failed to find the /chosen node"); + + ret = fdt_setprop(dtb, offset, "linux,initrd-start", &base_be, sizeof(base_be)); + assert(ret == 0 && "failed to set initrd-start on dtb"); + + ret = fdt_setprop(dtb, offset, "linux,initrd-end", &end_be, sizeof(end_be)); + assert(ret == 0 && "failed to set initrd-end on dtb"); + +} + + +static void patch_devicetree(char *dtb, uint64_t initrd_base, + uint64_t initrd_size) +{ + int ret; + + patch_devicetree_with_initrd_info(dtb, initrd_base, initrd_size); + + file_traversal(dtb, "/proc/device-tree"); + + ret = fdt_add_mem_rsv(dtb, initrd_base, initrd_size); + assert(ret == 0 && "failed to add rsvmap"); + + ret = fdt_add_mem_rsv(dtb, 0, fdt_totalsize(dtb)); + assert(ret == 0 && "failed to add rsvmap"); + + fdt_set_boot_cpuid_phys(dtb, 0x0); + fdt_set_last_comp_version(dtb, 17); + +} + +static char* alloc_new_dtb(char* dtb, off_t* newsize) { + int ret; + + *newsize = fdt_totalsize(dtb) + 256; + dtb = (char*) realloc(dtb, *newsize); + ret = fdt_open_into(dtb, dtb, *newsize); + + assert(ret == 0 && + "fdt_open_into failed"); + + return dtb; +} + int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len, struct kexec_info *info) { @@ -340,6 +499,12 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len, if (devicetreeblob) { /* Grab device tree from buffer */ seg_buf = slurp_file(devicetreeblob, &seg_size); + + if (ramdisk) { + seg_buf = alloc_new_dtb(seg_buf, &seg_size); + patch_devicetree(seg_buf, initrd_base, initrd_size); + } + } else { /* create from fs2dt */ create_flatten_tree(&seg_buf, &seg_size, cmdline); -- 2.51.0
