[PATCH v3 1/2] kho: history: track previous kernel version

Pratyush Yadav pratyush at kernel.org
Tue Jan 20 07:40:12 PST 2026


On Fri, Jan 16 2026, Breno Leitao wrote:

> Hello Pratyush,
>
> On Wed, Jan 14, 2026 at 07:19:11PM +0000, Pratyush Yadav wrote:
>> On Thu, Jan 08 2026, Breno Leitao wrote:
>> 
>> > Store and display the kernel version from the previous kexec boot.
>> >
>> > The current kernel's release string is saved to the "previous-release"
>> > property in the KHO FDT before kexec. On the next boot, if this property
>> > exists, the previous kernel version is retrieved and printed during
>> > early boot.
>> >
>> > This helps diagnose bugs that only manifest when kexecing from specific
>> > kernel versions, making it easier to correlate crashes with the kernel
>> > that initiated the kexec.
>> 
>> The KHO FDT is ABI. So you should be bumping the version number when you
>> make changes to it.
>> 
>> But honestly, adding this "optional" stuff to the core KHO ABI makes me
>> uneasy. I say optional since it is not needed for the main functionality
>> of KHO. Making this a part of the ABI increases the surface area we
>> have. The more things we stuff in the ABI, the more inflexible it gets
>> over time.
>> 
>> Any changes to the KHO ABI means all consumers also need a version bump.
>> This includes LUO and all its users for example. So I would really like
>> to avoid adding optional things in core KHO FDT.
>> 
>> The easy fix is to add a separate subtree for the optional metadata. You
>> would still need to create an ABI for the data format, but being
>> independent of core KHO, it will make it more flexible and easier to
>> change in the future. You can keep the code in kexec_handover.c.
>
> Thanks for the feedback and guidance!
>
> I was able to hack this a bit and I came up with something like the
> follow. Is this what you have in mind?

Thanks! Yes, this looks much better. Some comments below.

>
> Author: Breno Leitao <leitao at debian.org>
> Date:   Fri Jan 16 06:42:56 2026 -0800
>
>     kho: history: track previous kernel version and kexec count
>     
>     Use Kexec Handover (KHO) to pass the previous kernel's version string
>     and the number of kexec reboots since the last cold boot to the next
>     kernel, and print it at boot time.
>     
>     Example output:
>         [    0.000000] KHO: exec from: 6.19.0-rc4-next-20260107 (count 1)
>     
>     Motivation
>     ==========
>     
>     Bugs that only reproduce when kexecing from specific kernel versions
>     are difficult to diagnose. These issues occur when a buggy kernel
>     kexecs into a new kernel, with the bug manifesting only in the second
>     kernel.
>     
>     Recent examples include:
>     
>      * eb2266312507 ("x86/boot: Fix page table access in 5-level to 4-level paging transition")
>      * 77d48d39e991 ("efistub/tpm: Use ACPI reclaim memory for event log to avoid corruption")
>      * 64b45dd46e15 ("x86/efi: skip memattr table on kexec boot")
>     
>     As kexec-based reboots become more common, these version-dependent bugs
>     are appearing more frequently. At scale, correlating crashes to the
>     previous kernel version is challenging, especially when issues only
>     occur in specific transition scenarios.
>     
>     Implementation
>     ==============
>     
>     The history metadata is stored in a separate FDT subtree registered via
>     kho_add_subtree(), rather than being embedded directly in the root KHO
>     FDT. This design choice:
>     
>      - Keeps the core KHO ABI minimal and stable
>      - Allows the history format to evolve independently
>      - Avoids requiring version bumps for all KHO consumers (LUO, etc.)
>        when the history format changes
>     
>     The history subtree uses its own compatible string "kho-history-v1" and
>     contains two properties:
>      - previous-release: The kernel version that initiated the kexec
>      - kexec-count: Number of kexec boots since last cold boot
>     
>     On cold boot, kexec-count starts at 0 and increments with each kexec.
>     The count helps identify issues that only manifest after multiple
>     consecutive kexec reboots.

Very well written changelog!

>     
>     Signed-off-by: Breno Leitao <leitao at debian.org>
>
> diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
> index 285eda8a36e4..da19d6029815 100644
> --- a/include/linux/kho/abi/kexec_handover.h
> +++ b/include/linux/kho/abi/kexec_handover.h
> @@ -84,6 +84,29 @@
>  /* The FDT property for sub-FDTs. */
>  #define KHO_FDT_SUB_TREE_PROP_NAME "fdt"
>  
> +/*
> + * The "history" subtree stores optional metadata about the kexec chain.
> + * It is registered as a separate FDT via kho_add_subtree(), keeping it
> + * independent from the core KHO ABI. This allows the history format to
> + * evolve without affecting other KHO consumers.
> + *
> + * The history FDT structure:

I don't have a strong preference here, but you don't _have_ to use FDT.
For example, with memfd, we moved from FDT to plain C structs during the
evolution of the patchset. Main reason is that FDT programming is a bit
annoying. C structs make many things much easier. For example, you can
always assume a certain property always exists and is of a given size,
and you don't have to validate every single property you read.

Anyway, I don't mind either way.

> + *
> + *   / {
> + *       compatible = "kho-history-v1";
> + *       previous-release = "6.x.y-...";
> + *       kexec-count = <N>;
> + *   };
> + */
> +#define KHO_HISTORY_NODE_NAME "history"

Do we want to call it history? Perhaps "kexec-metadata" instead? So we
could use it for other misc information if needed later.

Mike/Pasha, any thoughts?

> +#define KHO_HISTORY_COMPATIBLE "kho-history-v1"
> +
> +/* The FDT property to track previous kernel (kexec caller) */
> +#define KHO_PROP_PREVIOUS_RELEASE "previous-release"
> +
> +/* The FDT property to track number of kexec counts so far */
> +#define KHO_PROP_KEXEC_COUNT "kexec-count"
> +
>  /**
>   * DOC: Kexec Handover ABI for vmalloc Preservation
>   *
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 3cf2dc6840c9..fd22b0947587 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -15,6 +15,7 @@
>  #include <linux/count_zeros.h>
>  #include <linux/kexec.h>
>  #include <linux/kexec_handover.h>
> +#include <linux/utsname.h>
>  #include <linux/kho/abi/kexec_handover.h>
>  #include <linux/libfdt.h>
>  #include <linux/list.h>
> @@ -1246,6 +1247,8 @@ struct kho_in {
>  	phys_addr_t fdt_phys;
>  	phys_addr_t scratch_phys;
>  	phys_addr_t mem_map_phys;
> +	char previous_release[__NEW_UTS_LEN + 1];
> +	u32 kexec_count;
>  	struct kho_debugfs dbg;
>  };
>  
> @@ -1331,6 +1334,48 @@ static __init int kho_out_fdt_setup(void)
>  	return err;
>  }
>  
> +/*
> + * Create a separate FDT subtree for optional history metadata.
> + * This keeps the core KHO ABI minimal and allows the history format
> + * to evolve independently.
> + */
> +static __init int kho_history_init(void)
> +{
> +	u32 kexec_count;
> +	void *fdt;
> +	int err;
> +
> +	fdt = kho_alloc_preserve(PAGE_SIZE);
> +	if (IS_ERR(fdt))
> +		return PTR_ERR(fdt);
> +
> +	err = fdt_create(fdt, PAGE_SIZE);
> +	err |= fdt_finish_reservemap(fdt);
> +	err |= fdt_begin_node(fdt, "");
> +	err |= fdt_property_string(fdt, "compatible", KHO_HISTORY_COMPATIBLE);
> +	err |= fdt_property_string(fdt, KHO_PROP_PREVIOUS_RELEASE,
> +				   init_uts_ns.name.release);
> +	/* kho_in.kexec_count is set to 0 on cold boot */
> +	kexec_count = kho_in.kexec_count + 1;
> +	err |= fdt_property(fdt, KHO_PROP_KEXEC_COUNT, &kexec_count,
> +			    sizeof(kexec_count));
> +	err |= fdt_end_node(fdt);
> +	err |= fdt_finish(fdt);
> +
> +	if (err) {
> +		kho_unpreserve_free(fdt);
> +		return err;
> +	}
> +
> +	err = kho_add_subtree(KHO_HISTORY_NODE_NAME, fdt);
> +	if (err) {
> +		kho_unpreserve_free(fdt);
> +		return err;
> +	}
> +
> +	return 0;
> +}
> +
>  static __init int kho_init(void)
>  {
>  	const void *fdt = kho_get_fdt();
> @@ -1357,6 +1402,10 @@ static __init int kho_init(void)
>  	if (err)
>  		goto err_free_fdt;
>  
> +	err = kho_history_init();
> +	if (err)
> +		pr_warn("failed to initialize history subtree: %d\n", err);
> +
>  	if (fdt) {
>  		kho_in_debugfs_init(&kho_in.dbg, fdt);
>  		return 0;
> @@ -1425,6 +1474,61 @@ static void __init kho_release_scratch(void)
>  	}
>  }
>  
> +static int __init kho_print_previous_kernel(const void *fdt)
> +{
> +	const char *prev_release;
> +	const u64 *history_phys;
> +	const u32 *count_ptr;
> +	void *history_fdt;
> +	int history_node;
> +	int len;
> +	int ret;
> +
> +	/* Find the history subtree reference in root FDT */
> +	history_node = fdt_subnode_offset(fdt, 0, KHO_HISTORY_NODE_NAME);
> +	if (history_node < 0)
> +		/* This is fine, previous kernel didn't export history */
> +		return -ENOENT;
> +
> +	/* Get the physical address of the history FDT */
> +	history_phys = fdt_getprop(fdt, history_node, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> +	if (!history_phys || len != sizeof(*history_phys))
> +		return -ENOENT;
> +
> +	/* Map the history FDT */
> +	history_fdt = early_memremap(*history_phys, PAGE_SIZE);
> +	if (!history_fdt)
> +		return -ENOMEM;

There is no real reason to call this so early in boot. You can call it
from an initcall or from kho_init(). Then you won't need the
early_memremap(). And you should also not poke into the KHO FDT
directly. Use kho_retrieve_subtree() instead.

> +
> +	prev_release = fdt_getprop(history_fdt, 0, KHO_PROP_PREVIOUS_RELEASE, &len);
> +	if (!prev_release || len <= 0) {
> +		ret = -ENOENT;
> +		goto exit;
> +	}
> +
> +	/* Read the kexec count from the previous kernel */
> +	count_ptr = fdt_getprop(history_fdt, 0, KHO_PROP_KEXEC_COUNT, &len);
> +	if (WARN_ON(!count_ptr || len != sizeof(u32))) {
> +		ret = -ENOENT;
> +		goto exit;
> +	}
> +	/*
> +	 * This populate the kernel structure that will be persisted during
> +	 * kernel life time, and the fdt will be unmapped
> +	 */
> +	kho_in.kexec_count = *count_ptr;

This is another annoying thing about FDT. Alignment is only guaranteed
at 32 bits. So you should use get_unaligned() AFAIK.

> +
> +	strscpy(kho_in.previous_release, prev_release,
> +		sizeof(kho_in.previous_release));
> +	pr_info("exec from: %s (count %u)\n", kho_in.previous_release,
> +					      kho_in.kexec_count);
> +
> +	ret = 0;
> +exit:
> +	early_memunmap(history_fdt, PAGE_SIZE);
> +	return ret;
> +}
> +
>  void __init kho_memory_init(void)
>  {
>  	if (kho_in.mem_map_phys) {
> @@ -1513,7 +1617,10 @@ void __init kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
>  	kho_in.scratch_phys = scratch_phys;
>  	kho_in.mem_map_phys = mem_map_phys;
>  	kho_scratch_cnt = scratch_cnt;
> -	pr_info("found kexec handover data.\n");
> +
> +	if (kho_print_previous_kernel(fdt))
> +		/* Fallback message when previous kernel info unavailable */
> +		pr_info("found kexec handover data.\n");
>  
>  out:
>  	if (fdt)

-- 
Regards,
Pratyush Yadav



More information about the kexec mailing list