Re: [PATCH v4 2/4] LoongArch: kexec: add KHO support
Huacai Chen <[email protected]>
| Newsgroups | org.kernel.vger.linux-efi,dev.linux.lists.loongarch,org.infradead.lists.kexec,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kvack.linux-mm |
|---|---|
| Message-ID | <CAAhV-H4PKRNCa4g=6TGepP+trtM82JiMoWgVr3O7PqbZYP9uGw@mail.gmail.com> |
Hi, George, On Fri, Aug 7, 2026 at 6:38 PM George Guo <[email protected]> wrote: > > From: George Guo <[email protected]> > > Enable Kexec Handover (KHO) on LoongArch64. > > LoongArch has no boot FDT: the efistub passes the EFI system table and > the command line to the core kernel directly, so the arm64 /chosen path > (append linux,kho-fdt / linux,kho-scratch and let > early_init_dt_check_kho() read them) does not apply. Follow the x86 > model instead, which has no boot FDT either: carry the KHO pointer out of > band and call kho_populate() directly. The channel is the EFI > configuration table entry added by the previous patch. > > - Kconfig: ARCH_SUPPORTS_KEXEC_HANDOVER is def_bool 64BIT. > - machine_kexec_file.c: kho_load_data() builds a small handover blob > (struct linux_efi_kho_data) holding the KHO state FDT and scratch > addresses, and a new EFI configuration table with a > LINUX_EFI_KHO_TABLE_GUID entry pointing to it; both are loaded as kexec > segments. > - machine_kexec.c: before jumping to the next kernel, switch the EFI > system table to the extended configuration table. > - setup.c: kho_populate_from_efi() scans the configuration table for > LINUX_EFI_KHO_TABLE_GUID and calls kho_populate() from setup_arch(), > after efi_init() and before memblock_init(). > > Handover is set up by the kexec_file_load() syscall only. kho_load_data() > runs from load_other_segments(), which the older kexec_load() syscall does > not reach. This matches x86, where KHO lives in the bzImage64 loader. > > Tested on a LoongArch machine booting through ACPI/UEFI. After the kexec, > the second kernel reports > > KHO: found kexec handover data. > > and the two-stage test passes: luo_kexec_simple --stage 1, kexec into the > second kernel, then luo_kexec_simple --stage 2. > > Co-developed-by: Kexin Liu <[email protected]> > Signed-off-by: Kexin Liu <[email protected]> > Signed-off-by: George Guo <[email protected]> > --- > arch/loongarch/Kconfig | 3 + > arch/loongarch/include/asm/kexec.h | 7 ++ > arch/loongarch/kernel/machine_kexec.c | 16 +++ > arch/loongarch/kernel/machine_kexec_file.c | 131 +++++++++++++++++++++ > arch/loongarch/kernel/setup.c | 41 +++++++ > 5 files changed, 198 insertions(+) > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > index d8d252325017..698828ef70f0 100644 > --- a/arch/loongarch/Kconfig > +++ b/arch/loongarch/Kconfig > @@ -684,6 +684,9 @@ config ARCH_SUPPORTS_KEXEC > config ARCH_SUPPORTS_KEXEC_FILE > def_bool 64BIT > > +config ARCH_SUPPORTS_KEXEC_HANDOVER > + def_bool 64BIT > + > config ARCH_SELECTS_KEXEC_FILE > def_bool 64BIT > depends on KEXEC_FILE > diff --git a/arch/loongarch/include/asm/kexec.h b/arch/loongarch/include/asm/kexec.h > index 6be136e9f0a0..2bcc8bc0d3cc 100644 > --- a/arch/loongarch/include/asm/kexec.h > +++ b/arch/loongarch/include/asm/kexec.h > @@ -39,6 +39,13 @@ struct kimage_arch { > unsigned long efi_boot; > unsigned long cmdline_ptr; > unsigned long systable_ptr; > +#ifdef CONFIG_KEXEC_HANDOVER > + void *kho_data; /* KHO handover blob buffer (virtual) */ > + unsigned long kho_data_mem; /* physical address of the KHO handover blob */ > + 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 > }; > > struct kimage; > diff --git a/arch/loongarch/kernel/machine_kexec.c b/arch/loongarch/kernel/machine_kexec.c > index 1883cae93bc3..c98a5607684d 100644 > --- a/arch/loongarch/kernel/machine_kexec.c > +++ b/arch/loongarch/kernel/machine_kexec.c > @@ -6,6 +6,7 @@ > */ > #include <linux/compiler.h> > #include <linux/cpu.h> > +#include <linux/efi.h> > #include <linux/kexec.h> > #include <linux/crash_dump.h> > #include <linux/delay.h> > @@ -287,6 +288,21 @@ void machine_kexec(struct kimage *image) > pr_notice("We will call new kernel at 0x%lx\n", start_addr); > pr_notice("Bye ...\n"); > > +#ifdef CONFIG_KEXEC_HANDOVER > + /* > + * KHO: switch the EFI system table to the extended configuration table > + * built in kho_load_data(), which carries the LINUX_EFI_KHO_TABLE_GUID > + * entry the next kernel reads to find the KHO handover blob. > + */ > + if (internal->efi_tables_mem) { > + 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 > + > /* Make reboot code buffer available to the boot CPU. */ > flush_cache_all(); > > diff --git a/arch/loongarch/kernel/machine_kexec_file.c b/arch/loongarch/kernel/machine_kexec_file.c > index 5584b798ba46..d4c2243b6cb0 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> > @@ -18,6 +19,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[] = { > @@ -32,6 +34,13 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image) > image->elf_headers = NULL; > image->elf_headers_sz = 0; > > +#ifdef CONFIG_KEXEC_HANDOVER > + kfree(image->arch.kho_data); > + image->arch.kho_data = NULL; > + kvfree(image->arch.efi_tables); > + image->arch.efi_tables = NULL; > +#endif > + > return kexec_image_post_load_cleanup_default(image); > } > > @@ -55,6 +64,121 @@ static void cmdline_add_initrd(struct kimage *image, unsigned long *cmdline_tmpl > *cmdline_tmplen += initrd_strlen; > } > > +#ifdef CONFIG_KEXEC_HANDOVER > +/* > + * Hand the KHO state to the next kernel through a dedicated EFI configuration > + * table entry. > + * > + * LoongArch has no boot FDT: the efistub passes the EFI system table and the > + * command line to the core kernel directly. So instead of the arm64 /chosen > + * FDT path, build a small handover blob (struct linux_efi_kho_data) holding the > + * KHO state FDT and scratch addresses, register it in the EFI configuration > + * table under LINUX_EFI_KHO_TABLE_GUID, and let the next kernel read it and > + * call kho_populate() directly. > + * > + * Both the blob and the extended configuration table are loaded as kexec > + * segments; machine_kexec() switches st->tables to the new table before jumping. > + * > + * image->kho.fdt and image->kho.scratch are filled in by kho_fill_kimage() > + * before the arch loader runs, so they are valid here. > + */ > +static int kho_load_data(struct kimage *image) > +{ > + struct linux_efi_kho_data *kho; > + efi_system_table_t *st; > + efi_config_table_t *ct, *new_ct; > + size_t old_sz, new_sz; > + struct kexec_buf kbuf = { > + .image = image, > + .buf_min = 0, > + .buf_max = ULONG_MAX, > + .top_down = true, > + }; > + int ret; > + > + if (!image->kho.fdt || !image->kho.scratch) > + return 0; > + > + if (!fw_arg2) { > + pr_err("KHO requires an EFI boot, no EFI system table found\n"); > + return -EINVAL; > + } This check is unnecessary, if no EFI system table, you cannot boot your system, and have no chance for kexec. > + > + /* Build the handover blob and load it as a kexec segment. */ > + kho = kzalloc(sizeof(*kho), GFP_KERNEL); > + if (!kho) > + return -ENOMEM; > + > + kho->fdt_addr = image->kho.fdt; > + kho->fdt_size = PAGE_SIZE; > + kho->scratch_addr = image->kho.scratch->mem; > + kho->scratch_size = image->kho.scratch->memsz; > + > + kbuf.buffer = kho; > + kbuf.bufsz = sizeof(*kho); > + kbuf.memsz = sizeof(*kho); > + kbuf.buf_align = sizeof(u64); > + kbuf.mem = KEXEC_BUF_MEM_UNKNOWN; > + > + ret = kexec_add_buffer(&kbuf); > + if (ret) { > + kfree(kho); > + return ret; > + } > + image->arch.kho_data = kho; > + image->arch.kho_data_mem = kbuf.mem; > + > + kexec_dprintk("Loaded KHO handover blob at 0x%lx bufsz=0x%lx memsz=0x%lx\n", > + image->arch.kho_data_mem, kbuf.bufsz, kbuf.memsz); > + kexec_dprintk("KHO fdt at 0x%llx, scratch at 0x%llx size 0x%llx\n", > + kho->fdt_addr, kho->scratch_addr, kho->scratch_size); > + > + /* > + * Build a new EFI configuration table with a LINUX_EFI_KHO_TABLE_GUID > + * entry appended, pointing at the handover blob, and load it as a kexec > + * segment. machine_kexec() updates 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 because image->arch.systable_ptr > + * is set later in machine_kexec_prepare(), which runs after this. > + */ > + st = (efi_system_table_t *)TO_CACHE(fw_arg2); > + ct = (efi_config_table_t *)TO_CACHE((unsigned long)st->tables); > + old_sz = st->nr_tables * sizeof(efi_config_table_t); > + new_sz = old_sz + sizeof(efi_config_table_t); > + > + new_ct = kvmalloc(new_sz, GFP_KERNEL); > + if (!new_ct) > + return -ENOMEM; > + > + memcpy(new_ct, ct, old_sz); > + new_ct[st->nr_tables].guid = LINUX_EFI_KHO_TABLE_GUID; > + new_ct[st->nr_tables].table = (void *)image->arch.kho_data_mem; > + > + kbuf.buffer = new_ct; > + kbuf.bufsz = new_sz; > + kbuf.memsz = new_sz; > + kbuf.buf_align = sizeof(void *); > + kbuf.mem = KEXEC_BUF_MEM_UNKNOWN; > + > + ret = kexec_add_buffer(&kbuf); > + if (ret) { > + kvfree(new_ct); > + return ret; > + } > + image->arch.efi_tables = new_ct; > + image->arch.efi_tables_mem = kbuf.mem; > + image->arch.efi_tables_cnt = st->nr_tables + 1; > + > + kexec_dprintk("Loaded EFI config table at 0x%lx bufsz=0x%lx memsz=0x%lx nr_tables=%lu\n", > + image->arch.efi_tables_mem, kbuf.bufsz, kbuf.memsz, > + image->arch.efi_tables_cnt); > + > + return 0; > +} > +#endif > + > #ifdef CONFIG_CRASH_DUMP > > static int prepare_elf_headers(void **addr, unsigned long *sz) > @@ -220,6 +344,13 @@ int load_other_segments(struct kimage *image, > cmdline_add_initrd(image, &cmdline_tmplen, modified_cmdline, initrd_load_addr); > } > > +#ifdef CONFIG_KEXEC_HANDOVER > + /* Load the KHO handover blob and the extended EFI configuration table */ > + ret = kho_load_data(image); > + if (ret) > + goto out_err; > +#endif > + > if (cmdline_len + cmdline_tmplen > COMMAND_LINE_SIZE) { > pr_err("Appending command line exceeds COMMAND_LINE_SIZE\n"); > ret = -EINVAL; > diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c > index 6fa4a22a58fd..1e2fbf37aecf 100644 > --- a/arch/loongarch/kernel/setup.c > +++ b/arch/loongarch/kernel/setup.c > @@ -20,6 +20,7 @@ > #include <linux/initrd.h> > #include <linux/ioport.h> > #include <linux/kexec.h> > +#include <linux/kexec_handover.h> > #include <linux/crash_dump.h> > #include <linux/root_dev.h> > #include <linux/console.h> > @@ -280,6 +281,45 @@ static void __init arch_reserve_crashkernel(void) > reserve_crashkernel_generic(crash_size, crash_base, low_size, high); > } > > +#ifdef CONFIG_KEXEC_HANDOVER > +/* > + * On a KHO kexec boot the previous kernel registered a handover blob in the EFI > + * configuration table under LINUX_EFI_KHO_TABLE_GUID (see > + * arch/loongarch/kernel/machine_kexec_file.c). Scan the configuration table > + * for it, read the KHO state FDT and scratch addresses, and hand them to the > + * KHO core. fw_arg2 is the EFI system table physical address. > + * > + * This runs from setup_arch(), after efi_init() and before memblock_init(), > + * which is where the generic reader early_init_dt_check_kho() would call > + * kho_populate(). > + */ > +static void __init kho_populate_from_efi(void) > +{ > + efi_system_table_t *st; > + efi_config_table_t *ct; > + struct linux_efi_kho_data *kho; > + unsigned long i; > + > + if (!fw_arg2) > + return; > + > + st = (efi_system_table_t *)TO_CACHE(fw_arg2); > + ct = (efi_config_table_t *)TO_CACHE((unsigned long)st->tables); > + > + for (i = 0; i < st->nr_tables; i++) { > + if (efi_guidcmp(ct[i].guid, LINUX_EFI_KHO_TABLE_GUID)) > + continue; > + > + kho = (struct linux_efi_kho_data *)TO_CACHE((unsigned long)ct[i].table); > + kho_populate(kho->fdt_addr, kho->fdt_size, > + kho->scratch_addr, kho->scratch_size); > + break; > + } > +} If you do this in efi.c, you can significantly simplify the code. By defining the KHO table in arch_tables[], you almost only need to call kho_populate() at the end of efi_init(). Huacai > +#else > +static void __init kho_populate_from_efi(void) { } > +#endif > + > static void __init fdt_setup(void) > { > #ifdef CONFIG_OF_EARLY_FLATTREE > @@ -599,6 +639,7 @@ void __init setup_arch(char **cmdline_p) > > init_environ(); > efi_init(); > + kho_populate_from_efi(); > fdt_setup(); > memblock_init(); > pagetable_init(); > -- > 2.53.0 >