From: George Guo <[email protected]> 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 <[email protected]> --- 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 98529c71d001..cbb7f8368843 100644 --- a/arch/loongarch/kernel/machine_kexec.c +++ b/arch/loongarch/kernel/machine_kexec.c @@ -313,6 +313,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

