Hi, George,

On Fri, Sep 4, 2026 at 6:10 PM George Guo <[email protected]> wrote:
>
> From: George Guo <[email protected]>
>
> Add an architecture-agnostic EFI configuration table channel for kexec
> handover (KHO): a LINUX_EFI_KEXEC_HANDOVER_GUID table entry pointing at
> a struct linux_efi_kho_data that carries the KHO state FDT and scratch
> area addresses from one kernel to the next.
>
> This is the channel for architectures that boot through EFI without a
> device tree (e.g. LoongArch), where the /chosen linux,kho-fdt and
> linux,kho-scratch properties read by early_init_dt_check_kho() are not
> available.  Architectures with a boot FDT (arm64, riscv) keep using the
> FDT path and do not select this.
>
> The design mirrors the LINUX_EFI_MEMRESERVE_TABLE_GUID channel:
>
>   - The EFI stub allocates and installs the table once at boot
>     (install_kho_table(), next to install_memreserve_table()), so the
>     config table entry is inherited across kexec for free.
>
>   - The reader is a common_tables[] entry in
>     efi_config_parse_tables().  It reserves the stub-allocated table
>     with memblock_reserve(), the same way the memreserve entries are
>     reserved there, so the table is neither handed out by the buddy
>     allocator nor placed on by kexec segments.  It then maps the table
>     and calls kho_populate().  No arch-specific setup.c hook is
>     needed.
>
>   - efi_kho_update() rewrites the table contents in place before a
>     kexec; the config table array is never rebuilt and st->tables is
>     never switched, unlike the per-arch approach it replaces.  The
>     table stays persistently mapped from an early_initcall, the same
>     way the memreserve root is, so the update also works on the crash
>     kexec path.
>
> Gated behind CONFIG_EFI_KHO, selected by architectures that use this
> channel.
>
> Signed-off-by: George Guo <[email protected]>
> ---
>  drivers/firmware/efi/Kconfig            | 12 ++++
>  drivers/firmware/efi/efi.c              | 78 +++++++++++++++++++++++++
>  drivers/firmware/efi/libstub/efi-stub.c | 25 ++++++++
>  include/linux/efi.h                     | 36 ++++++++++++
>  4 files changed, 151 insertions(+)
>
> diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig
> index 29e0729299f5..d6c1372484b4 100644
> --- a/drivers/firmware/efi/Kconfig
> +++ b/drivers/firmware/efi/Kconfig
> @@ -314,6 +314,18 @@ config EFI_SBAT_FILE
>
>           If unsure, leave blank.
>
> +config EFI_KHO
> +       bool
> +       depends on EFI_STUB && EFI_GENERIC_STUB && KEXEC_HANDOVER
> +       help
> +         Carry the KHO state (the KHO state FDT and the scratch area) from
> +         one kernel to the next across kexec via an EFI configuration table
> +         entry under LINUX_EFI_KEXEC_HANDOVER_GUID, for architectures that
> +         boot through EFI without a device tree (e.g. LoongArch).
> +
> +         Architectures with a boot FDT (arm64, riscv) use the /chosen FDT
> +         path instead and do not select this.
> +
>  endmenu
>
>  config UEFI_CPER
> diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c
> index 0327a39d31fa..6380cfab1493 100644
> --- a/drivers/firmware/efi/efi.c
> +++ b/drivers/firmware/efi/efi.c
> @@ -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,9 @@ 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;
> +#ifdef CONFIG_EFI_KHO
> +static unsigned long __ro_after_init efi_kho_table_phys = 
> EFI_INVALID_TABLE_ADDR;
I think it is better to define:
static unsigned long __initdata efi_kho = EFI_INVALID_TABLE_ADDR;

> +#endif
>
>  extern unsigned long primary_display_table;
>
> @@ -629,6 +633,9 @@ 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"    },
> +#ifdef CONFIG_EFI_KHO
> +       {LINUX_EFI_KEXEC_HANDOVER_GUID,         &efi_kho_table_phys,    "KHO" 
>           },
> +#endif
>         {LINUX_EFI_INITRD_MEDIA_GUID,           &initrd,                
> "INITRD"        },
>         {EFI_RT_PROPERTIES_TABLE_GUID,          &rt_prop,               
> "RTPROP"        },
>  #ifdef CONFIG_OVMF_DEBUG_LOG
> @@ -806,6 +813,31 @@ int __init efi_config_parse_tables(const 
> efi_config_table_t *config_tables,
>                 }
>         }
>
> +#ifdef CONFIG_EFI_KHO
> +       if (efi_kho_table_phys != EFI_INVALID_TABLE_ADDR) {
> +               struct linux_efi_kho_data *kho;
> +
> +               /*
> +                * Reserve the stub-allocated table so it is neither handed
> +                * out by the buddy allocator nor placed on by kexec
> +                * segments, mirroring the memreserve handling above.  This
> +                * runs on every boot, so it also protects the table in the
> +                * next kernel until it reads it.
> +                */
> +               memblock_reserve(efi_kho_table_phys, sizeof(*kho));
> +
> +               kho = early_memremap(efi_kho_table_phys, sizeof(*kho));
> +               if (kho) {
> +                       if (kho->fdt_addr)
> +                               kho_populate((phys_addr_t)kho->fdt_addr,
> +                                            kho->fdt_size,
> +                                            (phys_addr_t)kho->scratch_addr,
> +                                            kho->scratch_size);
Now there is no 80 cols limit, you can just use
     kho_populate((phys_addr_t)kho->fdt_addr, kho->fdt_size,
              (phys_addr_t)kho->scratch_addr, kho->scratch_size);

> +                       early_memunmap(kho, sizeof(*kho));
> +               }
> +       }
> +#endif
> +
>         if (rt_prop != EFI_INVALID_TABLE_ADDR) {
>                 efi_rt_properties_table_t *tbl;
>
> @@ -1171,6 +1203,52 @@ static int __init efi_memreserve_root_init(void)
>  }
>  early_initcall(efi_memreserve_root_init);
>
> +#ifdef CONFIG_EFI_KHO
> +static struct linux_efi_kho_data *efi_kho_table __ro_after_init;
> +
> +static int __init efi_kho_table_init(void)
> +{
> +       if (efi_kho_table_phys == EFI_INVALID_TABLE_ADDR)
> +               return 0;
> +
> +       /*
> +        * Keep a persistent mapping of the table, the same way
> +        * efi_memreserve_root_init() keeps the memreserve root mapped:
> +        * efi_kho_update() is also called on the crash kexec path, where
> +        * memremap() is no longer an option.
> +        */
> +       efi_kho_table = memremap(efi_kho_table_phys, sizeof(*efi_kho_table),
> +                                MEMREMAP_WB);
The same, just write it in one line.

> +       WARN_ON_ONCE(!efi_kho_table);
> +
> +       return 0;
> +}
> +early_initcall(efi_kho_table_init);
> +
> +/*
> + * Update the KHO config table in place before a kexec, so the next kernel
> + * finds the current handover state.  Mirrors efi_mem_reserve_persistent():
> + * the config table entry was installed once by the EFI stub and is inherited
> + * across kexec, so only the table contents are rewritten here -- the config
> + * table array is never rebuilt and st->tables is never switched.
> + */
> +int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
> +                  phys_addr_t scratch_addr, u64 scratch_size)
> +{
> +       struct linux_efi_kho_data *kho = efi_kho_table;
> +
> +       if (!kho)
> +               return -ENODEV;
> +
> +       kho->fdt_addr           = fdt_addr;
> +       kho->fdt_size           = fdt_size;
> +       kho->scratch_addr       = scratch_addr;
> +       kho->scratch_size       = scratch_size;
> +
> +       return 0;
> +}
> +#endif
> +
>  #ifdef CONFIG_KEXEC
>  static int update_efi_random_seed(struct notifier_block *nb,
>                                   unsigned long code, void *unused)
> diff --git a/drivers/firmware/efi/libstub/efi-stub.c 
> b/drivers/firmware/efi/libstub/efi-stub.c
> index 42d6073bcd06..751f46280433 100644
> --- a/drivers/firmware/efi/libstub/efi-stub.c
> +++ b/drivers/firmware/efi/libstub/efi-stub.c
> @@ -100,6 +100,29 @@ static void install_memreserve_table(void)
>                 efi_err("Failed to install memreserve config table!\n");
>  }
>
> +static void install_kho_table(void)
> +{
> +#ifdef CONFIG_EFI_KHO
> +       struct linux_efi_kho_data *kho;
> +       efi_guid_t kho_table_guid = LINUX_EFI_KEXEC_HANDOVER_GUID;
> +       efi_status_t status;
> +
> +       status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, sizeof(*kho),
> +                            (void **)&kho);
The same, just write it in one line.

> +       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);
The same, just write it in one line.


> +       if (status != EFI_SUCCESS)
> +               efi_err("Failed to install KHO config table!\n");
> +#endif
> +}
> +
>  static u32 get_supported_rt_services(void)
>  {
>         const efi_rt_properties_table_t *rt_prop_table;
> @@ -180,6 +203,8 @@ efi_status_t efi_stub_common(efi_handle_t handle,
>
>         install_memreserve_table();
>
> +       install_kho_table();
> +
>         status = efi_boot_kernel(handle, image, image_addr, cmdline_ptr);
>
>         free_primary_display(dpy);
> diff --git a/include/linux/efi.h b/include/linux/efi.h
> index aa15ff88539b..564b3cbd5ccb 100644
> --- a/include/linux/efi.h
> +++ b/include/linux/efi.h
> @@ -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,41 @@ struct linux_efi_memreserve {
>
>  void __init efi_arch_mem_reserve(phys_addr_t addr, u64 size);
>
> +#ifdef CONFIG_EFI_KHO
> +/*
> + * The LINUX_EFI_KEXEC_HANDOVER_GUID config table points to this structure.
> + * It carries the kexec handover (KHO) state from the current kernel to the
> + * next one: the addresses of the KHO state FDT and of the scratch area.
> + *
> + * This is the handover channel for architectures that boot through EFI
> + * without a device tree (e.g. LoongArch), where the /chosen linux,kho-fdt
> + * and linux,kho-scratch properties read by early_init_dt_check_kho() are not
> + * available.  The EFI stub allocates and installs the table once at boot;
> + * the current kernel updates its contents before a kexec, and the next
> + * kernel reads it back and calls kho_populate().
> + *
> + * The layout is an ABI between the two kernels and carries no version
> + * field: an incompatible change must use a new GUID.  The handover payload
> + * itself is versioned separately by the compatible string of the KHO state
> + * FDT, which kho_populate() checks.
> + */
> +struct linux_efi_kho_data {
> +       u64 fdt_addr;
> +       u64 fdt_size;
> +       u64 scratch_addr;
> +       u64 scratch_size;
> +} __packed;
> +
> +int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
> +                  phys_addr_t scratch_addr, u64 scratch_size);
> +#else
> +static inline int efi_kho_update(phys_addr_t fdt_addr, u64 fdt_size,
> +                                phys_addr_t scratch_addr, u64 scratch_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
> --
> 2.53.0
>

Reply via email to