Skip to contentSkip to conversation

LoongArch: Add KHO support and selftests

PATCH In review George Guo 7 participants 29 messages Oct 29 – Nov 8

Subsystems (8)
lore
Share & tools
lore

George Guo

author
#0 lore raw

v6 cover LoongArch: Add KHO support and selftests

From: George Guo <[email protected]>

This series enables Kexec HandOver (KHO) on 64-bit LoongArch and adds LoongArch to the KHO and liveupdate QEMU selftests.

KHO has to give the next kernel two addresses: the KHO FDT and the bootmem area list. arm64 and riscv put them under /chosen in the boot FDT, and x86 puts them in setup_data. LoongArch systems that boot with EFI and ACPI have no boot FDT, so this series passes the addresses in a Linux EFI configuration table:

- On a cold boot, the LoongArch EFI stub installs the table, zeroed. Kernels started by kexec are given the same EFI system table, so they find the same table.
- In early boot, the kernel reserves the table and calls kho_populate() if the table holds KHO state.
- Just before kexec, the kernel writes the KHO state of the image it is about to run, or zeroes the table if that image has none, for example a crash kernel.

A kernel started by kexec from a kernel that did not install the table has no table and cannot pass KHO state on, so LoongArch turns KHO off at boot in that case.

Patch 1 adds the table, and patch 2 writes it at kexec time. Patch 3 fixes a kho_block.c build failure with CONFIG_KFENCE=y on LoongArch. Patch 4 adds kho_disable(). Patch 5 lets the LoongArch memblock arrays grow, which restoring many preserved regions needs, and patch 6 enables KHO on LoongArch. Patches 7 and 8 add LoongArch to the KHO and liveupdate vmtests.

The series is based on liveupdate-7.4 and depends on "liveupdate: kho:
calculate per-node bootmem sizes before allocating any" [1]. Without it, the KHO vmtest fails on a 1 GiB LoongArch guest because KHO cannot reserve its bootmem areas.

Tested in QEMU: the KHO vmtest passes on LoongArch with both CONFIG_NUMA=n and CONFIG_NUMA=y, and run-vmtests.sh passes all four liveupdate tests on x86_64, aarch64 and loongarch64.

Changes since v5:
- Drop CONFIG_EFI_KHO. Only the LoongArch stub installs the table, from efi_boot_kernel(). The other code is built with CONFIG_EFI and does nothing when there is no table (Pratyush).
- efi_kho_update() returns 0 when there is no table, so kexec on other architectures is not affected.
- Warn if the table cannot be mapped in early boot (Pratyush).
- Document each member of struct linux_efi_kho_data and drop __packed

    (Pratyush).
  - Rename efi_kho_table_phys to efi_kho, mark it __initdata, and join
    wrapped lines (Huacai).
  - Rename kho_sync_channel() to kho_update_efi_table(), and stop
    calling the table a "channel" or "transport" (Mike, Pratyush).
  - New patch 4: turn KHO off at boot on LoongArch when the table is
    missing.
  - New patch 5: allow the memblock arrays to resize on LoongArch. The
    liveupdate luo_stress_files test overflowed the reserved array in
    the next kernel when CONFIG_SMP was not set.
  - New patch 8: add LoongArch to the liveupdate vmtest.
  - Rename scratch to bootmem, following Pratyush's rename series.
  - Drop the luo_session history from patch 3's commit message
    (Pratyush).

- Capitalize the subjects and use the kho: prefix (Huacai).
- Rewrite the cover letter and commit messages (Mike, Pratyush).
- Rebase onto liveupdate-7.4.
- No Assisted-by tags, although my v5 reply said patches written with LLM help would carry one: the Assisted-by references have since been removed from the documentation in mainline.

[1] https://lore.kernel.org/all/[email protected]/

v5: https://lore.kernel.org/loongarch/[email protected]/
v4: https://lore.kernel.org/loongarch/[email protected]/

George Guo (8):
efi: Pass KHO state to the next kernel in a configuration table
kho: Write KHO state to the EFI table at kexec time
kho: Include linux/mm.h in kho_block.c
kho: Add kho_disable()
LoongArch: Allow memblock arrays to resize
LoongArch: Enable kexec handover (KHO)
selftests/kho: Add LoongArch vmtest support selftests/liveupdate: Add LoongArch vmtest support

 arch/loongarch/Kconfig                        |  3 +
 arch/loongarch/kernel/setup.c                 | 19 ++++++
 drivers/firmware/efi/efi.c                    | 68 +++++++++++++++++++
 drivers/firmware/efi/libstub/efi-stub.c       | 22 ++++++
 drivers/firmware/efi/libstub/efistub.h        |  2 +
 drivers/firmware/efi/libstub/loongarch.c      |  2 +
 include/linux/efi.h                           | 40 +++++++++++
 include/linux/kexec_handover.h                |  3 +
 kernel/crash_core.c                           |  6 ++
 kernel/kexec_core.c                           |  5 ++
 kernel/kexec_internal.h                       |  8 +++
 kernel/liveupdate/kexec_handover.c            | 35 ++++++++++
 kernel/liveupdate/kho_block.c                 |  1 +
 tools/testing/selftests/kho/loongarch.conf    | 11 +++
 tools/testing/selftests/kho/vmtest.sh         |  3 +-
 .../selftests/liveupdate/config.loongarch64   |  5 ++
 .../selftests/liveupdate/run-vmtests.sh       |  4 +-
 tools/testing/selftests/liveupdate/vmtest.sh  | 14 +++-
 18 files changed, 247 insertions(+), 4 deletions(-)
 create mode 100644 tools/testing/selftests/kho/loongarch.conf
 create mode 100644 tools/testing/selftests/liveupdate/config.loongarch64

base-commit: 9a6a70aefe77f6fb9ff2b3037dcf87f6dff65c8c
prerequisite-patch-id: 433e6f8fdec5151f12221a0a34f8fb111b95bb46

Signature · 1 line
2.53.0
author
#1 lore raw

PATCH v6 1/8 efi: Pass KHO state to the next kernel in a configuration table

From: George Guo <[email protected]>

KHO has to give the next kernel two addresses: the KHO FDT and the bootmem area list. arm64 and riscv put them under /chosen in the boot FDT. LoongArch systems booting with EFI and ACPI do not require a boot FDT, so KHO needs another way to pass this information.

Add a Linux EFI configuration table for them, under LINUX_EFI_KEXEC_HANDOVER_GUID. It points to a struct linux_efi_kho_data.

- On a cold boot, the LoongArch EFI stub installs the table, zeroed. Each kexec'd kernel is given the same EFI system table, so it finds the same table.

- In early boot, efi_config_parse_tables() reserves the table so neither the page allocator nor kexec segments use it, and calls kho_populate() if the table holds KHO state.

- Provide efi_kho_update() to update the table before kexec. Map the table during boot so updates do not require allocation or mapping at panic time.

- efi_kho_table_found() tells architecture code whether the table was found at boot, so it can turn KHO off when the table is missing.

Only the LoongArch stub installs the table, and only with CONFIG_KEXEC_HANDOVER. Elsewhere there is no table and this code does nothing.

---
 drivers/firmware/efi/efi.c               | 68 ++++++++++++++++++++++++
 drivers/firmware/efi/libstub/efi-stub.c  | 22 ++++++++
 drivers/firmware/efi/libstub/efistub.h   |  2 +
 drivers/firmware/efi/libstub/loongarch.c |  2 +
 include/linux/efi.h                      | 40 ++++++++++++++
 5 files changed, 134 insertions(+)
drivers/firmware/efi/efi.c +68 -0
index 0327a39d31fa..e409e4a57378 100644
@@ -24,6 +24,7 @@
 #include <linux/initrd.h>
 #include <linux/io.h>
 #include <linux/kexec.h>
+#include <linux/kexec_handover.h>
 #include <linux/platform_device.h>
 #include <linux/random.h>
 #include <linux/reboot.h>
@@ -62,6 +63,7 @@ unsigned long __ro_after_init efi_rng_seed = EFI_INVALID_TABLE_ADDR;
 static unsigned long __initdata mem_reserve = EFI_INVALID_TABLE_ADDR;
 static unsigned long __initdata rt_prop = EFI_INVALID_TABLE_ADDR;
 static unsigned long __initdata initrd = EFI_INVALID_TABLE_ADDR;
+static unsigned long efi_kho __initdata = EFI_INVALID_TABLE_ADDR;
 
 extern unsigned long primary_display_table;
 
@@ -629,6 +631,7 @@ static const efi_config_table_type_t common_tables[] __initconst = {
 	{EFI_TCG2_FINAL_EVENTS_TABLE_GUID,	&efi.tpm_final_log,	"TPMFinalLog"	},
 	{EFI_CC_FINAL_EVENTS_TABLE_GUID,	&efi.tpm_final_log,	"CCFinalLog"	},
 	{LINUX_EFI_MEMRESERVE_TABLE_GUID,	&mem_reserve,		"MEMRESERVE"	},
+	{LINUX_EFI_KEXEC_HANDOVER_GUID,		&efi_kho,		"KHO"		},
 	{LINUX_EFI_INITRD_MEDIA_GUID,		&initrd,		"INITRD"	},
 	{EFI_RT_PROPERTIES_TABLE_GUID,		&rt_prop,		"RTPROP"	},
 #ifdef CONFIG_OVMF_DEBUG_LOG
@@ -806,6 +809,23 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
 		}
 	}
 
+	if (efi_kho != EFI_INVALID_TABLE_ADDR) {
+		struct linux_efi_kho_data *kho;
+
+		/* Keep the buddy allocator and kexec segments off the table. */
+		memblock_reserve(efi_kho, sizeof(*kho));
+
+		kho = early_memremap(efi_kho, sizeof(*kho));
+		if (kho) {
+			if (kho->fdt_addr)
+				kho_populate((phys_addr_t)kho->fdt_addr, kho->fdt_size,
+					     (phys_addr_t)kho->bootmem_addr, kho->bootmem_size);
+			early_memunmap(kho, sizeof(*kho));
+		} else {
+			pr_warn("Could not map UEFI KHO table!\n");
+		}
+	}
+
 	if (rt_prop != EFI_INVALID_TABLE_ADDR) {
 		efi_rt_properties_table_t *tbl;
 
@@ -1171,6 +1191,54 @@ static int __init efi_memreserve_root_init(void)
 }
 early_initcall(efi_memreserve_root_init);
 
+static struct linux_efi_kho_data *efi_kho_table __ro_after_init;
+
+/* Did the previous kernel or the EFI stub pass a KHO table? */
+bool __init efi_kho_table_found(void)
+{
+	return efi_kho != EFI_INVALID_TABLE_ADDR;
+}
+
+static int __init efi_kho_table_init(void)
+{
+	if (efi_kho == EFI_INVALID_TABLE_ADDR)
+		return 0;
+
+	/* Map it now: the table is also written at panic time. */
+	efi_kho_table = memremap(efi_kho, sizeof(*efi_kho_table), MEMREMAP_WB);
+	if (WARN_ON_ONCE(!efi_kho_table))
+		efi_kho_table = ERR_PTR(-ENOMEM);
+
+	return 0;
+}
+early_initcall(efi_kho_table_init);
+
+/*
+ * Write the KHO addresses the next kernel should use, or zeroes
+ * if it should boot without KHO.
+ */
+int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
+		   phys_addr_t bootmem_addr, u64 bootmem_size)
+{
+	struct linux_efi_kho_data *kho = efi_kho_table;
+
+	/*
+	 * No table: either the architecture passes the KHO state some other
+	 * way, or it turned KHO off at boot because the table was missing.
+	 */
+	if (!kho)
+		return 0;
+	if (IS_ERR(kho))
+		return PTR_ERR(kho);
+
+	kho->fdt_addr		= fdt_addr;
+	kho->fdt_size		= fdt_size;
+	kho->bootmem_addr	= bootmem_addr;
+	kho->bootmem_size	= bootmem_size;
+
+	return 0;
+}
+
 #ifdef CONFIG_KEXEC
 static int update_efi_random_seed(struct notifier_block *nb,
 				  unsigned long code, void *unused)
drivers/firmware/efi/libstub/efi-stub.c +22 -0
index 42d6073bcd06..0fb0d4041d1c 100644
@@ -100,6 +100,28 @@ static void install_memreserve_table(void)
 		efi_err("Failed to install memreserve config table!\n");
 }
 
+void install_kho_table(void)
+{
+	struct linux_efi_kho_data *kho;
+	efi_guid_t kho_table_guid = LINUX_EFI_KEXEC_HANDOVER_GUID;
+	efi_status_t status;
+
+	if (!IS_ENABLED(CONFIG_KEXEC_HANDOVER))
+		return;
+
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*kho), (void **)&kho);
+	if (status != EFI_SUCCESS) {
+		efi_err("Failed to allocate KHO config table!\n");
+		return;
+	}
+
+	*kho = (struct linux_efi_kho_data){};
+
+	status = efi_bs_call(install_configuration_table, &kho_table_guid, kho);
+	if (status != EFI_SUCCESS)
+		efi_err("Failed to install KHO config table!\n");
+}
+
 static u32 get_supported_rt_services(void)
 {
 	const efi_rt_properties_table_t *rt_prop_table;
drivers/firmware/efi/libstub/efistub.h +2 -0
index fd91fc15ec81..9e0034c1260a 100644
@@ -1057,6 +1057,8 @@ efi_status_t efi_exit_boot_services(void *handle, void *priv,
 efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
 			     unsigned long kernel_addr, char *cmdline_ptr);
 
+void install_kho_table(void);
+
 void *get_fdt(unsigned long *fdt_size);
 
 efi_status_t efi_alloc_virtmap(efi_memory_desc_t **virtmap,
drivers/firmware/efi/libstub/loongarch.c +2 -0
index 2b0c87dc9908..c2df097771e4 100644
@@ -74,6 +74,8 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
 	efi_status_t status;
 	u32 desc_ver;
 
+	install_kho_table();
+
 	status = efi_alloc_virtmap(&priv.runtime_map, &desc_size, &desc_ver);
 	if (status != EFI_SUCCESS) {
 		efi_err("Unable to retrieve UEFI memory map.\n");
include/linux/efi.h +40 -0
index aa15ff88539b..253e98bf9922 100644
@@ -422,6 +422,7 @@ void efi_native_runtime_setup(void);
 #define LINUX_EFI_COCO_SECRET_AREA_GUID		EFI_GUID(0xadf956ad, 0xe98c, 0x484c,  0xae, 0x11, 0xb5, 0x1c, 0x7d, 0x33, 0x64, 0x47)
 #define LINUX_EFI_BOOT_MEMMAP_GUID		EFI_GUID(0x800f683f, 0xd08b, 0x423a,  0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4)
 #define LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID	EFI_GUID(0xd5d1de3c, 0x105c, 0x44f9,  0x9e, 0xa9, 0xbc, 0xef, 0x98, 0x12, 0x00, 0x31)
+#define LINUX_EFI_KEXEC_HANDOVER_GUID		EFI_GUID(0xc941b6c7, 0x7b3f, 0x4af6,  0x9e, 0x50, 0xfc, 0xb3, 0xa8, 0x86, 0x8a, 0x17)
 
 #define RISCV_EFI_BOOT_PROTOCOL_GUID		EFI_GUID(0xccd15fec, 0x6f73, 0x4eec,  0x83, 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf)
 
@@ -1273,6 +1274,45 @@ struct linux_efi_memreserve {
 
 void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
 
+/**
+ * struct linux_efi_kho_data - KHO state passed to the next kernel
+ * @fdt_addr: physical address of the KHO FDT, 0 if there is none
+ * @fdt_size: size of the KHO FDT
+ * @bootmem_addr: physical address of the bootmem area list
+ * @bootmem_size: size of the bootmem area list
+ *
+ * Pointed to by the LINUX_EFI_KEXEC_HANDOVER_GUID configuration table.
+ *
+ * The EFI stub installs this table zeroed on a cold boot. It stays
+ * in place across kexec; the running kernel rewrites it just before
+ * each kexec, and the next kernel reads it during early boot.
+ *
+ * The layout is fixed. Changing it requires a new GUID.
+ */
+struct linux_efi_kho_data {
+	u64 fdt_addr;
+	u64 fdt_size;
+	u64 bootmem_addr;
+	u64 bootmem_size;
+};
+
+#ifdef CONFIG_EFI
+bool efi_kho_table_found(void);
+int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
+		   phys_addr_t bootmem_addr, u64 bootmem_size);
+#else
+static inline bool efi_kho_table_found(void)
+{
+	return false;
+}
+
+static inline int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
+				 phys_addr_t bootmem_addr, u64 bootmem_size)
+{
+	return 0;
+}
+#endif
+
 /*
  * The LINUX_EFI_MOK_VARIABLE_TABLE_GUID config table can be provided
  * to the kernel by an EFI boot loader. The table contains a packed
Signature · 1 line
2.53.0
#2 lore raw

Re: [PATCH v6 1/8] efi: Pass KHO state to the next kernel in a configuration table

Sashiko has reviewed this patch and found no issues.
Quoted 22 lines

From: George Guo <[email protected]>

KHO has to give the next kernel two addresses: the KHO FDT and the bootmem area list. arm64 and riscv put them under /chosen in the boot FDT. LoongArch systems booting with EFI and ACPI do not require a boot FDT, so KHO needs another way to pass this information.

Add a Linux EFI configuration table for them, under LINUX_EFI_KEXEC_HANDOVER_GUID. It points to a struct linux_efi_kho_data.

- On a cold boot, the LoongArch EFI stub installs the table, zeroed. Each kexec'd kernel is given the same EFI system table, so it finds the same table.

- In early boot, efi_config_parse_tables() reserves the table so neither the page allocator nor kexec segments use it, and calls kho_populate() if the table holds KHO state.

- Provide efi_kho_update() to update the table before kexec. Map the table during boot so updates do not require allocation or mapping at panic time.

Quoted 8 lines

- efi_kho_table_found() tells architecture code whether the table was found at boot, so it can turn KHO off when the table is missing.

Only the LoongArch stub installs the table, and only with CONFIG_KEXEC_HANDOVER. Elsewhere there is no table and this code does nothing.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#3 lore raw

Re: [PATCH v6 1/8] efi: Pass KHO state to the next kernel in a configuration table

In reply to George Guo
Quoted 22 lines · George Guo

From: George Guo <[email protected]>

KHO has to give the next kernel two addresses: the KHO FDT and the bootmem area list. arm64 and riscv put them under /chosen in the boot FDT. LoongArch systems booting with EFI and ACPI do not require a boot FDT, so KHO needs another way to pass this information.

Add a Linux EFI configuration table for them, under LINUX_EFI_KEXEC_HANDOVER_GUID. It points to a struct linux_efi_kho_data.

- On a cold boot, the LoongArch EFI stub installs the table, zeroed. Each kexec'd kernel is given the same EFI system table, so it finds the same table.

- In early boot, efi_config_parse_tables() reserves the table so neither the page allocator nor kexec segments use it, and calls kho_populate() if the table holds KHO state.

- Provide efi_kho_update() to update the table before kexec. Map the table during boot so updates do not require allocation or mapping at panic time.

Quoted 7 lines

- efi_kho_table_found() tells architecture code whether the table was found at boot, so it can turn KHO off when the table is missing.

Only the LoongArch stub installs the table, and only with CONFIG_KEXEC_HANDOVER. Elsewhere there is no table and this code does nothing.

Can we handle this as an arch table then, and handle everything in the LoongArch arch code?

maintainer
#4 lore raw

Re: [PATCH v6 1/8] efi: Pass KHO state to the next kernel in a configuration table

Hi Ard,

Quoted 26 lines · Ard Biesheuvel
Quoted 25 lines · George Guo

From: George Guo <[email protected]>

KHO has to give the next kernel two addresses: the KHO FDT and the bootmem area list. arm64 and riscv put them under /chosen in the boot FDT. LoongArch systems booting with EFI and ACPI do not require a boot FDT, so KHO needs another way to pass this information.

Add a Linux EFI configuration table for them, under LINUX_EFI_KEXEC_HANDOVER_GUID. It points to a struct linux_efi_kho_data.

- On a cold boot, the LoongArch EFI stub installs the table, zeroed. Each kexec'd kernel is given the same EFI system table, so it finds the same table.

- In early boot, efi_config_parse_tables() reserves the table so neither the page allocator nor kexec segments use it, and calls kho_populate() if the table holds KHO state.

- Provide efi_kho_update() to update the table before kexec. Map the table during boot so updates do not require allocation or mapping at panic time.

- efi_kho_table_found() tells architecture code whether the table was found at boot, so it can turn KHO off when the table is missing.

Quoted 7 lines
Quoted 4 lines

Only the LoongArch stub installs the table, and only with CONFIG_KEXEC_HANDOVER. Elsewhere there is no table and this code does nothing.

Can we handle this as an arch table then, and handle everything in the LoongArch arch code?

I think this is useful for all architectures that use EFI, not only Loongarch. Today Loongarch is the only user, but this can very easily be used by other architectures.

For example, I have been considering moving x86 over to this too. Today we use setup_data. Once we get EFI support, I would like to get rid of that and unify everything under EFI. It is much simpler to have a universal way to pass KHO payload over.

Signature · 2 lines
Regards,
Pratyush Yadav
author
#5 lore raw

Re: [PATCH v6 1/8] efi: Pass KHO state to the next kernel in a configuration table

In reply to Ard Biesheuvel

Hi Ard, Pratyush,

Quoted 2 lines · Ard Biesheuvel

Can we handle this as an arch table then, and handle everything in the LoongArch arch code?

Ard, LoongArch is currently the only user, so I understand the suggestion to keep the handling in the architecture code.

My preference is to keep the table definition and handling common, given the potential for x86 to use it as Pratyush described in his reply, to replace setup_data once x86 gains EFI support. The table contains no LoongArch-specific fields, and only the LoongArch stub installs it. On systems without the table, the common code does nothing.

Would that be acceptable, or would you prefer to keep the handling in LoongArch until a second architecture adopts it?

Thanks,
George

maintainer
#6 lore raw

Re: [PATCH v6 1/8] efi: Pass KHO state to the next kernel in a configuration table

Quoted 10 lines · George Guo

Hi Ard, Pratyush,

Quoted 2 lines · Ard Biesheuvel

Can we handle this as an arch table then, and handle everything in the LoongArch arch code?

Ard, LoongArch is currently the only user, so I understand the suggestion to keep the handling in the architecture code.

Quoted 8 lines

My preference is to keep the table definition and handling common, given the potential for x86 to use it as Pratyush described in his reply, to replace setup_data once x86 gains EFI support. The table contains no LoongArch-specific fields, and only the LoongArch stub installs it. On systems without the table, the common code does nothing.

Would that be acceptable, or would you prefer to keep the handling in LoongArch until a second architecture adopts it?

I prefer to keep it in LoongArch until a second architecture, and I think Ard is the same. But this is not a strong objection to Pratyush.

Huacai

Quoted 3 lines

Thanks,
George

#7 lore raw

Re: [PATCH v6 1/8] efi: Pass KHO state to the next kernel in a configuration table

In reply to George Guo

Hi George,

Quoted 74 lines · George Guo

From: George Guo <[email protected]>

KHO has to give the next kernel two addresses: the KHO FDT and the bootmem area list. arm64 and riscv put them under /chosen in the boot FDT. LoongArch systems booting with EFI and ACPI do not require a boot FDT, so KHO needs another way to pass this information.

Add a Linux EFI configuration table for them, under LINUX_EFI_KEXEC_HANDOVER_GUID. It points to a struct linux_efi_kho_data.

- On a cold boot, the LoongArch EFI stub installs the table, zeroed. Each kexec'd kernel is given the same EFI system table, so it finds the same table.

- In early boot, efi_config_parse_tables() reserves the table so neither the page allocator nor kexec segments use it, and calls kho_populate() if the table holds KHO state.

- Provide efi_kho_update() to update the table before kexec. Map the table during boot so updates do not require allocation or mapping at panic time.

- efi_kho_table_found() tells architecture code whether the table was found at boot, so it can turn KHO off when the table is missing.

Only the LoongArch stub installs the table, and only with CONFIG_KEXEC_HANDOVER. Elsewhere there is no table and this code does nothing.

---
 drivers/firmware/efi/efi.c               | 68 ++++++++++++++++++++++++
 drivers/firmware/efi/libstub/efi-stub.c  | 22 ++++++++
 drivers/firmware/efi/libstub/efistub.h   |  2 +
 drivers/firmware/efi/libstub/loongarch.c |  2 +
 include/linux/efi.h                      | 40 ++++++++++++++
 5 files changed, 134 insertions(+)
drivers/firmware/efi/efi.c +7 -0
index 0327a39d31fa..e409e4a57378 100644
@@ -24,6 +24,7 @@
 #include <linux/initrd.h>
 #include <linux/io.h>
 #include <linux/kexec.h>
+#include <linux/kexec_handover.h>
 #include <linux/platform_device.h>
 #include <linux/random.h>
 #include <linux/reboot.h>
@@ -62,6 +63,7 @@ unsigned long __ro_after_init efi_rng_seed = EFI_INVALID_TABLE_ADDR;
 static unsigned long __initdata mem_reserve = EFI_INVALID_TABLE_ADDR;
 static unsigned long __initdata rt_prop = EFI_INVALID_TABLE_ADDR;
 static unsigned long __initdata initrd = EFI_INVALID_TABLE_ADDR;
+static unsigned long efi_kho __initdata = EFI_INVALID_TABLE_ADDR;
 
 extern unsigned long primary_display_table;
 
@@ -629,6 +631,7 @@ static const efi_config_table_type_t common_tables[] __initconst = {
 	{EFI_TCG2_FINAL_EVENTS_TABLE_GUID,	&efi.tpm_final_log,	"TPMFinalLog"	},
 	{EFI_CC_FINAL_EVENTS_TABLE_GUID,	&efi.tpm_final_log,	"CCFinalLog"	},
 	{LINUX_EFI_MEMRESERVE_TABLE_GUID,	&mem_reserve,		"MEMRESERVE"	},
+	{LINUX_EFI_KEXEC_HANDOVER_GUID,		&efi_kho,		"KHO"		},
 	{LINUX_EFI_INITRD_MEDIA_GUID,		&initrd,		"INITRD"	},
 	{EFI_RT_PROPERTIES_TABLE_GUID,		&rt_prop,		"RTPROP"	},
 #ifdef CONFIG_OVMF_DEBUG_LOG
@@ -806,6 +809,23 @@ int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
 		}
 	}
 
+	if (efi_kho != EFI_INVALID_TABLE_ADDR) {
+		struct linux_efi_kho_data *kho;
+
+		/* Keep the buddy allocator and kexec segments off the table. */

drivers/firmware/efi/efi.c int __init efi_config_parse_tables(const efi_config_table_t *config_tables, · line 825

Quoted 10 lines
drivers/firmware/efi/efi.c +10 -0
+		memblock_reserve(efi_kho, sizeof(*kho));
+
+		kho = early_memremap(efi_kho, sizeof(*kho));
+		if (kho) {
+			if (kho->fdt_addr)
+				kho_populate((phys_addr_t)kho->fdt_addr, kho->fdt_size,
+					     (phys_addr_t)kho->bootmem_addr, kho->bootmem_size);
+			early_memunmap(kho, sizeof(*kho));
+		} else {
+			pr_warn("Could not map UEFI KHO table!\n");

This is a Linux EFI configuration table, not a __UEFI KHO table__, so IMHO; UEFI wording is wrong here.

Quoted 80 lines
drivers/firmware/efi/efi.c +51 -0
+		}
+	}
+
 	if (rt_prop != EFI_INVALID_TABLE_ADDR) {
 		efi_rt_properties_table_t *tbl;
 
@@ -1171,6 +1191,54 @@ static int __init efi_memreserve_root_init(void)
 }
 early_initcall(efi_memreserve_root_init);
 
+static struct linux_efi_kho_data *efi_kho_table __ro_after_init;
+
+/* Did the previous kernel or the EFI stub pass a KHO table? */
+bool __init efi_kho_table_found(void)
+{
+	return efi_kho != EFI_INVALID_TABLE_ADDR;
+}
+
+static int __init efi_kho_table_init(void)
+{
+	if (efi_kho == EFI_INVALID_TABLE_ADDR)
+		return 0;
+
+	/* Map it now: the table is also written at panic time. */
+	efi_kho_table = memremap(efi_kho, sizeof(*efi_kho_table), MEMREMAP_WB);
+	if (WARN_ON_ONCE(!efi_kho_table))
+		efi_kho_table = ERR_PTR(-ENOMEM);
+
+	return 0;
+}
+early_initcall(efi_kho_table_init);
+
+/*
+ * Write the KHO addresses the next kernel should use, or zeroes
+ * if it should boot without KHO.
+ */
+int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
+		   phys_addr_t bootmem_addr, u64 bootmem_size)
+{
+	struct linux_efi_kho_data *kho = efi_kho_table;
+
+	/*
+	 * No table: either the architecture passes the KHO state some other
+	 * way, or it turned KHO off at boot because the table was missing.
+	 */
+	if (!kho)
+		return 0;
+	if (IS_ERR(kho))
+		return PTR_ERR(kho);
+
+	kho->fdt_addr		= fdt_addr;
+	kho->fdt_size		= fdt_size;
+	kho->bootmem_addr	= bootmem_addr;
+	kho->bootmem_size	= bootmem_size;
+
+	return 0;
+}
+
 #ifdef CONFIG_KEXEC
 static int update_efi_random_seed(struct notifier_block *nb,
 				  unsigned long code, void *unused)
drivers/firmware/efi/libstub/efi-stub.c +11 -0
index 42d6073bcd06..0fb0d4041d1c 100644
@@ -100,6 +100,28 @@ static void install_memreserve_table(void)
 		efi_err("Failed to install memreserve config table!\n");
 }
 
+void install_kho_table(void)
+{
+	struct linux_efi_kho_data *kho;
+	efi_guid_t kho_table_guid = LINUX_EFI_KEXEC_HANDOVER_GUID;
+	efi_status_t status;
+
+	if (!IS_ENABLED(CONFIG_KEXEC_HANDOVER))
+		return;
+
+	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*kho), (void **)&kho);
+	if (status != EFI_SUCCESS) {

drivers/firmware/efi/libstub/efi-stub.c static void install_memreserve_table(void) · line 123

Quoted 10 lines
drivers/firmware/efi/libstub/efi-stub.c +9 -0
+		efi_err("Failed to allocate KHO config table!\n");
+		return;
+	}
+
+	*kho = (struct linux_efi_kho_data){};
+
+	status = efi_bs_call(install_configuration_table, &kho_table_guid, kho);
 
+	if (status != EFI_SUCCESS)
+		efi_err("Failed to install KHO config table!\n");

When install_configuration_table fails, the pool allocation made just above using efi_bs_callis never freed, so a page of EFI_LOADER_DATA is leaked until ExitBootServices, This is a minor leak so mentioned here for review completeness.

Quoted 49 lines
drivers/firmware/efi/libstub/efi-stub.c +2 -0
+}
+
 static u32 get_supported_rt_services(void)
 {
drivers/firmware/efi/libstub/efi-stub.c +0 -0
 	const efi_rt_properties_table_t *rt_prop_table;
drivers/firmware/efi/libstub/efistub.h +2 -0
index fd91fc15ec81..9e0034c1260a 100644
@@ -1057,6 +1057,8 @@ efi_status_t efi_exit_boot_services(void *handle, void *priv,
 efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
 			     unsigned long kernel_addr, char *cmdline_ptr);
 
+void install_kho_table(void);
+
 void *get_fdt(unsigned long *fdt_size);
 
 efi_status_t efi_alloc_virtmap(efi_memory_desc_t **virtmap,
drivers/firmware/efi/libstub/loongarch.c +2 -0
index 2b0c87dc9908..c2df097771e4 100644
@@ -74,6 +74,8 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image,
 	efi_status_t status;
 	u32 desc_ver;
 
+	install_kho_table();
+
 	status = efi_alloc_virtmap(&priv.runtime_map, &desc_size, &desc_ver);
 	if (status != EFI_SUCCESS) {
 		efi_err("Unable to retrieve UEFI memory map.\n");
include/linux/efi.h +3 -0
index aa15ff88539b..253e98bf9922 100644
@@ -422,6 +422,7 @@ void efi_native_runtime_setup(void);
 #define LINUX_EFI_COCO_SECRET_AREA_GUID		EFI_GUID(0xadf956ad, 0xe98c, 0x484c,  0xae, 0x11, 0xb5, 0x1c, 0x7d, 0x33, 0x64, 0x47)
 #define LINUX_EFI_BOOT_MEMMAP_GUID		EFI_GUID(0x800f683f, 0xd08b, 0x423a,  0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4)
 #define LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID	EFI_GUID(0xd5d1de3c, 0x105c, 0x44f9,  0x9e, 0xa9, 0xbc, 0xef, 0x98, 0x12, 0x00, 0x31)
+#define LINUX_EFI_KEXEC_HANDOVER_GUID		EFI_GUID(0xc941b6c7, 0x7b3f, 0x4af6,  0x9e, 0x50, 0xfc, 0xb3, 0xa8, 0x86, 0x8a, 0x17)
 
 #define RISCV_EFI_BOOT_PROTOCOL_GUID		EFI_GUID(0xccd15fec, 0x6f73, 0x4eec,  0x83, 0x95, 0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf)
 
@@ -1273,6 +1274,45 @@ struct linux_efi_memreserve {
 
 void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
 
+/**
+ * struct linux_efi_kho_data - KHO state passed to the next kernel

include/linux/efi.h struct linux_efi_memreserve { · line 1288

Quoted 10 lines
include/linux/efi.h +10 -0
+ * @fdt_addr: physical address of the KHO FDT, 0 if there is none
+ * @fdt_size: size of the KHO FDT
+ * @bootmem_addr: physical address of the bootmem area list
+ * @bootmem_size: size of the bootmem area list
+ *
+ * Pointed to by the LINUX_EFI_KEXEC_HANDOVER_GUID configuration table.
+ *
+ * The EFI stub installs this table zeroed on a cold boot. It stays
+ * in place across kexec; the running kernel rewrites it just before
+ * each kexec, and the next kernel reads it during early boot.

This statement is only true for the LoongArch and on arm64/riscv/x86 no table exists, so the comment describes a kernel-wide invariant that does not hold.

Quoted 30 lines
include/linux/efi.h +27 -0
+ *
+ * The layout is fixed. Changing it requires a new GUID.
+ */
+struct linux_efi_kho_data {
+	u64 fdt_addr;
+	u64 fdt_size;
+	u64 bootmem_addr;
+	u64 bootmem_size;
+};
+
+#ifdef CONFIG_EFI
+bool efi_kho_table_found(void);
+int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
+		   phys_addr_t bootmem_addr, u64 bootmem_size);
+#else
+static inline bool efi_kho_table_found(void)
+{
+	return false;
+}
+
+static inline int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
+				 phys_addr_t bootmem_addr, u64 bootmem_size)
+{
+	return 0;
+}
+#endif
+
 /*
  * The LINUX_EFI_MOK_VARIABLE_TABLE_GUID config table can be provided
  * to the kernel by an EFI boot loader. The table contains a packed
Signature · 2 lines
Regards,
Mukesh Pilaniya
author
#8 lore raw

PATCH v6 2/8 kho: Write KHO state to the EFI table at kexec time

In reply to George Guo

From: George Guo <[email protected]>

On arm64 and riscv, KHO addresses and sizes are passed through /chosen properties in the boot FDT prepared for each kexec image. On x86, they are passed through a SETUP_KEXEC_KHO record linked from the image's boot parameters. These records are per-image, unlike the shared EFI table.

The EFI table is shared. A KHO-enabled normal image and a crash image can both be loaded, but need different contents: the normal image needs the KHO addresses, while the crash image needs an empty table. Updating the table at load time cannot satisfy both.

Write the table in kernel_kexec() and __crash_kexec(), once the image that will run is known. kho_update_efi_table() fills the table from that image, or zeroes it if the image has no KHO state, including images loaded with kexec_load().

Clear the table even with CONFIG_KEXEC_HANDOVER disabled, since the kernel may have inherited a populated table. This prevents a later kernel from trying to revive obsolete KHO state.

The table stores the size of the bootmem area list (bufsz), not of its page-aligned segment, as the FDT and x86 code do. If updating or clearing the table fails, kernel_kexec() returns before liveupdate_reboot(), so LUO does not save state that the next kernel would not find. __crash_kexec() ignores the error so the crash boot can still proceed.

---
 kernel/crash_core.c                |  6 ++++++
 kernel/kexec_core.c                |  5 +++++
 kernel/kexec_internal.h            |  8 ++++++++
 kernel/liveupdate/kexec_handover.c | 26 ++++++++++++++++++++++++++
 4 files changed, 45 insertions(+)
kernel/crash_core.c +6 -0
index 2b36aa9fade0..0f77adc0928b 100644
@@ -138,6 +138,12 @@ void __noclone __crash_kexec(struct pt_regs *regs)
 		if (kexec_crash_image) {
 			struct pt_regs fixed_regs;
 
+			/*
+			 * A crash image has no KHO state.  Zero the EFI
+			 * table so the crash kernel has no inherited KHO state.
+			 */
+			(void)kho_update_efi_table(kexec_crash_image);
+
 			crash_setup_regs(&fixed_regs, regs);
 			crash_save_vmcoreinfo();
 			machine_crash_shutdown(&fixed_regs);
kernel/kexec_core.c +5 -0
index dc770b9a6d05..37597091770a 100644
@@ -1146,6 +1146,11 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
+	/* Write this image's KHO state into the EFI table. */
+	error = kho_update_efi_table(kexec_image);
+	if (error)
+		goto Unlock;
+
 	if (!kexec_image->preserve_context) {
 		error = liveupdate_reboot();
 		if (error)
kernel/kexec_internal.h +8 -0
index 228bb88c018b..cfdd0b158f72 100644
@@ -2,6 +2,7 @@
 #ifndef LINUX_KEXEC_INTERNAL_H
 #define LINUX_KEXEC_INTERNAL_H
 
+#include <linux/efi.h>
 #include <linux/kexec.h>
 
 struct kexec_segment;
@@ -46,6 +47,7 @@ struct kexec_buf;
 int kho_locate_mem_hole(struct kexec_buf *kbuf,
 			int (*func)(struct resource *, void *));
 int kho_fill_kimage(struct kimage *image);
+int kho_update_efi_table(struct kimage *image);
 #else
 static inline int kho_locate_mem_hole(struct kexec_buf *kbuf,
 				      int (*func)(struct resource *, void *))
@@ -54,5 +56,11 @@ static inline int kho_locate_mem_hole(struct kexec_buf *kbuf,
 }
 
 static inline int kho_fill_kimage(struct kimage *image) { return 0; }
+
+static inline int kho_update_efi_table(struct kimage *image)
+{
+	/* The previous kernel may have left KHO state in the EFI table. */
+	return efi_kho_update(0, 0, 0, 0);
+}
 #endif /* CONFIG_KEXEC_HANDOVER */
 #endif /* LINUX_KEXEC_INTERNAL_H */
kernel/liveupdate/kexec_handover.c +26 -0
index 9a53d7750367..74c4633f5afc 100644
@@ -15,6 +15,7 @@
 #include <linux/crash_dump.h>
 #include <linux/kmemleak.h>
 #include <linux/count_zeros.h>
+#include <linux/efi.h>
 #include <linux/kasan.h>
 #include <linux/kexec.h>
 #include <linux/kexec_handover.h>
@@ -2094,6 +2095,31 @@ int kho_fill_kimage(struct kimage *image)
 	return 0;
 }
 
+/*
+ * Write the KHO state of @image into the EFI table, or zero the table if
+ * @image has none.
+ *
+ * There is one table, but the normal and the crash image can both be loaded,
+ * so the table is written just before @image runs rather than at load time.
+ * Crash images, images loaded with kexec_load() and images loaded with KHO
+ * disabled have no KHO state. Zeroing the table for them makes the next
+ * kernel boot without inherited KHO state instead of reading stale addresses.
+ */
+int kho_update_efi_table(struct kimage *image)
+{
+	int err;
+
+	if (!image->kho.fdt || !image->kho.bootmem)
+		return efi_kho_update(0, 0, 0, 0);
+
+	err = efi_kho_update(image->kho.fdt, PAGE_SIZE,
+			     image->kho.bootmem->mem, image->kho.bootmem->bufsz);
+	if (err)
+		pr_warn("failed to update EFI config table: %pe\n", ERR_PTR(err));
+
+	return err;
+}
+
 static int kho_walk_bootmem(struct kexec_buf *kbuf,
 			    int (*func)(struct resource *, void *))
 {
Signature · 1 line
2.53.0
#9 lore raw

Re: [PATCH v6 2/8] kho: Write KHO state to the EFI table at kexec time

Sashiko has reviewed this patch and found no issues.
Quoted 21 lines

From: George Guo <[email protected]>

On arm64 and riscv, KHO addresses and sizes are passed through /chosen properties in the boot FDT prepared for each kexec image. On x86, they are passed through a SETUP_KEXEC_KHO record linked from the image's boot parameters. These records are per-image, unlike the shared EFI table.

The EFI table is shared. A KHO-enabled normal image and a crash image can both be loaded, but need different contents: the normal image needs the KHO addresses, while the crash image needs an empty table. Updating the table at load time cannot satisfy both.

Write the table in kernel_kexec() and __crash_kexec(), once the image that will run is known. kho_update_efi_table() fills the table from that image, or zeroes it if the image has no KHO state, including images loaded with kexec_load().

Clear the table even with CONFIG_KEXEC_HANDOVER disabled, since the kernel may have inherited a populated table. This prevents a later kernel from trying to revive obsolete KHO state.

Quoted 7 lines

The table stores the size of the bootmem area list (bufsz), not of its page-aligned segment, as the FDT and x86 code do. If updating or clearing the table fails, kernel_kexec() returns before liveupdate_reboot(), so LUO does not save state that the next kernel would not find. __crash_kexec() ignores the error so the crash boot can still proceed.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author
#10 lore raw

PATCH v6 3/8 kho: Include linux/mm.h in kho_block.c

In reply to George Guo

From: George Guo <[email protected]>

kho_block.c calls virt_to_phys() and phys_to_virt(). With CONFIG_KFENCE=y, the LoongArch implementations use offset_in_page() and page_address(), which are provided by <linux/mm.h>.

Include <linux/mm.h> explicitly to avoid implicit-declaration build errors for these helpers.

---
 kernel/liveupdate/kho_block.c | 1 +
 1 file changed, 1 insertion(+)
kernel/liveupdate/kho_block.c +1 -0
index 0d2a342ef422..0d56891b2f4c 100644
@@ -25,6 +25,7 @@
 #include <linux/kho/abi/block.h>
 #include <linux/kho_block.h>
 #include <linux/slab.h>
+#include <linux/mm.h>
 
 /*
  * Safeguard limit for the number of serialization blocks. This is used to
Signature · 1 line
2.53.0
#11 lore raw

Re: [PATCH v6 3/8] kho: Include linux/mm.h in kho_block.c

Sashiko has reviewed this patch and found no issues.
Quoted 6 lines

From: George Guo <[email protected]>

kho_block.c calls virt_to_phys() and phys_to_virt(). With CONFIG_KFENCE=y, the LoongArch implementations use offset_in_page() and page_address(), which are provided by <linux/mm.h>.

Quoted 6 lines

Include <linux/mm.h> explicitly to avoid implicit-declaration build errors for these helpers.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author
#12 lore raw

PATCH v6 4/8 kho: Add kho_disable()

In reply to George Guo

From: George Guo <[email protected]>

On LoongArch, KHO state is passed to the next kernel only through an EFI configuration table. Only the EFI stub installs this table, during a cold boot.

A kernel entered through kexec does not run the EFI stub, so it cannot create the table if the previous kernel did not provide one. Without the table, it cannot pass KHO state to the next kernel on a subsequent kexec.

Add kho_disable() so architecture code can turn KHO off when the EFI configuration table is missing.

---
 include/linux/kexec_handover.h     | 3 +++
 kernel/liveupdate/kexec_handover.c | 9 +++++++++
 2 files changed, 12 insertions(+)
include/linux/kexec_handover.h +3 -0
index ef2b188c8ed0..6f80e1b0ccd4 100644
@@ -15,6 +15,7 @@ struct page;
 
 #ifdef CONFIG_KEXEC_HANDOVER
 bool kho_is_enabled(void);
+void kho_disable(void);
 bool is_kho_boot(void);
 
 int kho_preserve_folio(struct folio *folio);
@@ -54,6 +55,8 @@ static inline bool kho_is_enabled(void)
 	return false;
 }
 
+static inline void kho_disable(void) { }
+
 static inline bool is_kho_boot(void)
 {
 	return false;
kernel/liveupdate/kexec_handover.c +9 -0
index 74c4633f5afc..3280533eaebe 100644
@@ -82,6 +82,15 @@ static int __init kho_parse_enable(char *p)
 }
 early_param("kho", kho_parse_enable);
 
+/*
+ * For architecture code that finds, after the early parameters are parsed,
+ * that it cannot pass the KHO state to the next kernel.
+ */
+void __init kho_disable(void)
+{
+	kho_enable = false;
+}
+
 struct kho_out {
 	void *fdt;
 	struct mutex lock; /* protects KHO FDT */
Signature · 1 line
2.53.0
#13 lore raw

Re: [PATCH v6 4/8] kho: Add kho_disable()

Sashiko has reviewed this patch and found no issues.
Quoted 11 lines

From: George Guo <[email protected]>

On LoongArch, KHO state is passed to the next kernel only through an EFI configuration table. Only the EFI stub installs this table, during a cold boot.

A kernel entered through kexec does not run the EFI stub, so it cannot create the table if the previous kernel did not provide one. Without the table, it cannot pass KHO state to the next kernel on a subsequent kexec.

Quoted 4 lines

Add kho_disable() so architecture code can turn KHO off when the EFI configuration table is missing.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
maintainer
#14 lore raw

Re: [PATCH v6 4/8] kho: Add kho_disable()

In reply to George Guo

Hi George,

Quoted 10 lines · George Guo

From: George Guo <[email protected]>

On LoongArch, KHO state is passed to the next kernel only through an EFI configuration table. Only the EFI stub installs this table, during a cold boot.

A kernel entered through kexec does not run the EFI stub, so it cannot create the table if the previous kernel did not provide one. Without the table, it cannot pass KHO state to the next kernel on a subsequent kexec.

The idea behind KHO and liveupdate is that the system uses kexec + KHO for reboots after the cold boot unless a new cold reboot is a must.

Doing one yes, one no, breaks that assumption.

Quoted 2 lines

Add kho_disable() so architecture code can turn KHO off when the EFI configuration table is missing.

I don't like a global kho_disable() API. It's ambiguous and fragile.

Quoted 5 lines
---
 include/linux/kexec_handover.h     | 3 +++
 kernel/liveupdate/kexec_handover.c | 9 +++++++++
 2 files changed, 12 insertions(+)
Signature · 2 lines
Sincerely yours,
Mike.
author
#15 lore raw

PATCH v6 5/8 LoongArch: Allow memblock arrays to resize

In reply to George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

After kexec, kho_memory_init() reserves every range preserved by KHO. Each range that is not adjacent to another one needs its own entry, so with enough scattered preserved pages the array overflows and the incoming kernel panics:

  Kernel panic - not syncing: memblock: cannot resize reserved array
  Call Trace:
   memblock_double_array+0x2d4/0x2f8
   memblock_isolate_range+0x54/0x1a8
   memblock_setclr_flag+0x44/0xe0
   kho_preserved_memory_reserve+0x78/0xb8
   ...
   kho_memory_init+0xbc/0x394
   mm_core_init+0x124/0x154
   start_kernel+0x568/0x6dc

The liveupdate luo_stress_files test with 500 files triggers it on a kernel without CONFIG_SMP, where NR_CPUS is 1. With CONFIG_SMP the array is larger and the test passes, but more preserved memory still overflows it.

When the reserved array is full, memblock allocates a bigger array from free memory. This is only safe once all memory in use has been reserved, so enable resizing in arch_mem_init() after the firmware, kernel and initrd reservations are made.

The memory preserved by KHO is reserved later, in kho_memory_init(). A new array cannot land on it either, because kho_populate() already limits memblock allocations to the KHO bootmem areas, which hold no preserved data.

---
 arch/loongarch/kernel/setup.c | 3 +++
 1 file changed, 3 insertions(+)
arch/loongarch/kernel/setup.c +3 -0
index 6fa4a22a58fd..dff2e0c67ea0 100644
@@ -402,6 +402,9 @@ static void __init arch_mem_init(char **cmdline_p)
 
 	check_kernel_sections_mem();
 
+	/* KHO may restore more reserved regions than the initial array holds. */
+	memblock_allow_resize();
+
 	memblock_set_bottom_up(true);
 
 	swiotlb_init(true, SWIOTLB_VERBOSE);
Signature · 1 line
2.53.0
#16 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

Sashiko has reviewed this patch and found no issues.
Quoted 15 lines

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

After kexec, kho_memory_init() reserves every range preserved by KHO. Each range that is not adjacent to another one needs its own entry, so with enough scattered preserved pages the array overflows and the incoming kernel panics:

  Kernel panic - not syncing: memblock: cannot resize reserved array
  Call Trace:
   memblock_double_array+0x2d4/0x2f8
   memblock_isolate_range+0x54/0x1a8
Quoted 6 lines

[ ... ]
preserved data.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
maintainer
#17 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

In reply to George Guo
Quoted 5 lines · George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

This might be something to fix independent of KHO.

Huacai,

What's the reason LoongArch never enables memblock resizing and why it defines INIT_MEMBLOCK_RESERVED_REGIONS to include NR_CPUS?

Signature · 2 lines
Sincerely yours,
Mike.
maintainer
#18 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

Hi, Mike,

Quoted 8 lines · Mike Rapoport
Quoted 5 lines · George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

Quoted 6 lines

This might be something to fix independent of KHO.

Huacai,

What's the reason LoongArch never enables memblock resizing and why it defines INIT_MEMBLOCK_RESERVED_REGIONS to include NR_CPUS?

At first we didn't define INIT_MEMBLOCK_RESERVED_REGIONS, then we got boot failures on a 128-cores machine, so define it, and the value is similar to arm64.

The reason LoongArch never enables memblock resizing is because the mm code is derived from MIPS, and MIPS never enables it.

Huacai

Quoted 4 lines

--
Sincerely yours,
Mike.

maintainer
#19 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

Quoted 13 lines · Huacai Chen

Hi, Mike,

Quoted 10 lines · Mike Rapoport
Quoted 5 lines · George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

This might be something to fix independent of KHO.

Quoted 7 lines
Quoted 4 lines

Huacai,

What's the reason LoongArch never enables memblock resizing and why it defines INIT_MEMBLOCK_RESERVED_REGIONS to include NR_CPUS?

At first we didn't define INIT_MEMBLOCK_RESERVED_REGIONS, then we got boot failures on a 128-cores machine, so define it, and the value is similar to arm64.

AFAIR, arm64 had to reserve some per cpu data before they could enable memblock resizing, so they had to make it static.

Quoted 2 lines

The reason LoongArch never enables memblock resizing is because the mm code is derived from MIPS, and MIPS never enables it.

If that's the only reason, you might want to revisit that :)

Quoted 6 lines

Huacai

Quoted 4 lines

--
Sincerely yours,
Mike.

Signature · 2 lines
Sincerely yours,
Mike.
maintainer
#20 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

Quoted 17 lines · Mike Rapoport
Quoted 15 lines · Huacai Chen

Hi, Mike,

Quoted 12 lines · Mike Rapoport
Quoted 5 lines · George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

This might be something to fix independent of KHO.

Huacai,

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

What's the reason LoongArch never enables memblock resizing and why it defines INIT_MEMBLOCK_RESERVED_REGIONS to include NR_CPUS?

At first we didn't define INIT_MEMBLOCK_RESERVED_REGIONS, then we got boot failures on a 128-cores machine, so define it, and the value is similar to arm64.

AFAIR, arm64 had to reserve some per cpu data before they could enable memblock resizing, so they had to make it static.

LoongArch is similar, so below 64-cores machine has no problem.

Quoted 5 lines
Quoted 3 lines

The reason LoongArch never enables memblock resizing is because the mm code is derived from MIPS, and MIPS never enables it.

If that's the only reason, you might want to revisit that :)

Yes, it seems we need memblock_allow_resize(), but maybe it is better to bring back paging_init()? Most other archs call memblock_allow_resize() in paging_init().

Huacai

Quoted 11 lines
Quoted 7 lines

Huacai

Quoted 4 lines

--
Sincerely yours,
Mike.

--
Sincerely yours,
Mike.

maintainer
#21 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

Quoted 29 lines · Huacai Chen
Quoted 17 lines · Mike Rapoport
Quoted 15 lines · Huacai Chen

Hi, Mike,

Quoted 12 lines · Mike Rapoport
Quoted 5 lines · George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

This might be something to fix independent of KHO.

Huacai,

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

What's the reason LoongArch never enables memblock resizing and why it defines INIT_MEMBLOCK_RESERVED_REGIONS to include NR_CPUS?

At first we didn't define INIT_MEMBLOCK_RESERVED_REGIONS, then we got boot failures on a 128-cores machine, so define it, and the value is similar to arm64.

AFAIR, arm64 had to reserve some per cpu data before they could enable memblock resizing, so they had to make it static.

LoongArch is similar, so below 64-cores machine has no problem.

Quoted 1 line
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

The reason LoongArch never enables memblock resizing is because the mm code is derived from MIPS, and MIPS never enables it.

If that's the only reason, you might want to revisit that :)

Yes, it seems we need memblock_allow_resize(), but maybe it is better to bring back paging_init()? Most other archs call memblock_allow_resize() in paging_init().

paging_init() is a historical relict that hugely misnamed everywhere :)

I think platform_init() should be fine.

As far as I can tell, by the time platform_init() calls init_numa_memory(), all memory reservations that must happen before any memblock allocations are done.

Quoted 1 line

Huacai

Signature · 2 lines
Sincerely yours,
Mike.
maintainer
#22 lore raw

Re: [PATCH v6 5/8] LoongArch: Allow memblock arrays to resize

Quoted 34 lines · Mike Rapoport
Quoted 32 lines · Huacai Chen
Quoted 17 lines · Mike Rapoport
Quoted 15 lines · Huacai Chen

Hi, Mike,

Quoted 12 lines · Mike Rapoport
Quoted 5 lines · George Guo

From: George Guo <[email protected]>

LoongArch never calls memblock_allow_resize(), so the reserved region array cannot grow beyond INIT_MEMBLOCK_RESERVED_REGIONS entries (INIT_MEMBLOCK_REGIONS + NR_CPUS).

This might be something to fix independent of KHO.

Huacai,

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

What's the reason LoongArch never enables memblock resizing and why it defines INIT_MEMBLOCK_RESERVED_REGIONS to include NR_CPUS?

At first we didn't define INIT_MEMBLOCK_RESERVED_REGIONS, then we got boot failures on a 128-cores machine, so define it, and the value is similar to arm64.

AFAIR, arm64 had to reserve some per cpu data before they could enable memblock resizing, so they had to make it static.

LoongArch is similar, so below 64-cores machine has no problem.

Quoted 4 lines
Quoted 3 lines

The reason LoongArch never enables memblock resizing is because the mm code is derived from MIPS, and MIPS never enables it.

Quoted 8 lines
Quoted 4 lines
Quoted 1 line

If that's the only reason, you might want to revisit that :)

Yes, it seems we need memblock_allow_resize(), but maybe it is better to bring back paging_init()? Most other archs call memblock_allow_resize() in paging_init().

paging_init() is a historical relict that hugely misnamed everywhere :)

I think platform_init() should be fine.

If paging_init() is unnecessary, then arch_mem_init() is better than platform_init(), at least the name is better.

Huacai

Quoted 10 lines

As far as I can tell, by the time platform_init() calls init_numa_memory(), all memory reservations that must happen before any memblock allocations are done.

Quoted 1 line

Huacai

--
Sincerely yours,
Mike.

author
#23 lore raw

PATCH v6 6/8 LoongArch: Enable kexec handover (KHO)

In reply to George Guo

From: George Guo <[email protected]>

Enable ARCH_SUPPORTS_KEXEC_HANDOVER on 64-bit LoongArch with EFI_STUB. KHO state is passed through an EFI configuration table installed by the stub.

Disable KHO at boot if the table is missing, since the kernel cannot pass KHO state to the next kernel without it.

---
 arch/loongarch/Kconfig        |  3 +++
 arch/loongarch/kernel/setup.c | 16 ++++++++++++++++
 2 files changed, 19 insertions(+)
arch/loongarch/Kconfig +3 -0
index a21f51e5815e..d35ddd6ec540 100644
@@ -685,6 +685,9 @@ config ARCH_SUPPORTS_KEXEC
 config ARCH_SUPPORTS_KEXEC_FILE
 	def_bool 64BIT
 
+config ARCH_SUPPORTS_KEXEC_HANDOVER
+	def_bool 64BIT && EFI_STUB
+
 config ARCH_SELECTS_KEXEC_FILE
 	def_bool 64BIT
 	depends on KEXEC_FILE
arch/loongarch/kernel/setup.c +16 -0
index dff2e0c67ea0..e1023f93460c 100644
@@ -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>
@@ -594,6 +595,20 @@ static void __init prefill_possible_map(void)
 }
 #endif
 
+/*
+ * Only the EFI stub installs the KHO table, on a cold boot. A kernel
+ * started by kexec from a kernel that did not install it has no table,
+ * and the next kernel would not find what this kernel preserves, so
+ * turn KHO off.
+ */
+static void __init kho_check_efi_table(void)
+{
+	if (kho_is_enabled() && !efi_kho_table_found()) {
+		pr_warn("No EFI KHO table, disabling kexec handover\n");
+		kho_disable();
+	}
+}
+
 void __init setup_arch(char **cmdline_p)
 {
 	cpu_probe();
@@ -608,6 +623,7 @@ void __init setup_arch(char **cmdline_p)
 	bootcmdline_init(cmdline_p);
 	jump_label_init(); /* Initialise the static keys for early params */
 	parse_early_param();
+	kho_check_efi_table();
 	reserve_initrd_mem();
 
 	platform_init();
Signature · 1 line
2.53.0
#24 lore raw

Re: [PATCH v6 6/8] LoongArch: Enable kexec handover (KHO)

Sashiko has reviewed this patch and found no issues.
Quoted 6 lines

From: George Guo <[email protected]>

Enable ARCH_SUPPORTS_KEXEC_HANDOVER on 64-bit LoongArch with EFI_STUB. KHO state is passed through an EFI configuration table installed by the stub.

Quoted 6 lines

Disable KHO at boot if the table is missing, since the kernel cannot pass KHO state to the next kernel without it.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
author
#25 lore raw

PATCH v6 7/8 selftests/kho: Add LoongArch vmtest support

In reply to George Guo

From: George Guo <[email protected]>

Add loongarch.conf to run the test on QEMU's LoongArch virtual machine with a la464 CPU and an 8250 serial console, and let vmtest.sh accept the loongarch64 target.

Boot vmlinux.efi through EFI firmware. The EFI stub installs the KHO EFI table, and QEMU's direct kernel boot would skip the stub. QEMU_EFI defaults to /usr/share/edk2/loongarch64/QEMU_EFI.fd and can be set for distributions that install the firmware elsewhere, such as Debian's qemu-efi-loongarch64 package.

QEMU's LoongArch virtual machine has no i8042 controller. Enable the generic PCI host so its I/O space is mapped before the i8042 fallback probe touches the legacy ports. This keeps i8042 enabled, so the same kernel configuration still works on real hardware.

Enable ACPI_SPCR_TABLE so the bare earlycon parameter gets the UART from the SPCR table installed by EDK2. Unlike x86, LoongArch does not enable it by default, and without it there is no early console and vmtest sees no early boot output.

The KHO vmtest needs CONFIG_DEFERRED_STRUCT_PAGE_INIT=y, which depends on SPARSEMEM, so select CONFIG_SPARSEMEM_MANUAL=y.

---
 tools/testing/selftests/kho/loongarch.conf | 11 +++++++++++
 tools/testing/selftests/kho/vmtest.sh      |  3 ++-
 2 files changed, 13 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/kho/loongarch.conf
tools/testing/selftests/kho/loongarch.conf new file +11 -0
new file mode 100644
index 000000000000..36153b03c4ef
@@ -0,0 +1,11 @@
+QEMU_EFI="${QEMU_EFI:-/usr/share/edk2/loongarch64/QEMU_EFI.fd}"
+QEMU_CMD="qemu-system-loongarch64 -M virt -cpu la464 -bios $QEMU_EFI"
+QEMU_KCONFIG="
+CONFIG_ACPI_SPCR_TABLE=y
+CONFIG_PCI_HOST_GENERIC=y
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SPARSEMEM_MANUAL=y
+"
+KERNEL_IMAGE="vmlinux.efi"
+KERNEL_CMDLINE="console=ttyS0 earlycon"
tools/testing/selftests/kho/vmtest.sh +2 -1
index 0014bd76e88d..d05ba0734b25 100755
@@ -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, loongarch64, x86_64
 	-h)	display this help
 EOF
 }
@@ -126,6 +126,7 @@ function target_to_arch() {
 
 	case $target in
 	     aarch64) echo "arm64" ;;
+	     loongarch64) echo "loongarch" ;;
 	     x86_64) echo "x86" ;;
 	     *) skip "architecture $target is not supported"
 	esac
Signature · 1 line
2.53.0
#26 lore raw

Re: [PATCH v6 7/8] selftests/kho: Add LoongArch vmtest support

Sashiko has reviewed this patch and found no issues.
Quoted 22 lines

From: George Guo <[email protected]>

Add loongarch.conf to run the test on QEMU's LoongArch virtual machine with a la464 CPU and an 8250 serial console, and let vmtest.sh accept the loongarch64 target.

Boot vmlinux.efi through EFI firmware. The EFI stub installs the KHO EFI table, and QEMU's direct kernel boot would skip the stub. QEMU_EFI defaults to /usr/share/edk2/loongarch64/QEMU_EFI.fd and can be set for distributions that install the firmware elsewhere, such as Debian's qemu-efi-loongarch64 package.

QEMU's LoongArch virtual machine has no i8042 controller. Enable the generic PCI host so its I/O space is mapped before the i8042 fallback probe touches the legacy ports. This keeps i8042 enabled, so the same kernel configuration still works on real hardware.

Enable ACPI_SPCR_TABLE so the bare earlycon parameter gets the UART from the SPCR table installed by EDK2. Unlike x86, LoongArch does not enable it by default, and without it there is no early console and vmtest sees no early boot output.

Quoted 6 lines

The KHO vmtest needs CONFIG_DEFERRED_STRUCT_PAGE_INIT=y, which depends on SPARSEMEM, so select CONFIG_SPARSEMEM_MANUAL=y.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
author
#27 lore raw

PATCH v6 8/8 selftests/liveupdate: Add LoongArch vmtest support

In reply to George Guo

From: George Guo <[email protected]>

Add the LoongArch kernel configuration, target and cross-compiler detection, and include loongarch64 in the VM test suite.

Boot vmlinux.efi through EFI firmware so the EFI stub installs the KHO table. Allow the firmware path to be set with QEMU_EFI and skip the test if it is unavailable.

---
 .../selftests/liveupdate/config.loongarch64        |  5 +++++
 tools/testing/selftests/liveupdate/run-vmtests.sh  |  4 ++--
 tools/testing/selftests/liveupdate/vmtest.sh       | 14 +++++++++++++-
 3 files changed, 20 insertions(+), 3 deletions(-)
 create mode 100644 tools/testing/selftests/liveupdate/config.loongarch64
tools/testing/selftests/liveupdate/config.loongarch64 new file +5 -0
new file mode 100644
index 000000000000..264067ef5ab1
@@ -0,0 +1,5 @@
+CONFIG_ACPI_SPCR_TABLE=y
+CONFIG_PCI_HOST_GENERIC=y
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SMP=y
tools/testing/selftests/liveupdate/run-vmtests.sh +2 -2
index d656ce58c5a7..f6cfb76b1dac 100755
@@ -4,7 +4,7 @@
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 TEST_RUNNER="$SCRIPT_DIR/vmtest.sh"
 
-TARGETS=("x86_64" "aarch64")
+TARGETS=("x86_64" "aarch64" "loongarch64")
 
 GREEN='\033[0;32m'
 RED='\033[0;31m'
@@ -72,7 +72,7 @@ function main() {
 		for test_name in "${TEST_NAMES[@]}"; do
 			log="$output_dir/${arch}_${test_name}.log"
 
-			printf "  -> %-8s %-24s ... " "$arch" "$test_name"
+			printf "  -> %-11s %-24s ... " "$arch" "$test_name"
 
 			"$TEST_RUNNER" -t "$arch" -T "$test_name" &> "$log"
 			exit_code=$?
tools/testing/selftests/liveupdate/vmtest.sh +13 -1
index 69beda6c961d..ffe82512f3e4 100755
@@ -21,6 +21,12 @@ function get_arch_conf() {
 		QEMU_CMD="qemu-system-aarch64 -M virt -cpu max"
 		KERNEL_IMAGE="Image"
 		KERNEL_CMDLINE="console=ttyAMA0"
+	elif [[ "$arch" == "loongarch" ]]; then
+		local qemu_efi="${QEMU_EFI:-/usr/share/edk2/loongarch64/QEMU_EFI.fd}"
+		[ -r "$qemu_efi" ] || skip "EFI firmware '$qemu_efi' not found, set QEMU_EFI"
+		QEMU_CMD="qemu-system-loongarch64 -M virt -cpu la464 -bios $qemu_efi"
+		KERNEL_IMAGE="vmlinux.efi"
+		KERNEL_CMDLINE="console=ttyS0 earlycon"
 	elif [[ "$arch" == "x86" ]]; then
 		QEMU_CMD="qemu-system-x86_64"
 		KERNEL_IMAGE="bzImage"
@@ -37,11 +43,15 @@ $0 [-d build_dir] [-j jobs] [-t target_arch] [-T test_name] [-w workspace_dir] [
 Options:
 	-d)	path to the kernel build directory (default: .luo_test_build.<arch>)
 	-j)	number of jobs for compilation
-	-t)	run test for target_arch (aarch64, x86_64)
+	-t)	run test for target_arch (aarch64, loongarch64, x86_64)
 	-T)	test name to run (default: luo_kexec_simple)
 	-w)	custom workspace directory (default: creates temp dir)
 	-k)	keep workspace directory after successful test
 	-h)	display this help
+
+Environment:
+	QEMU_EFI	LoongArch EFI firmware path
+			(default: /usr/share/edk2/loongarch64/QEMU_EFI.fd)
 EOF
 }
 
@@ -86,6 +96,7 @@ function detect_cross_compile() {
 	local candidate=""
 	case "$target" in
 		aarch64) candidate="aarch64-linux-gnu-" ;;
+		loongarch64) candidate="loongarch64-linux-gnu-" ;;
 		x86_64)  candidate="x86_64-linux-gnu-" ;;
 		*)       skip "Auto-detection for target '$target' not supported. Please set CROSS_COMPILE manually." ;;
 	esac
@@ -193,6 +204,7 @@ function target_to_arch() {
 	local target=$1
 	case $target in
 	     aarch64) echo "arm64" ;;
+	     loongarch64) echo "loongarch" ;;
 	     x86_64) echo "x86" ;;
 	     *) skip "architecture $target is not supported"
 	esac
Signature · 1 line
2.53.0
#28 lore raw

Re: [PATCH v6 8/8] selftests/liveupdate: Add LoongArch vmtest support

Sashiko has reviewed this patch and found no issues.
Quoted 5 lines

From: George Guo <[email protected]>

Add the LoongArch kernel configuration, target and cross-compiler detection, and include loongarch64 in the VM test suite.

Quoted 7 lines

Boot vmlinux.efi through EFI firmware so the EFI stub installs the KHO table. Allow the firmware path to be set with QEMU_EFI and skip the test if it is unavailable.

Sashiko has reviewed this patch and found no issues.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8