[PATCH v2 2/4] LoongArch: kexec: add KHO support for ACPI-only systems
George Guo
dongtai.guo at linux.dev
Fri May 29 07:32:36 PDT 2026
From: George Guo <guodongtai at kylinos.cn>
On ACPI-only systems fdt_setup() returns early when it detects ACPI,
so initial_boot_params remains NULL and the FDT-based kho_load_fdt()
path cannot be used.
machine_kexec_file.c:
- kho_load_fdt(): add an else branch that builds a minimal FDT from
scratch (SZ_1K) containing only a /chosen node with linux,kho-fdt
and linux,kho-scratch properties, using the libfdt creation API.
- Since DEVICE_TREE_GUID is absent from the EFI config table on
ACPI-only systems, build a new config table with DEVICE_TREE_GUID
appended and load it as a kexec segment. Store the result in
kimage_arch so machine_kexec() can switch st->tables before jumping.
- arch_kimage_file_post_load_cleanup(): free the efi_tables kvmalloc
buffer once the kexec image has been loaded.
machine_kexec.c:
- Before jumping, update the EFI system table pointer: for FDT-based
systems update the existing DEVICE_TREE_GUID entry; for ACPI-only
systems switch st->tables / st->nr_tables to the new extended table.
setup.c:
- fdt_setup(): when ACPI is detected, use efi_fdt_pointer() to detect
whether this is a KHO kexec boot. The first kernel switches the EFI
config table to a new one that includes a DEVICE_TREE_GUID entry
pointing to the minimal KHO FDT. If found, call early_init_dt_scan()
so early_init_dt_check_kho() can consume linux,kho-fdt and
linux,kho-scratch, then reset initial_boot_params to NULL so the rest
of the ACPI boot path is unaffected.
kexec.h:
- Add efi_tables, efi_tables_mem, efi_tables_cnt to kimage_arch.
Signed-off-by: George Guo <guodongtai at kylinos.cn>
---
arch/loongarch/include/asm/kexec.h | 3 +
arch/loongarch/kernel/machine_kexec.c | 11 ++
arch/loongarch/kernel/machine_kexec_file.c | 162 +++++++++++++++++++--
arch/loongarch/kernel/setup.c | 21 ++-
4 files changed, 184 insertions(+), 13 deletions(-)
diff --git a/arch/loongarch/include/asm/kexec.h b/arch/loongarch/include/asm/kexec.h
index adf54bfcdd49..e1abaf40b06a 100644
--- a/arch/loongarch/include/asm/kexec.h
+++ b/arch/loongarch/include/asm/kexec.h
@@ -42,6 +42,9 @@ struct kimage_arch {
#ifdef CONFIG_KEXEC_HANDOVER
void *fdt; /* virtual address of KHO FDT segment buffer */
unsigned long fdt_mem; /* physical address of KHO FDT segment */
+ void *efi_tables; /* new EFI config table buffer (virtual) */
+ unsigned long efi_tables_mem; /* physical address of new EFI config table */
+ unsigned long efi_tables_cnt; /* number of entries in new EFI config table */
#endif
};
diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c
index 130f1adfa515..4ee9a433d2de 100644
--- a/arch/loongarch/kernel/machine_kexec.c
+++ b/arch/loongarch/kernel/machine_kexec.c
@@ -308,6 +308,17 @@ void machine_kexec(struct kimage *image)
break;
}
}
+ } else if (internal->efi_tables_mem) {
+ /*
+ * ACPI-only system: DEVICE_TREE_GUID was not in the original
+ * EFI config table. Switch to the new table that was built in
+ * kho_load_fdt() with DEVICE_TREE_GUID appended.
+ */
+ efi_system_table_t *st =
+ (efi_system_table_t *)TO_CACHE(systable_ptr);
+
+ st->tables = internal->efi_tables_mem;
+ st->nr_tables = internal->efi_tables_cnt;
}
#endif
diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c
index bf1e8c1c7e70..c1955d991061 100644
--- a/arch/loongarch/kernel/machine_kexec_file.c
+++ b/arch/loongarch/kernel/machine_kexec_file.c
@@ -10,6 +10,7 @@
#define pr_fmt(fmt) "kexec_file: " fmt
+#include <linux/efi.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/kexec.h>
@@ -20,6 +21,7 @@
#include <linux/string.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
+#include <asm/addrspace.h>
#include <asm/bootinfo.h>
const struct kexec_file_ops * const kexec_file_loaders[] = {
@@ -37,6 +39,8 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
#ifdef CONFIG_KEXEC_HANDOVER
kvfree(image->arch.fdt);
image->arch.fdt = NULL;
+ kvfree(image->arch.efi_tables);
+ image->arch.efi_tables = NULL;
#endif
return kexec_image_post_load_cleanup_default(image);
@@ -68,6 +72,13 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl
* segment. The second kernel reads linux,kho-fdt and linux,kho-scratch
* from /chosen via early_init_dt_check_kho() and calls kho_populate().
*
+ * On FDT-based systems (initial_boot_params != NULL), the current FDT is
+ * copied and the KHO properties are appended to /chosen.
+ *
+ * On ACPI-only systems (initial_boot_params == NULL), a minimal FDT
+ * containing only /chosen is built from scratch. machine_kexec() updates
+ * the EFI config table DEVICE_TREE_GUID entry to point to this segment so
+ * that the second kernel's fdt_setup() can find and parse it.
*/
static int kho_load_fdt(struct kimage *image)
{
@@ -143,24 +154,151 @@ static int kho_load_fdt(struct kimage *image)
* once the kexec image has been loaded.
*/
fdt_pack(fdt);
+ } else {
+ /*
+ * ACPI boot: build a minimal FDT containing only /chosen with
+ * the two KHO properties. No system FDT is available to copy.
+ */
- kbuf.buffer = fdt;
- kbuf.bufsz = fdt_totalsize(fdt);
- kbuf.memsz = kbuf.bufsz;
- kbuf.buf_align = PAGE_SIZE;
- kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+ __be64 prop[2];
- ret = kexec_add_buffer(&kbuf);
- if (ret)
+ fdt_size = SZ_1K;
+ fdt = kvmalloc(fdt_size, GFP_KERNEL);
+ if (!fdt)
+ return -ENOMEM;
+
+ ret = fdt_create(fdt, fdt_size);
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_finish_reservemap(fdt);
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_begin_node(fdt, ""); /* root */
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_property_u32(fdt, "#address-cells", 2);
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_property_u32(fdt, "#size-cells", 2);
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_begin_node(fdt, "chosen");
+ if (ret < 0)
goto out_free;
- image->arch.fdt = fdt;
- image->arch.fdt_mem = kbuf.mem;
- return 0;
- } else {
- return -EINVAL;
+ prop[0] = cpu_to_be64(image->kho.fdt);
+ prop[1] = cpu_to_be64(PAGE_SIZE);
+ ret = fdt_property(fdt, "linux,kho-fdt", prop, sizeof(prop));
+ if (ret < 0)
+ goto out_free;
+
+ prop[0] = cpu_to_be64(image->kho.scratch->mem);
+ prop[1] = cpu_to_be64(image->kho.scratch->bufsz);
+ ret = fdt_property(fdt, "linux,kho-scratch", prop, sizeof(prop));
+ if (ret < 0)
+ goto out_free;
+
+ ret = fdt_end_node(fdt); /* chosen */
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_end_node(fdt); /* root */
+ if (ret < 0)
+ goto out_free;
+ ret = fdt_finish(fdt);
+ if (ret < 0)
+ goto out_free;
}
+ kbuf.buffer = fdt;
+ kbuf.bufsz = fdt_totalsize(fdt);
+ kbuf.memsz = kbuf.bufsz;
+ kbuf.buf_align = PAGE_SIZE;
+ kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+
+ ret = kexec_add_buffer(&kbuf);
+ if (ret)
+ goto out_free;
+
+ image->arch.fdt = fdt;
+ image->arch.fdt_mem = kbuf.mem;
+
+ /*
+ * On ACPI-only systems DEVICE_TREE_GUID is not in the EFI config
+ * table, so the second kernel's efi_fdt_pointer() cannot locate the
+ * KHO FDT. Build a new EFI config table with DEVICE_TREE_GUID added
+ * and load it as a kexec segment; machine_kexec() will update
+ * st->tables / st->nr_tables to point to it before jumping.
+ */
+
+ /*
+ * fw_arg2 is the EFI system table physical address passed by the
+ * firmware/bootloader. Use it directly here because
+ * image->arch.systable_ptr is set later in machine_kexec_prepare(),
+ * which runs after load_other_segments() / kho_load_fdt().
+ */
+ if (!initial_boot_params && fw_arg2) {
+ efi_system_table_t *st =
+ (efi_system_table_t *)TO_CACHE(fw_arg2);
+ efi_config_table_t *ct =
+ (efi_config_table_t *)TO_CACHE((unsigned long)st->tables);
+ unsigned long i;
+ bool found = false;
+
+ /*
+ * Scan the original config table;
+ * DEVICE_TREE_GUID is absent on ACPI-only systems.
+ */
+ for (i = 0; i < st->nr_tables; i++) {
+ if (!efi_guidcmp(ct[i].guid, DEVICE_TREE_GUID)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ size_t old_sz = st->nr_tables * sizeof(efi_config_table_t);
+ size_t new_sz = old_sz + sizeof(efi_config_table_t);
+ efi_config_table_t *new_ct;
+ struct kexec_buf tbuf = {
+ .image = image,
+ .buf_min = 0,
+ .buf_max = ULONG_MAX,
+ .top_down = true,
+ };
+
+ /*
+ * Allocate a new table with n+1 entries and append
+ * the DEVICE_TREE_GUID entry.
+ */
+ new_ct = kvmalloc(new_sz, GFP_KERNEL);
+ if (!new_ct)
+ return -ENOMEM;
+
+ memcpy(new_ct, ct, old_sz);
+ new_ct[st->nr_tables].guid = DEVICE_TREE_GUID;
+ new_ct[st->nr_tables].table = (void *)image->arch.fdt_mem;
+
+ /* Register the new config table as a kexec segment. */
+ tbuf.buffer = new_ct;
+ tbuf.bufsz = new_sz;
+ tbuf.memsz = new_sz;
+ tbuf.buf_align = sizeof(void *);
+ tbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+
+ ret = kexec_add_buffer(&tbuf);
+ if (ret) {
+ kvfree(new_ct);
+ return ret;
+ }
+
+ image->arch.efi_tables = new_ct;
+ image->arch.efi_tables_mem = tbuf.mem;
+ image->arch.efi_tables_cnt = st->nr_tables + 1;
+ }
+ }
+
+ return 0;
+
out_free:
kvfree(fdt);
return ret;
diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
index 839b23edee87..c82067d1dc75 100644
--- a/arch/loongarch/kernel/setup.c
+++ b/arch/loongarch/kernel/setup.c
@@ -286,8 +286,27 @@ static void __init fdt_setup(void)
void *fdt_pointer;
/* ACPI-based systems do not require parsing fdt */
- if (acpi_os_get_root_pointer())
+ if (acpi_os_get_root_pointer()) {
+#ifdef CONFIG_KEXEC_HANDOVER
+ /*
+ * On a KHO kexec boot the first kernel builds a minimal FDT
+ * containing only /chosen with linux,kho-fdt and
+ * linux,kho-scratch, and switches the EFI config table to a
+ * new one that includes a DEVICE_TREE_GUID entry pointing to
+ * it. Use efi_fdt_pointer() to detect this case.
+ *
+ * Call early_init_dt_scan() to let early_init_dt_check_kho()
+ * consume the KHO data, then reset initial_boot_params so the
+ * rest of the ACPI boot path is not confused by this FDT.
+ */
+ fdt_pointer = efi_fdt_pointer();
+ if (fdt_pointer && !fdt_check_header(fdt_pointer)) {
+ early_init_dt_scan(fdt_pointer, __pa(fdt_pointer));
+ initial_boot_params = NULL;
+ }
+#endif
return;
+ }
/* Prefer to use built-in dtb, checking its legality first. */
if (IS_ENABLED(CONFIG_BUILTIN_DTB) && !fdt_check_header(__dtb_start))
--
2.25.1
More information about the kexec
mailing list