* [PATCH v3 2/3] LoongArch: kexec: add KHO support for ACPI-only
2026-06-01 9:39 ` [PATCH v3 1/3] LoongArch: kexec: add KHO support for FDT-based George Guo
@ 2026-06-01 9:39 ` George Guo
2026-06-01 9:39 ` [PATCH v3 3/3] selftests/kho: add LoongArch vmtest support George Guo
1 sibling, 0 replies; 6+ messages in thread
From: George Guo @ 2026-06-01 9:39 UTC (permalink / raw)
To: Huacai Chen, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
Shuah Khan
Cc: George Guo, WANG Xuerui, Alexander Graf, loongarch, linux-kernel,
kexec, linux-mm, linux-kselftest, systems
From: George Guo <guodongtai@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@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 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
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v3 3/3] selftests/kho: add LoongArch vmtest support
2026-06-01 9:39 ` [PATCH v3 1/3] LoongArch: kexec: add KHO support for FDT-based George Guo
2026-06-01 9:39 ` [PATCH v3 2/3] LoongArch: kexec: add KHO support for ACPI-only George Guo
@ 2026-06-01 9:39 ` George Guo
1 sibling, 0 replies; 6+ messages in thread
From: George Guo @ 2026-06-01 9:39 UTC (permalink / raw)
To: Huacai Chen, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
Shuah Khan
Cc: George Guo, WANG Xuerui, Alexander Graf, loongarch, linux-kernel,
kexec, linux-mm, linux-kselftest, Kexin Liu
From: George Guo <guodongtai@kylinos.cn>
Add loongarch.conf to configure QEMU's LoongArch virt machine with a
la464 CPU, enable the 8250 serial console, and set the kernel image
to vmlinux.efi. Extend vmtest.sh to recognise loongarch64 as a
supported target and map it to the 'loongarch' kernel arch name.
QEMU's LoongArch virt machine provides no ACPI tables and relies on
FDT to describe hardware. Without 'earlycon' on the kernel command
line, the FDT is not scanned for a console UART, no output reaches
the console, and vmtest.sh's console log stays empty causing the test
to always fail. Add 'earlycon' to KERNEL_CMDLINE in loongarch.conf.
QEMU's LoongArch virt machine has no i8042 PS/2 controller. When PNP
detection finds nothing, i8042_init() falls back to probing the ports
directly. On LoongArch the I/O ports are memory-mapped, and the i8042
port addresses are not backed by any device on the virt machine, so
i8042_flush() takes a page fault and the kernel panics:
i8042: PNP: No PS/2 controller found.
i8042: Probing ports directly.
CPU 0 Unable to handle kernel paging request at virtual address ffff800000008064
ERA: i8042_flush+0x50/0x198
RA: i8042_init+0x2a8/0x35c
Kernel panic - not syncing: Attempted to kill init!
Disable SERIO_I8042 and its dependents (KEYBOARD_ATKBD, MOUSE_PS2) in
the QEMU_KCONFIG fragment to prevent the driver from being built.
All three options are scoped to loongarch.conf; no other architecture
is affected.
QEMU provides no EFI runtime services on LoongArch, so machine_restart()
falls through to an infinite idle loop after kexec. Set QEMU_TIMEOUT=120
in loongarch.conf so vmtest.sh wraps the QEMU invocation with timeout(1),
which terminates QEMU after 120 seconds if it does not exit on its own.
Architectures that do not set QEMU_TIMEOUT are unaffected.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
tools/testing/selftests/kho/loongarch.conf | 13 ++++++++++++
tools/testing/selftests/kho/vmtest.sh | 23 +++++++++++++++-------
2 files changed, 29 insertions(+), 7 deletions(-)
create mode 100644 tools/testing/selftests/kho/loongarch.conf
diff --git a/tools/testing/selftests/kho/loongarch.conf b/tools/testing/selftests/kho/loongarch.conf
new file mode 100644
index 000000000000..68727654578d
--- /dev/null
+++ b/tools/testing/selftests/kho/loongarch.conf
@@ -0,0 +1,13 @@
+QEMU_CMD="qemu-system-loongarch64 -M virt -cpu la464"
+QEMU_KCONFIG="
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_KEYBOARD_ATKBD is not set
+# CONFIG_MOUSE_PS2 is not set
+# CONFIG_SERIO_I8042 is not set
+"
+KERNEL_IMAGE="vmlinux.efi"
+KERNEL_CMDLINE="console=ttyS0 earlycon"
+# QEMU never exits after kexec on LoongArch (no EFI runtime services);
+# give the test a fixed time limit and let timeout(1) terminate QEMU.
+QEMU_TIMEOUT=120
diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh
index 49fdac8e8b15..918698b6dd2a 100755
--- a/tools/testing/selftests/kho/vmtest.sh
+++ b/tools/testing/selftests/kho/vmtest.sh
@@ -21,7 +21,7 @@ Options:
-d) path to the kernel build directory
-j) number of jobs for compilation, similar to -j in make
-t) run test for target_arch, requires CROSS_COMPILE set
- supported targets: aarch64, x86_64
+ supported targets: aarch64, x86_64, loongarch64
-h) display this help
EOF
}
@@ -107,12 +107,20 @@ function run_qemu() {
cmdline="$cmdline kho=on panic=-1"
- $qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \
- -accel kvm -accel hvf -accel tcg \
- -serial file:"$serial" \
- -append "$cmdline" \
- -kernel "$kernel" \
- -initrd "$initrd"
+ local qemu_args=(
+ -m 1G -smp 2 -no-reboot -nographic -nodefaults
+ -accel kvm -accel hvf -accel tcg
+ -serial file:"$serial"
+ -append "$cmdline"
+ -kernel "$kernel"
+ -initrd "$initrd"
+ )
+
+ if [[ -n "${QEMU_TIMEOUT:-}" ]]; then
+ timeout "$QEMU_TIMEOUT" $qemu_cmd "${qemu_args[@]}" || true
+ else
+ $qemu_cmd "${qemu_args[@]}"
+ fi
grep "KHO restore succeeded" "$serial" &> /dev/null || fail "KHO failed"
}
@@ -123,6 +131,7 @@ function target_to_arch() {
case $target in
aarch64) echo "arm64" ;;
x86_64) echo "x86" ;;
+ loongarch64) echo "loongarch" ;;
*) skip "architecture $target is not supported"
esac
}
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread