From: George Guo <dongtai.guo@linux.dev>
To: chenhuacai@kernel.org, rppt@kernel.org,
pasha.tatashin@soleen.com, pratyush@kernel.org, shuah@kernel.org,
ardb@kernel.org
Cc: guodongtai@kylinos.cn, kernel@xen0n.name, graf@amazon.com,
loongarch@lists.linux.dev, linux-kernel@vger.kernel.org,
kexec@lists.infradead.org, linux-mm@kvack.org,
linux-kselftest@vger.kernel.org, linux-efi@vger.kernel.org,
Kexin Liu <liukexin@kylinos.cn>
Subject: [PATCH v4 2/4] LoongArch: kexec: add KHO support
Date: Fri, 7 Aug 2026 18:37:12 +0800 [thread overview]
Message-ID: <20260807103714.33074-3-dongtai.guo@linux.dev> (raw)
In-Reply-To: <20260807103714.33074-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
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 <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
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;
+ }
+
+ /* 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;
+ }
+}
+#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
next prev parent reply other threads:[~2026-08-07 10:38 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-07 10:37 [PATCH v4 0/4] LoongArch: add KHO support and selftests George Guo
2026-08-07 10:37 ` [PATCH v4 1/4] efi: add a KHO configuration table GUID George Guo
2026-08-09 4:18 ` Huacai Chen
2026-08-10 13:13 ` Ard Biesheuvel
2026-08-10 14:35 ` Huacai Chen
2026-08-10 16:19 ` Pratyush Yadav
2026-08-07 10:37 ` George Guo [this message]
2026-08-10 14:42 ` [PATCH v4 2/4] LoongArch: kexec: add KHO support Huacai Chen
2026-08-10 17:37 ` Pratyush Yadav
2026-08-07 10:37 ` [PATCH v4 3/4] liveupdate: luo_session: include linux/mm.h for virt/phys translation George Guo
2026-08-10 14:37 ` Huacai Chen
2026-08-10 17:39 ` Pratyush Yadav
2026-08-10 17:44 ` Pratyush Yadav
2026-08-07 10:37 ` [PATCH v4 4/4] selftests/kho: add LoongArch vmtest support George Guo
2026-08-09 4:16 ` Huacai Chen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260807103714.33074-3-dongtai.guo@linux.dev \
--to=dongtai.guo@linux.dev \
--cc=ardb@kernel.org \
--cc=chenhuacai@kernel.org \
--cc=graf@amazon.com \
--cc=guodongtai@kylinos.cn \
--cc=kernel@xen0n.name \
--cc=kexec@lists.infradead.org \
--cc=linux-efi@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=liukexin@kylinos.cn \
--cc=loongarch@lists.linux.dev \
--cc=pasha.tatashin@soleen.com \
--cc=pratyush@kernel.org \
--cc=rppt@kernel.org \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.