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 <[email protected]> > 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 <[email protected]> > > 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
