[PATCH kexec-tools 1/4] ppc64: ensure /memreserve/ sections exist in user-provided FDT

Shivang Upadhyay shivangu at linux.ibm.com
Wed Oct 22 06:46:06 PDT 2025


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 <shivangu at linux.ibm.com>
---
 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




More information about the kexec mailing list