* [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count
@ 2026-03-09 13:41 Breno Leitao
2026-03-09 13:41 ` [PATCH v8 1/6] kho: add size parameter to kho_add_subtree() Breno Leitao
` (6 more replies)
0 siblings, 7 replies; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
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
=======
[ 0.000000] Linux version 6.19.0-rc3-upstream-00047-ge5d992347849
...
[ 0.000000] KHO: exec from: 6.19.0-rc4-next-20260107upstream-00004-g3071b0dc4498 (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.
Some bugs manifest only after multiple consecutive kexec reboots.
Tracking the kexec count helps identify these cases (this metric is
already used by live update sub-system).
KHO provides a reliable mechanism to pass information between kernels.
By carrying the previous kernel's release string and kexec count
forward, we can print this context at boot time to aid debugging.
The goal of this feature is to have this information being printed in
early boot, so, users can trace back kernel releases in kexec. Systemd
is not helpful because we cannot assume that the previous kernel has
systemd or even write access to the disk (common when using Linux as
bootloaders)
Signed-off-by: Breno Leitao <leitao@debian.org>
Acked-by: SeongJae Park <sj@kernel.org>
---
Changes in v8:
- Removed an unnecessary size variable (Mike)
- Moved some renaming to the proper rename patch (Mike)
- Link to v7: https://patch.msgid.link/20260306-kho-v7-0-404d14c188bb@debian.org
Changes in v7:
- Persist blob size in KHO FDT as a "blob-size" property so the
incoming kernel can determine blob sizes without assuming FDT
format (Pratyush)
- Link to v6: https://patch.msgid.link/20260127-kho-v6-0-56f9396681c2@debian.org
Changes in v6:
- s/UAPI/uABI (Mike)
- Expanded the rename of s/fdt/blob to kho_debugfs_* calls also (Mike)
- Link to v5: https://patch.msgid.link/20260126-kho-v5-0-7cd0f69ab204@debian.org
Changes in v5:
- Add a patch to document the new feature.
- Change kho_add_subtree() to receive the size instad of guessing it.
- reworked some code path to make it more standard.
- Link to v4: https://patch.msgid.link/20260121-kho-v4-1-5c8fe77b6804@debian.org
Changes in v4:
- Squashed everything in a single commit
- Moved from FDT to C structs (Pratyush)
- Usage of subtress intead of FDT directly (Pratyush)
- Renamed a bunch of variables and functions.
- Link to v3: https://patch.msgid.link/20260108-kho-v3-0-b1d6b7a89342@debian.org
Changes in v3:
- Remove the extra CONFIG for this feature.
- Reworded some identifiers, properties and printks.
- Better documented the questions raised during v2.
- Link to v2: https://patch.msgid.link/20260102-kho-v2-0-1747b1a3a1d6@debian.org
Changes from v2 to v1 (RFC)
- Track the number of kexecs since cold boot (Pasha)
- Change the printk() order compared to KHO
- Rewording of the commit summary
- Link to RFC: https://patch.msgid.link/20251230-kho-v1-1-4d795a24da9e@debian.org
---
Breno Leitao (6):
kho: add size parameter to kho_add_subtree()
kho: rename fdt parameter to blob in kho_add/remove_subtree()
kho: persist blob size in KHO FDT
kho: fix kho_in_debugfs_init() to handle non-FDT blobs
kho: kexec-metadata: track previous kernel chain
kho: document kexec-metadata tracking feature
Documentation/admin-guide/mm/kho.rst | 41 ++++++++-
include/linux/kexec_handover.h | 13 +--
include/linux/kho/abi/kexec_handover.h | 51 ++++++++++-
kernel/liveupdate/kexec_handover.c | 134 +++++++++++++++++++++++-----
kernel/liveupdate/kexec_handover_debugfs.c | 56 +++++++-----
kernel/liveupdate/kexec_handover_internal.h | 15 ++--
kernel/liveupdate/luo_core.c | 5 +-
lib/test_kho.c | 5 +-
mm/memblock.c | 4 +-
9 files changed, 260 insertions(+), 64 deletions(-)
---
base-commit: 3f9cd19e764b782706dbaacc69e502099cb014ba
change-id: 20251230-kho-7707e8a2ef1e
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v8 1/6] kho: add size parameter to kho_add_subtree()
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
@ 2026-03-09 13:41 ` Breno Leitao
2026-03-13 8:50 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 2/6] kho: rename fdt parameter to blob in kho_add/remove_subtree() Breno Leitao
` (5 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
kho_add_subtree() assumes the fdt argument is always an FDT and calls
fdt_totalsize() on it in the debugfs code path. This assumption will
break if a caller passes arbitrary data instead of an FDT.
When CONFIG_KEXEC_HANDOVER_DEBUGFS is enabled, kho_debugfs_fdt_add()
calls __kho_debugfs_fdt_add(), which executes:
f->wrapper.size = fdt_totalsize(fdt);
Fix this by adding an explicit size parameter to kho_add_subtree() so
callers specify the blob size. This allows subtrees to contain
arbitrary data formats, not just FDTs. Update all callers:
- memblock.c: use fdt_totalsize(fdt)
- luo_core.c: use fdt_totalsize(fdt_out)
- test_kho.c: use fdt_totalsize()
- kexec_handover.c (root fdt): use fdt_totalsize(kho_out.fdt)
Also update __kho_debugfs_fdt_add() to receive the size explicitly
instead of computing it internally via fdt_totalsize(). In
kho_in_debugfs_init(), pass fdt_totalsize() for the root FDT and
sub-blobs since all current users are FDTs. A subsequent patch will
persist the size in the KHO FDT so the incoming side can handle
non-FDT blobs correctly.
Suggested-by: Pratyush Yadav <pratyush@kernel.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/kexec_handover.h | 4 ++--
kernel/liveupdate/kexec_handover.c | 8 +++++---
kernel/liveupdate/kexec_handover_debugfs.c | 15 +++++++++------
kernel/liveupdate/kexec_handover_internal.h | 5 +++--
kernel/liveupdate/luo_core.c | 3 ++-
lib/test_kho.c | 3 ++-
mm/memblock.c | 2 +-
7 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index ac4129d1d7416..abb1d324f42d0 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -32,7 +32,7 @@ void kho_restore_free(void *mem);
struct folio *kho_restore_folio(phys_addr_t phys);
struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
-int kho_add_subtree(const char *name, void *fdt);
+int kho_add_subtree(const char *name, void *fdt, size_t size);
void kho_remove_subtree(void *fdt);
int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
@@ -97,7 +97,7 @@ static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
return NULL;
}
-static inline int kho_add_subtree(const char *name, void *fdt)
+static inline int kho_add_subtree(const char *name, void *fdt, size_t size)
{
return -EOPNOTSUPP;
}
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index b64f36a452965..55bdc5d2d1929 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -752,6 +752,7 @@ static void __init kho_reserve_scratch(void)
* kho_add_subtree - record the physical address of a sub FDT in KHO root tree.
* @name: name of the sub tree.
* @fdt: the sub tree blob.
+ * @size: size of the blob in bytes.
*
* Creates a new child node named @name in KHO root FDT and records
* the physical address of @fdt. The pages of @fdt must also be preserved
@@ -763,7 +764,7 @@ static void __init kho_reserve_scratch(void)
*
* Return: 0 on success, error code on failure
*/
-int kho_add_subtree(const char *name, void *fdt)
+int kho_add_subtree(const char *name, void *fdt, size_t size)
{
phys_addr_t phys = virt_to_phys(fdt);
void *root_fdt = kho_out.fdt;
@@ -788,7 +789,7 @@ int kho_add_subtree(const char *name, void *fdt)
if (err < 0)
goto out_pack;
- WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
+ WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, size, false));
out_pack:
fdt_pack(root_fdt);
@@ -1452,7 +1453,8 @@ static __init int kho_init(void)
}
WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, "fdt",
- kho_out.fdt, true));
+ kho_out.fdt,
+ fdt_totalsize(kho_out.fdt), true));
return 0;
diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index 3f395fbd978f4..b5a1e09ca8f14 100644
--- a/kernel/liveupdate/kexec_handover_debugfs.c
+++ b/kernel/liveupdate/kexec_handover_debugfs.c
@@ -24,7 +24,7 @@ struct fdt_debugfs {
};
static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
- const char *name, const void *fdt)
+ const char *name, const void *fdt, size_t size)
{
struct fdt_debugfs *f;
struct dentry *file;
@@ -34,7 +34,7 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
return -ENOMEM;
f->wrapper.data = (void *)fdt;
- f->wrapper.size = fdt_totalsize(fdt);
+ f->wrapper.size = size;
file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
if (IS_ERR(file)) {
@@ -49,7 +49,7 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
}
int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root)
+ const void *fdt, size_t size, bool root)
{
struct dentry *dir;
@@ -58,7 +58,7 @@ int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
else
dir = dbg->sub_fdt_dir;
- return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt);
+ return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt, size);
}
void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt)
@@ -112,7 +112,8 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
goto err_rmdir;
}
- err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt);
+ err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt,
+ fdt_totalsize(fdt));
if (err)
goto err_rmdir;
@@ -120,6 +121,7 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
int len = 0;
const char *name = fdt_get_name(fdt, child, NULL);
const u64 *fdt_phys;
+ void *sub_fdt;
fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
if (!fdt_phys)
@@ -129,8 +131,9 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
name, len);
continue;
}
+ sub_fdt = phys_to_virt(*fdt_phys);
err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
- phys_to_virt(*fdt_phys));
+ sub_fdt, fdt_totalsize(sub_fdt));
if (err) {
pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
ERR_PTR(err));
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 9a832a35254cf..2a28cb8db9b0a 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -27,7 +27,7 @@ int kho_debugfs_init(void);
void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
int kho_out_debugfs_init(struct kho_debugfs *dbg);
int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root);
+ const void *fdt, size_t size, bool root);
void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt);
#else
static inline int kho_debugfs_init(void) { return 0; }
@@ -35,7 +35,8 @@ static inline void kho_in_debugfs_init(struct kho_debugfs *dbg,
const void *fdt) { }
static inline int kho_out_debugfs_init(struct kho_debugfs *dbg) { return 0; }
static inline int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root) { return 0; }
+ const void *fdt, size_t size,
+ bool root) { return 0; }
static inline void kho_debugfs_fdt_remove(struct kho_debugfs *dbg,
void *fdt) { }
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 84ac728d63baa..04d06a0906c0e 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -172,7 +172,8 @@ static int __init luo_fdt_setup(void)
if (err)
goto exit_free;
- err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out);
+ err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
+ fdt_totalsize(fdt_out));
if (err)
goto exit_free;
luo_global.fdt_out = fdt_out;
diff --git a/lib/test_kho.c b/lib/test_kho.c
index 7ef9e40618696..2631824373152 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -143,7 +143,8 @@ static int kho_test_preserve(struct kho_test_state *state)
if (err)
goto err_unpreserve_data;
- err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt));
+ err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt),
+ fdt_totalsize(folio_address(state->fdt)));
if (err)
goto err_unpreserve_data;
diff --git a/mm/memblock.c b/mm/memblock.c
index ae6a5af46bd78..29e12ea2a854c 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2488,7 +2488,7 @@ static int __init prepare_kho_fdt(void)
if (err)
goto err_unpreserve_fdt;
- err = kho_add_subtree(MEMBLOCK_KHO_FDT, fdt);
+ err = kho_add_subtree(MEMBLOCK_KHO_FDT, fdt, fdt_totalsize(fdt));
if (err)
goto err_unpreserve_fdt;
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v8 2/6] kho: rename fdt parameter to blob in kho_add/remove_subtree()
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
2026-03-09 13:41 ` [PATCH v8 1/6] kho: add size parameter to kho_add_subtree() Breno Leitao
@ 2026-03-09 13:41 ` Breno Leitao
2026-03-13 8:52 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 3/6] kho: persist blob size in KHO FDT Breno Leitao
` (4 subsequent siblings)
6 siblings, 1 reply; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
Since kho_add_subtree() now accepts arbitrary data blobs (not just
FDTs), rename the parameter from 'fdt' to 'blob' to better reflect
its purpose. Apply the same rename to kho_remove_subtree() for
consistency.
Also rename kho_debugfs_fdt_add() and kho_debugfs_fdt_remove() to
kho_debugfs_blob_add() and kho_debugfs_blob_remove() respectively,
with the same parameter rename from 'fdt' to 'blob'.
Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
Documentation/admin-guide/mm/kho.rst | 2 +-
include/linux/kexec_handover.h | 8 +++----
kernel/liveupdate/kexec_handover.c | 33 +++++++++++++++--------------
kernel/liveupdate/kexec_handover_debugfs.c | 25 +++++++++++-----------
kernel/liveupdate/kexec_handover_internal.h | 16 +++++++-------
5 files changed, 43 insertions(+), 41 deletions(-)
diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index cb9a20f649206..6a4ddf3440465 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -80,5 +80,5 @@ stabilized.
it finished to interpret their metadata.
``/sys/kernel/debug/kho/in/sub_fdts/``
- Similar to ``kho/out/sub_fdts/``, but contains sub FDT blobs
+ Similar to ``kho/out/sub_fdts/``, but contains sub blobs
of KHO producers passed from the old kernel.
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index abb1d324f42d0..0666cf298c7f4 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -32,8 +32,8 @@ void kho_restore_free(void *mem);
struct folio *kho_restore_folio(phys_addr_t phys);
struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
-int kho_add_subtree(const char *name, void *fdt, size_t size);
-void kho_remove_subtree(void *fdt);
+int kho_add_subtree(const char *name, void *blob, size_t size);
+void kho_remove_subtree(void *blob);
int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
void kho_memory_init(void);
@@ -97,12 +97,12 @@ static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
return NULL;
}
-static inline int kho_add_subtree(const char *name, void *fdt, size_t size)
+static inline int kho_add_subtree(const char *name, void *blob, size_t size)
{
return -EOPNOTSUPP;
}
-static inline void kho_remove_subtree(void *fdt) { }
+static inline void kho_remove_subtree(void *blob) { }
static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
{
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 55bdc5d2d1929..54fe59fe43acd 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -749,13 +749,13 @@ static void __init kho_reserve_scratch(void)
}
/**
- * kho_add_subtree - record the physical address of a sub FDT in KHO root tree.
+ * kho_add_subtree - record the physical address of a sub blob in KHO root tree.
* @name: name of the sub tree.
- * @fdt: the sub tree blob.
+ * @blob: the sub tree blob.
* @size: size of the blob in bytes.
*
* Creates a new child node named @name in KHO root FDT and records
- * the physical address of @fdt. The pages of @fdt must also be preserved
+ * the physical address of @blob. The pages of @blob must also be preserved
* by KHO for the new kernel to retrieve it after kexec.
*
* A debugfs blob entry is also created at
@@ -764,9 +764,9 @@ static void __init kho_reserve_scratch(void)
*
* Return: 0 on success, error code on failure
*/
-int kho_add_subtree(const char *name, void *fdt, size_t size)
+int kho_add_subtree(const char *name, void *blob, size_t size)
{
- phys_addr_t phys = virt_to_phys(fdt);
+ phys_addr_t phys = virt_to_phys(blob);
void *root_fdt = kho_out.fdt;
int err = -ENOMEM;
int off, fdt_err;
@@ -789,7 +789,8 @@ int kho_add_subtree(const char *name, void *fdt, size_t size)
if (err < 0)
goto out_pack;
- WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, size, false));
+ WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
+ size, false));
out_pack:
fdt_pack(root_fdt);
@@ -798,9 +799,9 @@ int kho_add_subtree(const char *name, void *fdt, size_t size)
}
EXPORT_SYMBOL_GPL(kho_add_subtree);
-void kho_remove_subtree(void *fdt)
+void kho_remove_subtree(void *blob)
{
- phys_addr_t target_phys = virt_to_phys(fdt);
+ phys_addr_t target_phys = virt_to_phys(blob);
void *root_fdt = kho_out.fdt;
int off;
int err;
@@ -822,7 +823,7 @@ void kho_remove_subtree(void *fdt)
if ((phys_addr_t)*val == target_phys) {
fdt_del_node(root_fdt, off);
- kho_debugfs_fdt_remove(&kho_out.dbg, fdt);
+ kho_debugfs_blob_remove(&kho_out.dbg, blob);
break;
}
}
@@ -1310,11 +1311,11 @@ bool is_kho_boot(void)
EXPORT_SYMBOL_GPL(is_kho_boot);
/**
- * kho_retrieve_subtree - retrieve a preserved sub FDT by its name.
- * @name: the name of the sub FDT passed to kho_add_subtree().
- * @phys: if found, the physical address of the sub FDT is stored in @phys.
+ * kho_retrieve_subtree - retrieve a preserved sub blob by its name.
+ * @name: the name of the sub blob passed to kho_add_subtree().
+ * @phys: if found, the physical address of the sub blob is stored in @phys.
*
- * Retrieve a preserved sub FDT named @name and store its physical
+ * Retrieve a preserved sub blob named @name and store its physical
* address in @phys.
*
* Return: 0 on success, error code on failure
@@ -1452,9 +1453,9 @@ static __init int kho_init(void)
return 0;
}
- WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, "fdt",
- kho_out.fdt,
- fdt_totalsize(kho_out.fdt), true));
+ WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, "fdt",
+ kho_out.fdt,
+ fdt_totalsize(kho_out.fdt), true));
return 0;
diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index b5a1e09ca8f14..7a100f2bf3947 100644
--- a/kernel/liveupdate/kexec_handover_debugfs.c
+++ b/kernel/liveupdate/kexec_handover_debugfs.c
@@ -23,8 +23,9 @@ struct fdt_debugfs {
struct dentry *file;
};
-static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
- const char *name, const void *fdt, size_t size)
+static int __kho_debugfs_blob_add(struct list_head *list, struct dentry *dir,
+ const char *name, const void *blob,
+ size_t size)
{
struct fdt_debugfs *f;
struct dentry *file;
@@ -33,7 +34,7 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
if (!f)
return -ENOMEM;
- f->wrapper.data = (void *)fdt;
+ f->wrapper.data = (void *)blob;
f->wrapper.size = size;
file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
@@ -48,8 +49,8 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
return 0;
}
-int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, size_t size, bool root)
+int kho_debugfs_blob_add(struct kho_debugfs *dbg, const char *name,
+ const void *blob, size_t size, bool root)
{
struct dentry *dir;
@@ -58,15 +59,15 @@ int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
else
dir = dbg->sub_fdt_dir;
- return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt, size);
+ return __kho_debugfs_blob_add(&dbg->fdt_list, dir, name, blob, size);
}
-void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt)
+void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob)
{
struct fdt_debugfs *ff;
list_for_each_entry(ff, &dbg->fdt_list, list) {
- if (ff->wrapper.data == fdt) {
+ if (ff->wrapper.data == blob) {
debugfs_remove(ff->file);
list_del(&ff->list);
kfree(ff);
@@ -112,8 +113,8 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
goto err_rmdir;
}
- err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt,
- fdt_totalsize(fdt));
+ err = __kho_debugfs_blob_add(&dbg->fdt_list, dir, "fdt", fdt,
+ fdt_totalsize(fdt));
if (err)
goto err_rmdir;
@@ -132,8 +133,8 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
continue;
}
sub_fdt = phys_to_virt(*fdt_phys);
- err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
- sub_fdt, fdt_totalsize(sub_fdt));
+ err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name,
+ sub_fdt, fdt_totalsize(sub_fdt));
if (err) {
pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
ERR_PTR(err));
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 2a28cb8db9b0a..0399ff1077750 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -26,19 +26,19 @@ extern unsigned int kho_scratch_cnt;
int kho_debugfs_init(void);
void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
int kho_out_debugfs_init(struct kho_debugfs *dbg);
-int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, size_t size, bool root);
-void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt);
+int kho_debugfs_blob_add(struct kho_debugfs *dbg, const char *name,
+ const void *blob, size_t size, bool root);
+void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob);
#else
static inline int kho_debugfs_init(void) { return 0; }
static inline void kho_in_debugfs_init(struct kho_debugfs *dbg,
const void *fdt) { }
static inline int kho_out_debugfs_init(struct kho_debugfs *dbg) { return 0; }
-static inline int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, size_t size,
- bool root) { return 0; }
-static inline void kho_debugfs_fdt_remove(struct kho_debugfs *dbg,
- void *fdt) { }
+static inline int kho_debugfs_blob_add(struct kho_debugfs *dbg,
+ const char *name, const void *blob,
+ size_t size, bool root) { return 0; }
+static inline void kho_debugfs_blob_remove(struct kho_debugfs *dbg,
+ void *blob) { }
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v8 3/6] kho: persist blob size in KHO FDT
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
2026-03-09 13:41 ` [PATCH v8 1/6] kho: add size parameter to kho_add_subtree() Breno Leitao
2026-03-09 13:41 ` [PATCH v8 2/6] kho: rename fdt parameter to blob in kho_add/remove_subtree() Breno Leitao
@ 2026-03-09 13:41 ` Breno Leitao
2026-03-10 10:35 ` Mike Rapoport
2026-03-13 9:21 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs Breno Leitao
` (3 subsequent siblings)
6 siblings, 2 replies; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
kho_add_subtree() accepts a size parameter but only forwards it to
debugfs. The size is not persisted in the KHO FDT, so it is lost across
kexec. This makes it impossible for the incoming kernel to determine the
blob size without understanding the blob format.
Store the blob size as a "blob-size" property in the KHO FDT alongside
the "preserved-data" physical address. This allows the receiving kernel
to recover the size for any blob regardless of format.
Also extend kho_retrieve_subtree() with an optional size output
parameter so callers can learn the blob size without needing to
understand the blob format. Update all callers to pass NULL for the
new parameter.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/kexec_handover.h | 5 +++--
include/linux/kho/abi/kexec_handover.h | 20 ++++++++++++++++----
kernel/liveupdate/kexec_handover.c | 26 +++++++++++++++++++++-----
kernel/liveupdate/luo_core.c | 2 +-
lib/test_kho.c | 2 +-
mm/memblock.c | 2 +-
6 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index 0666cf298c7f4..8968c56d2d73e 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -34,7 +34,7 @@ struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
int kho_add_subtree(const char *name, void *blob, size_t size);
void kho_remove_subtree(void *blob);
-int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
+int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
void kho_memory_init(void);
@@ -104,7 +104,8 @@ static inline int kho_add_subtree(const char *name, void *blob, size_t size)
static inline void kho_remove_subtree(void *blob) { }
-static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
+static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,
+ size_t *size)
{
return -EOPNOTSUPP;
}
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 6b7d8ef550f98..7e847a2339b09 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -41,25 +41,28 @@
* restore the preserved data.::
*
* / {
- * compatible = "kho-v2";
+ * compatible = "kho-v3";
*
* preserved-memory-map = <0x...>;
*
* <subnode-name-1> {
* preserved-data = <0x...>;
+ * blob-size = <0x...>;
* };
*
* <subnode-name-2> {
* preserved-data = <0x...>;
+ * blob-size = <0x...>;
* };
* ... ...
* <subnode-name-N> {
* preserved-data = <0x...>;
+ * blob-size = <0x...>;
* };
* };
*
* Root KHO Node (/):
- * - compatible: "kho-v2"
+ * - compatible: "kho-v3"
*
* Indentifies the overall KHO ABI version.
*
@@ -78,16 +81,25 @@
*
* Physical address pointing to a subnode data blob that is also
* being preserved.
+ *
+ * - blob-size: u64
+ *
+ * Size in bytes of the preserved data blob. This is needed because
+ * blobs may use arbitrary formats (not just FDT), so the size
+ * cannot be determined from the blob content alone.
*/
/* The compatible string for the KHO FDT root node. */
-#define KHO_FDT_COMPATIBLE "kho-v2"
+#define KHO_FDT_COMPATIBLE "kho-v3"
/* The FDT property for the preserved memory map. */
#define KHO_FDT_MEMORY_MAP_PROP_NAME "preserved-memory-map"
/* The FDT property for preserved data blobs. */
-#define KHO_FDT_SUB_TREE_PROP_NAME "preserved-data"
+#define KHO_SUB_TREE_PROP_NAME "preserved-data"
+
+/* The FDT property for the size of preserved data blobs. */
+#define KHO_SUB_TREE_SIZE_PROP_NAME "blob-size"
/**
* DOC: Kexec Handover ABI for vmalloc Preservation
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 54fe59fe43acd..1f22705d5d246 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -768,6 +768,7 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
{
phys_addr_t phys = virt_to_phys(blob);
void *root_fdt = kho_out.fdt;
+ u64 size_u64 = size;
int err = -ENOMEM;
int off, fdt_err;
@@ -784,11 +785,16 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
goto out_pack;
}
- err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
+ err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
&phys, sizeof(phys));
if (err < 0)
goto out_pack;
+ err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
+ &size_u64, sizeof(size_u64));
+ if (err < 0)
+ goto out_pack;
+
WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
size, false));
@@ -817,7 +823,7 @@ void kho_remove_subtree(void *blob)
const u64 *val;
int len;
- val = fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+ val = fdt_getprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME, &len);
if (!val || len != sizeof(phys_addr_t))
continue;
@@ -1314,13 +1320,14 @@ EXPORT_SYMBOL_GPL(is_kho_boot);
* kho_retrieve_subtree - retrieve a preserved sub blob by its name.
* @name: the name of the sub blob passed to kho_add_subtree().
* @phys: if found, the physical address of the sub blob is stored in @phys.
+ * @size: if not NULL and found, the size of the sub blob is stored in @size.
*
* Retrieve a preserved sub blob named @name and store its physical
- * address in @phys.
+ * address in @phys and optionally its size in @size.
*
* Return: 0 on success, error code on failure
*/
-int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
+int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
{
const void *fdt = kho_get_fdt();
const u64 *val;
@@ -1336,12 +1343,21 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
if (offset < 0)
return -ENOENT;
- val = fdt_getprop(fdt, offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+ val = fdt_getprop(fdt, offset, KHO_SUB_TREE_PROP_NAME, &len);
if (!val || len != sizeof(*val))
return -EINVAL;
*phys = (phys_addr_t)*val;
+ if (size) {
+ val = fdt_getprop(fdt, offset, KHO_SUB_TREE_SIZE_PROP_NAME,
+ &len);
+ if (val && len == sizeof(*val))
+ *size = (size_t)*val;
+ else
+ *size = 0;
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 04d06a0906c0e..48b25c9abeda3 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -88,7 +88,7 @@ static int __init luo_early_startup(void)
}
/* Retrieve LUO subtree, and verify its format. */
- err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys);
+ err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
if (err) {
if (err != -ENOENT) {
pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
diff --git a/lib/test_kho.c b/lib/test_kho.c
index 2631824373152..aa6a0956bb8b7 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -319,7 +319,7 @@ static int __init kho_test_init(void)
if (!kho_is_enabled())
return 0;
- err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys);
+ err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys, NULL);
if (!err) {
err = kho_test_restore(fdt_phys);
if (err)
diff --git a/mm/memblock.c b/mm/memblock.c
index 29e12ea2a854c..4f4bf1a9d7900 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2533,7 +2533,7 @@ static void *__init reserve_mem_kho_retrieve_fdt(void)
if (fdt)
return fdt;
- err = kho_retrieve_subtree(MEMBLOCK_KHO_FDT, &fdt_phys);
+ err = kho_retrieve_subtree(MEMBLOCK_KHO_FDT, &fdt_phys, NULL);
if (err) {
if (err != -ENOENT)
pr_warn("failed to retrieve FDT '%s' from KHO: %d\n",
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
` (2 preceding siblings ...)
2026-03-09 13:41 ` [PATCH v8 3/6] kho: persist blob size in KHO FDT Breno Leitao
@ 2026-03-09 13:41 ` Breno Leitao
2026-03-10 10:36 ` Mike Rapoport
2026-03-13 9:23 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 5/6] kho: kexec-metadata: track previous kernel chain Breno Leitao
` (2 subsequent siblings)
6 siblings, 2 replies; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
kho_in_debugfs_init() has two problems when displaying incoming
sub-blobs in debugfs:
1. It uses the hardcoded property name "fdt" instead of
KHO_SUB_TREE_PROP_NAME ("preserved-data"), so it never finds
subtrees stored by the current kho_add_subtree().
2. It calls fdt_totalsize() to determine blob sizes, which assumes
all blobs are FDTs. This breaks for non-FDT blobs like struct
kho_kexec_metadata.
Fix both issues by using KHO_SUB_TREE_PROP_NAME to find subtrees
and reading the "blob-size" property from the FDT (persisted by
kho_add_subtree()) instead of calling fdt_totalsize().
Signed-off-by: Breno Leitao <leitao@debian.org>
---
kernel/liveupdate/kexec_handover_debugfs.c | 34 ++++++++++++++++++++----------
1 file changed, 23 insertions(+), 11 deletions(-)
diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index 7a100f2bf3947..257ee8a52be66 100644
--- a/kernel/liveupdate/kexec_handover_debugfs.c
+++ b/kernel/liveupdate/kexec_handover_debugfs.c
@@ -13,6 +13,7 @@
#include <linux/io.h>
#include <linux/libfdt.h>
#include <linux/mm.h>
+#include <linux/kho/abi/kexec_handover.h>
#include "kexec_handover_internal.h"
static struct dentry *debugfs_root;
@@ -121,23 +122,34 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
fdt_for_each_subnode(child, fdt, 0) {
int len = 0;
const char *name = fdt_get_name(fdt, child, NULL);
- const u64 *fdt_phys;
- void *sub_fdt;
+ const u64 *blob_phys;
+ const u64 *blob_size;
+ void *blob;
- fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
- if (!fdt_phys)
+ blob_phys = fdt_getprop(fdt, child,
+ KHO_SUB_TREE_PROP_NAME, &len);
+ if (!blob_phys)
continue;
- if (len != sizeof(*fdt_phys)) {
- pr_warn("node %s prop fdt has invalid length: %d\n",
- name, len);
+ if (len != sizeof(*blob_phys)) {
+ pr_warn("node %s prop %s has invalid length: %d\n",
+ name, KHO_SUB_TREE_PROP_NAME, len);
continue;
}
- sub_fdt = phys_to_virt(*fdt_phys);
+
+ blob_size = fdt_getprop(fdt, child,
+ KHO_SUB_TREE_SIZE_PROP_NAME, &len);
+ if (!blob_size || len != sizeof(*blob_size)) {
+ pr_warn("node %s missing or invalid %s property\n",
+ name, KHO_SUB_TREE_SIZE_PROP_NAME);
+ continue;
+ }
+
+ blob = phys_to_virt(*blob_phys);
err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name,
- sub_fdt, fdt_totalsize(sub_fdt));
+ blob, *blob_size);
if (err) {
- pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
- ERR_PTR(err));
+ pr_warn("failed to add blob %s to debugfs: %pe\n",
+ name, ERR_PTR(err));
continue;
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v8 5/6] kho: kexec-metadata: track previous kernel chain
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
` (3 preceding siblings ...)
2026-03-09 13:41 ` [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs Breno Leitao
@ 2026-03-09 13:41 ` Breno Leitao
2026-03-13 9:33 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 6/6] kho: document kexec-metadata tracking feature Breno Leitao
2026-03-13 10:01 ` [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Pratyush Yadav
6 siblings, 1 reply; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
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 the following commits:
* 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 kexec metadata is stored as a plain C struct (struct kho_kexec_metadata)
rather than FDT format, for simplicity and direct field access. It is
registered via kho_add_subtree() as a separate subtree, keeping it
independent from the core KHO ABI. This design choice:
- Keeps the core KHO ABI minimal and stable
- Allows the metadata format to evolve independently
- Avoids requiring version bumps for all KHO consumers (LUO, etc.)
when the metadata format changes
The struct kho_kexec_metadata contains two fields:
- 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.
Acked-by: SeongJae Park <sj@kernel.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/kho/abi/kexec_handover.h | 31 ++++++++++++++
kernel/liveupdate/kexec_handover.c | 75 ++++++++++++++++++++++++++++++++++
2 files changed, 106 insertions(+)
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 7e847a2339b09..832390f96f49c 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -14,6 +14,7 @@
#include <linux/log2.h>
#include <linux/math.h>
#include <linux/types.h>
+#include <linux/utsname.h>
#include <asm/page.h>
@@ -101,6 +102,36 @@
/* The FDT property for the size of preserved data blobs. */
#define KHO_SUB_TREE_SIZE_PROP_NAME "blob-size"
+/**
+ * DOC: Kexec Metadata ABI
+ *
+ * The "kexec-metadata" subtree stores optional metadata about the kexec chain.
+ * It is registered via kho_add_subtree(), keeping it independent from the core
+ * KHO ABI. This allows the metadata format to evolve without affecting other
+ * KHO consumers.
+ *
+ * The metadata is stored as a plain C struct rather than FDT format for
+ * simplicity and direct field access.
+ */
+
+/**
+ * struct kho_kexec_metadata - Kexec metadata passed between kernels
+ * @previous_release: Kernel version string that initiated the kexec
+ * @kexec_count: Number of kexec boots since last cold boot
+ *
+ * This structure is preserved across kexec and allows the new kernel to
+ * identify which kernel it was booted from and how many kexec reboots
+ * have occurred.
+ *
+ * __NEW_UTS_LEN is part of uABI, so it safe to use it in here.
+ */
+struct kho_kexec_metadata {
+ char previous_release[__NEW_UTS_LEN + 1];
+ u32 kexec_count;
+} __packed;
+
+#define KHO_METADATA_NODE_NAME "kexec-metadata"
+
/**
* DOC: Kexec Handover ABI for vmalloc Preservation
*
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 1f22705d5d246..7bac80e9a29a4 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -18,6 +18,7 @@
#include <linux/kexec.h>
#include <linux/kexec_handover.h>
#include <linux/kho_radix_tree.h>
+#include <linux/utsname.h>
#include <linux/kho/abi/kexec_handover.h>
#include <linux/libfdt.h>
#include <linux/list.h>
@@ -1285,6 +1286,8 @@ EXPORT_SYMBOL_GPL(kho_restore_free);
struct kho_in {
phys_addr_t fdt_phys;
phys_addr_t scratch_phys;
+ char previous_release[__NEW_UTS_LEN + 1];
+ u32 kexec_count;
struct kho_debugfs dbg;
};
@@ -1408,6 +1411,74 @@ static __init int kho_out_fdt_setup(void)
return err;
}
+static void __init kho_in_kexec_metadata(void)
+{
+ struct kho_kexec_metadata *metadata;
+ phys_addr_t metadata_phys;
+ int err;
+
+ err = kho_retrieve_subtree(KHO_METADATA_NODE_NAME, &metadata_phys,
+ NULL);
+ if (err)
+ /* This is fine, previous kernel didn't export metadata */
+ return;
+ metadata = phys_to_virt(metadata_phys);
+
+ /*
+ * Copy data to the kernel structure that will persist during
+ * kernel lifetime.
+ */
+ kho_in.kexec_count = metadata->kexec_count;
+ strscpy(kho_in.previous_release, metadata->previous_release,
+ sizeof(kho_in.previous_release));
+
+ pr_info("exec from: %s (count %u)\n", kho_in.previous_release,
+ kho_in.kexec_count);
+}
+
+/*
+ * Create kexec metadata to pass kernel version and boot count to the
+ * next kernel. This keeps the core KHO ABI minimal and allows the
+ * metadata format to evolve independently.
+ */
+static __init int kho_out_kexec_metadata(void)
+{
+ struct kho_kexec_metadata *metadata;
+ int err;
+
+ metadata = kho_alloc_preserve(sizeof(*metadata));
+ if (IS_ERR(metadata))
+ return PTR_ERR(metadata);
+
+ strscpy(metadata->previous_release, init_uts_ns.name.release,
+ sizeof(metadata->previous_release));
+ /* kho_in.kexec_count is set to 0 on cold boot */
+ metadata->kexec_count = kho_in.kexec_count + 1;
+
+ err = kho_add_subtree(KHO_METADATA_NODE_NAME, metadata,
+ sizeof(*metadata));
+ if (err)
+ kho_unpreserve_free(metadata);
+
+ return err;
+}
+
+static int __init kho_kexec_metadata_init(const void *fdt)
+{
+ int err;
+
+ if (fdt)
+ kho_in_kexec_metadata();
+
+ /* Populate kexec metadata for the possible next kexec */
+ err = kho_out_kexec_metadata();
+ if (err)
+ pr_warn("failed to initialize kexec-metadata subtree: %d\n",
+ err);
+
+ return err;
+}
+
static __init int kho_init(void)
{
struct kho_radix_tree *tree = &kho_out.radix_tree;
@@ -1441,6 +1512,10 @@ static __init int kho_init(void)
if (err)
goto err_free_fdt;
+ err = kho_kexec_metadata_init(fdt);
+ if (err)
+ goto err_free_fdt;
+
for (int i = 0; i < kho_scratch_cnt; i++) {
unsigned long base_pfn = PHYS_PFN(kho_scratch[i].addr);
unsigned long count = kho_scratch[i].size >> PAGE_SHIFT;
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH v8 6/6] kho: document kexec-metadata tracking feature
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
` (4 preceding siblings ...)
2026-03-09 13:41 ` [PATCH v8 5/6] kho: kexec-metadata: track previous kernel chain Breno Leitao
@ 2026-03-09 13:41 ` Breno Leitao
2026-03-13 9:34 ` Pratyush Yadav
2026-03-13 10:01 ` [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Pratyush Yadav
6 siblings, 1 reply; 19+ messages in thread
From: Breno Leitao @ 2026-03-09 13:41 UTC (permalink / raw)
To: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav
Cc: linux-kernel, kexec, linux-mm, usamaarif642, Breno Leitao,
SeongJae Park, kernel-team
Add documentation for the kexec-metadata feature that tracks the
previous kernel version and kexec boot count across kexec reboots.
This helps diagnose bugs that only reproduce when kexecing from
specific kernel versions.
Suggested-by: Mike Rapoport <rppt@kernel.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Documentation/admin-guide/mm/kho.rst | 39 ++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index 6a4ddf3440465..2c26e560bd78a 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -42,6 +42,45 @@ For example, if you used ``reserve_mem`` command line parameter to create
an early memory reservation, the new kernel will have that memory at the
same physical address as the old kernel.
+Kexec Metadata
+==============
+
+KHO automatically tracks metadata about the kexec chain, passing information
+about the previous kernel to the next kernel. This feature helps diagnose
+bugs that only reproduce when kexecing from specific kernel versions.
+
+On each KHO kexec, the kernel logs the previous kernel's version and the
+number of kexec reboots since the last cold boot::
+
+ [ 0.000000] KHO: exec from: 6.19.0-rc4-next-20260107 (count 1)
+
+The metadata includes:
+
+``previous_release``
+ The kernel version string (from ``uname -r``) of the kernel that
+ initiated the kexec.
+
+``kexec_count``
+ The number of kexec boots since the last cold boot. On cold boot,
+ this counter starts at 0 and increments with each kexec. This helps
+ identify issues that only manifest after multiple consecutive kexec
+ reboots.
+
+Use Cases
+---------
+
+This metadata is particularly useful for debugging kexec transition bugs,
+where a buggy kernel kexecs into a new kernel and the bug manifests only
+in the second kernel. Examples of such bugs include:
+
+- Memory corruption from the previous kernel affecting the new kernel
+- Incorrect hardware state left by the previous kernel
+- Firmware/ACPI state issues that only appear in kexec scenarios
+
+At scale, correlating crashes to the previous kernel version enables
+faster root cause analysis when issues only occur in specific kernel
+transition scenarios.
+
debugfs Interfaces
==================
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH v8 3/6] kho: persist blob size in KHO FDT
2026-03-09 13:41 ` [PATCH v8 3/6] kho: persist blob size in KHO FDT Breno Leitao
@ 2026-03-10 10:35 ` Mike Rapoport
2026-03-13 9:21 ` Pratyush Yadav
1 sibling, 0 replies; 19+ messages in thread
From: Mike Rapoport @ 2026-03-10 10:35 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Pasha Tatashin, Pratyush Yadav, linux-kernel,
kexec, linux-mm, usamaarif642, SeongJae Park, kernel-team
On Mon, Mar 09, 2026 at 06:41:46AM -0700, Breno Leitao wrote:
> kho_add_subtree() accepts a size parameter but only forwards it to
> debugfs. The size is not persisted in the KHO FDT, so it is lost across
> kexec. This makes it impossible for the incoming kernel to determine the
> blob size without understanding the blob format.
>
> Store the blob size as a "blob-size" property in the KHO FDT alongside
> the "preserved-data" physical address. This allows the receiving kernel
> to recover the size for any blob regardless of format.
>
> Also extend kho_retrieve_subtree() with an optional size output
> parameter so callers can learn the blob size without needing to
> understand the blob format. Update all callers to pass NULL for the
> new parameter.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> include/linux/kexec_handover.h | 5 +++--
> include/linux/kho/abi/kexec_handover.h | 20 ++++++++++++++++----
> kernel/liveupdate/kexec_handover.c | 26 +++++++++++++++++++++-----
> kernel/liveupdate/luo_core.c | 2 +-
> lib/test_kho.c | 2 +-
> mm/memblock.c | 2 +-
> 6 files changed, 43 insertions(+), 14 deletions(-)
>
> diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
> index 0666cf298c7f4..8968c56d2d73e 100644
> --- a/include/linux/kexec_handover.h
> +++ b/include/linux/kexec_handover.h
> @@ -34,7 +34,7 @@ struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
> void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
> int kho_add_subtree(const char *name, void *blob, size_t size);
> void kho_remove_subtree(void *blob);
> -int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
> +int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size);
>
> void kho_memory_init(void);
>
> @@ -104,7 +104,8 @@ static inline int kho_add_subtree(const char *name, void *blob, size_t size)
>
> static inline void kho_remove_subtree(void *blob) { }
>
> -static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> +static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys,
> + size_t *size)
> {
> return -EOPNOTSUPP;
> }
> diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
> index 6b7d8ef550f98..7e847a2339b09 100644
> --- a/include/linux/kho/abi/kexec_handover.h
> +++ b/include/linux/kho/abi/kexec_handover.h
> @@ -41,25 +41,28 @@
> * restore the preserved data.::
> *
> * / {
> - * compatible = "kho-v2";
> + * compatible = "kho-v3";
> *
> * preserved-memory-map = <0x...>;
> *
> * <subnode-name-1> {
> * preserved-data = <0x...>;
> + * blob-size = <0x...>;
> * };
> *
> * <subnode-name-2> {
> * preserved-data = <0x...>;
> + * blob-size = <0x...>;
> * };
> * ... ...
> * <subnode-name-N> {
> * preserved-data = <0x...>;
> + * blob-size = <0x...>;
> * };
> * };
> *
> * Root KHO Node (/):
> - * - compatible: "kho-v2"
> + * - compatible: "kho-v3"
> *
> * Indentifies the overall KHO ABI version.
> *
> @@ -78,16 +81,25 @@
> *
> * Physical address pointing to a subnode data blob that is also
> * being preserved.
> + *
> + * - blob-size: u64
> + *
> + * Size in bytes of the preserved data blob. This is needed because
> + * blobs may use arbitrary formats (not just FDT), so the size
> + * cannot be determined from the blob content alone.
> */
>
> /* The compatible string for the KHO FDT root node. */
> -#define KHO_FDT_COMPATIBLE "kho-v2"
> +#define KHO_FDT_COMPATIBLE "kho-v3"
>
> /* The FDT property for the preserved memory map. */
> #define KHO_FDT_MEMORY_MAP_PROP_NAME "preserved-memory-map"
>
> /* The FDT property for preserved data blobs. */
> -#define KHO_FDT_SUB_TREE_PROP_NAME "preserved-data"
> +#define KHO_SUB_TREE_PROP_NAME "preserved-data"
> +
> +/* The FDT property for the size of preserved data blobs. */
> +#define KHO_SUB_TREE_SIZE_PROP_NAME "blob-size"
>
> /**
> * DOC: Kexec Handover ABI for vmalloc Preservation
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 54fe59fe43acd..1f22705d5d246 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -768,6 +768,7 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
> {
> phys_addr_t phys = virt_to_phys(blob);
> void *root_fdt = kho_out.fdt;
> + u64 size_u64 = size;
> int err = -ENOMEM;
> int off, fdt_err;
>
> @@ -784,11 +785,16 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
> goto out_pack;
> }
>
> - err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
> + err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
> &phys, sizeof(phys));
> if (err < 0)
> goto out_pack;
>
> + err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
> + &size_u64, sizeof(size_u64));
> + if (err < 0)
> + goto out_pack;
> +
> WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
> size, false));
>
> @@ -817,7 +823,7 @@ void kho_remove_subtree(void *blob)
> const u64 *val;
> int len;
>
> - val = fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> + val = fdt_getprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME, &len);
> if (!val || len != sizeof(phys_addr_t))
> continue;
>
> @@ -1314,13 +1320,14 @@ EXPORT_SYMBOL_GPL(is_kho_boot);
> * kho_retrieve_subtree - retrieve a preserved sub blob by its name.
> * @name: the name of the sub blob passed to kho_add_subtree().
> * @phys: if found, the physical address of the sub blob is stored in @phys.
> + * @size: if not NULL and found, the size of the sub blob is stored in @size.
> *
> * Retrieve a preserved sub blob named @name and store its physical
> - * address in @phys.
> + * address in @phys and optionally its size in @size.
> *
> * Return: 0 on success, error code on failure
> */
> -int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> +int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
> {
> const void *fdt = kho_get_fdt();
> const u64 *val;
> @@ -1336,12 +1343,21 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> if (offset < 0)
> return -ENOENT;
>
> - val = fdt_getprop(fdt, offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> + val = fdt_getprop(fdt, offset, KHO_SUB_TREE_PROP_NAME, &len);
> if (!val || len != sizeof(*val))
> return -EINVAL;
>
> *phys = (phys_addr_t)*val;
>
> + if (size) {
> + val = fdt_getprop(fdt, offset, KHO_SUB_TREE_SIZE_PROP_NAME,
> + &len);
> + if (val && len == sizeof(*val))
> + *size = (size_t)*val;
> + else
> + *size = 0;
> + }
> +
> return 0;
> }
> EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
> diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
> index 04d06a0906c0e..48b25c9abeda3 100644
> --- a/kernel/liveupdate/luo_core.c
> +++ b/kernel/liveupdate/luo_core.c
> @@ -88,7 +88,7 @@ static int __init luo_early_startup(void)
> }
>
> /* Retrieve LUO subtree, and verify its format. */
> - err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys);
> + err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL);
> if (err) {
> if (err != -ENOENT) {
> pr_err("failed to retrieve FDT '%s' from KHO: %pe\n",
> diff --git a/lib/test_kho.c b/lib/test_kho.c
> index 2631824373152..aa6a0956bb8b7 100644
> --- a/lib/test_kho.c
> +++ b/lib/test_kho.c
> @@ -319,7 +319,7 @@ static int __init kho_test_init(void)
> if (!kho_is_enabled())
> return 0;
>
> - err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys);
> + err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys, NULL);
> if (!err) {
> err = kho_test_restore(fdt_phys);
> if (err)
> diff --git a/mm/memblock.c b/mm/memblock.c
> index 29e12ea2a854c..4f4bf1a9d7900 100644
> --- a/mm/memblock.c
> +++ b/mm/memblock.c
> @@ -2533,7 +2533,7 @@ static void *__init reserve_mem_kho_retrieve_fdt(void)
> if (fdt)
> return fdt;
>
> - err = kho_retrieve_subtree(MEMBLOCK_KHO_FDT, &fdt_phys);
> + err = kho_retrieve_subtree(MEMBLOCK_KHO_FDT, &fdt_phys, NULL);
> if (err) {
> if (err != -ENOENT)
> pr_warn("failed to retrieve FDT '%s' from KHO: %d\n",
>
> --
> 2.47.3
>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs
2026-03-09 13:41 ` [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs Breno Leitao
@ 2026-03-10 10:36 ` Mike Rapoport
2026-03-12 11:11 ` Breno Leitao
2026-03-13 9:23 ` Pratyush Yadav
1 sibling, 1 reply; 19+ messages in thread
From: Mike Rapoport @ 2026-03-10 10:36 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Pasha Tatashin, Pratyush Yadav, linux-kernel,
kexec, linux-mm, usamaarif642, SeongJae Park, kernel-team
On Mon, Mar 09, 2026 at 06:41:47AM -0700, Breno Leitao wrote:
> kho_in_debugfs_init() has two problems when displaying incoming
> sub-blobs in debugfs:
>
> 1. It uses the hardcoded property name "fdt" instead of
> KHO_SUB_TREE_PROP_NAME ("preserved-data"), so it never finds
> subtrees stored by the current kho_add_subtree().
>
> 2. It calls fdt_totalsize() to determine blob sizes, which assumes
> all blobs are FDTs. This breaks for non-FDT blobs like struct
> kho_kexec_metadata.
>
> Fix both issues by using KHO_SUB_TREE_PROP_NAME to find subtrees
> and reading the "blob-size" property from the FDT (persisted by
> kho_add_subtree()) instead of calling fdt_totalsize().
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> kernel/liveupdate/kexec_handover_debugfs.c | 34 ++++++++++++++++++++----------
> 1 file changed, 23 insertions(+), 11 deletions(-)
>
> diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
> index 7a100f2bf3947..257ee8a52be66 100644
> --- a/kernel/liveupdate/kexec_handover_debugfs.c
> +++ b/kernel/liveupdate/kexec_handover_debugfs.c
> @@ -13,6 +13,7 @@
> #include <linux/io.h>
> #include <linux/libfdt.h>
> #include <linux/mm.h>
> +#include <linux/kho/abi/kexec_handover.h>
> #include "kexec_handover_internal.h"
>
> static struct dentry *debugfs_root;
> @@ -121,23 +122,34 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
> fdt_for_each_subnode(child, fdt, 0) {
> int len = 0;
> const char *name = fdt_get_name(fdt, child, NULL);
> - const u64 *fdt_phys;
> - void *sub_fdt;
> + const u64 *blob_phys;
> + const u64 *blob_size;
> + void *blob;
>
> - fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
> - if (!fdt_phys)
> + blob_phys = fdt_getprop(fdt, child,
> + KHO_SUB_TREE_PROP_NAME, &len);
> + if (!blob_phys)
> continue;
> - if (len != sizeof(*fdt_phys)) {
> - pr_warn("node %s prop fdt has invalid length: %d\n",
> - name, len);
> + if (len != sizeof(*blob_phys)) {
> + pr_warn("node %s prop %s has invalid length: %d\n",
> + name, KHO_SUB_TREE_PROP_NAME, len);
> continue;
> }
> - sub_fdt = phys_to_virt(*fdt_phys);
> +
> + blob_size = fdt_getprop(fdt, child,
> + KHO_SUB_TREE_SIZE_PROP_NAME, &len);
> + if (!blob_size || len != sizeof(*blob_size)) {
> + pr_warn("node %s missing or invalid %s property\n",
> + name, KHO_SUB_TREE_SIZE_PROP_NAME);
> + continue;
> + }
> +
> + blob = phys_to_virt(*blob_phys);
> err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name,
> - sub_fdt, fdt_totalsize(sub_fdt));
> + blob, *blob_size);
> if (err) {
> - pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
> - ERR_PTR(err));
> + pr_warn("failed to add blob %s to debugfs: %pe\n",
> + name, ERR_PTR(err));
> continue;
> }
> }
>
> --
> 2.47.3
>
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs
2026-03-10 10:36 ` Mike Rapoport
@ 2026-03-12 11:11 ` Breno Leitao
2026-03-12 16:17 ` Mike Rapoport
0 siblings, 1 reply; 19+ messages in thread
From: Breno Leitao @ 2026-03-12 11:11 UTC (permalink / raw)
To: Mike Rapoport
Cc: Alexander Graf, Pasha Tatashin, Pratyush Yadav, linux-kernel,
kexec, linux-mm, usamaarif642, SeongJae Park, kernel-team
Hello Mike,
On Tue, Mar 10, 2026 at 12:36:40PM +0200, Mike Rapoport wrote:
> On Mon, Mar 09, 2026 at 06:41:47AM -0700, Breno Leitao wrote:
> > kho_in_debugfs_init() has two problems when displaying incoming
> > sub-blobs in debugfs:
> >
> > 1. It uses the hardcoded property name "fdt" instead of
> > KHO_SUB_TREE_PROP_NAME ("preserved-data"), so it never finds
> > subtrees stored by the current kho_add_subtree().
> >
> > 2. It calls fdt_totalsize() to determine blob sizes, which assumes
> > all blobs are FDTs. This breaks for non-FDT blobs like struct
> > kho_kexec_metadata.
> >
> > Fix both issues by using KHO_SUB_TREE_PROP_NAME to find subtrees
> > and reading the "blob-size" property from the FDT (persisted by
> > kho_add_subtree()) instead of calling fdt_totalsize().
> >
> > Signed-off-by: Breno Leitao <leitao@debian.org>
>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Thank you for the review.
As this is my first contribution to KHO, could you please let me know
which tree these patches typically go through?
Thanks,
--breno
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs
2026-03-12 11:11 ` Breno Leitao
@ 2026-03-12 16:17 ` Mike Rapoport
0 siblings, 0 replies; 19+ messages in thread
From: Mike Rapoport @ 2026-03-12 16:17 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Pasha Tatashin, Pratyush Yadav, linux-kernel,
kexec, linux-mm, usamaarif642, SeongJae Park, kernel-team
Hi Breno,
On Thu, Mar 12, 2026 at 04:11:54AM -0700, Breno Leitao wrote:
> Hello Mike,
>
> As this is my first contribution to KHO, could you please let me know
> which tree these patches typically go through?
KHO patches go via mm tree.
> Thanks,
> --breno
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 1/6] kho: add size parameter to kho_add_subtree()
2026-03-09 13:41 ` [PATCH v8 1/6] kho: add size parameter to kho_add_subtree() Breno Leitao
@ 2026-03-13 8:50 ` Pratyush Yadav
0 siblings, 0 replies; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 8:50 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
On Mon, Mar 09 2026, Breno Leitao wrote:
> kho_add_subtree() assumes the fdt argument is always an FDT and calls
> fdt_totalsize() on it in the debugfs code path. This assumption will
> break if a caller passes arbitrary data instead of an FDT.
>
> When CONFIG_KEXEC_HANDOVER_DEBUGFS is enabled, kho_debugfs_fdt_add()
> calls __kho_debugfs_fdt_add(), which executes:
>
> f->wrapper.size = fdt_totalsize(fdt);
>
> Fix this by adding an explicit size parameter to kho_add_subtree() so
> callers specify the blob size. This allows subtrees to contain
> arbitrary data formats, not just FDTs. Update all callers:
>
> - memblock.c: use fdt_totalsize(fdt)
> - luo_core.c: use fdt_totalsize(fdt_out)
> - test_kho.c: use fdt_totalsize()
> - kexec_handover.c (root fdt): use fdt_totalsize(kho_out.fdt)
>
> Also update __kho_debugfs_fdt_add() to receive the size explicitly
> instead of computing it internally via fdt_totalsize(). In
> kho_in_debugfs_init(), pass fdt_totalsize() for the root FDT and
> sub-blobs since all current users are FDTs. A subsequent patch will
> persist the size in the KHO FDT so the incoming side can handle
> non-FDT blobs correctly.
>
> Suggested-by: Pratyush Yadav <pratyush@kernel.org>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 2/6] kho: rename fdt parameter to blob in kho_add/remove_subtree()
2026-03-09 13:41 ` [PATCH v8 2/6] kho: rename fdt parameter to blob in kho_add/remove_subtree() Breno Leitao
@ 2026-03-13 8:52 ` Pratyush Yadav
0 siblings, 0 replies; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 8:52 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
On Mon, Mar 09 2026, Breno Leitao wrote:
> Since kho_add_subtree() now accepts arbitrary data blobs (not just
> FDTs), rename the parameter from 'fdt' to 'blob' to better reflect
> its purpose. Apply the same rename to kho_remove_subtree() for
> consistency.
>
> Also rename kho_debugfs_fdt_add() and kho_debugfs_fdt_remove() to
> kho_debugfs_blob_add() and kho_debugfs_blob_remove() respectively,
> with the same parameter rename from 'fdt' to 'blob'.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 3/6] kho: persist blob size in KHO FDT
2026-03-09 13:41 ` [PATCH v8 3/6] kho: persist blob size in KHO FDT Breno Leitao
2026-03-10 10:35 ` Mike Rapoport
@ 2026-03-13 9:21 ` Pratyush Yadav
2026-03-16 11:09 ` Breno Leitao
1 sibling, 1 reply; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 9:21 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
On Mon, Mar 09 2026, Breno Leitao wrote:
> kho_add_subtree() accepts a size parameter but only forwards it to
> debugfs. The size is not persisted in the KHO FDT, so it is lost across
> kexec. This makes it impossible for the incoming kernel to determine the
> blob size without understanding the blob format.
>
> Store the blob size as a "blob-size" property in the KHO FDT alongside
> the "preserved-data" physical address. This allows the receiving kernel
> to recover the size for any blob regardless of format.
>
> Also extend kho_retrieve_subtree() with an optional size output
> parameter so callers can learn the blob size without needing to
> understand the blob format. Update all callers to pass NULL for the
> new parameter.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
[...]
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 54fe59fe43acd..1f22705d5d246 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -768,6 +768,7 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
> {
> phys_addr_t phys = virt_to_phys(blob);
> void *root_fdt = kho_out.fdt;
> + u64 size_u64 = size;
> int err = -ENOMEM;
> int off, fdt_err;
>
> @@ -784,11 +785,16 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
> goto out_pack;
> }
>
> - err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
> + err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
> &phys, sizeof(phys));
> if (err < 0)
> goto out_pack;
>
> + err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
> + &size_u64, sizeof(size_u64));
> + if (err < 0)
> + goto out_pack;
> +
I noticed that the error handling here is a bit broken. We open the
subnode for the subtree, but then if we fail to add the "preserved-data"
property, we don't remove the subnode. So the next kernel gets an
invalid FDT (per KHO ABI) and might as well refuse to parse it.
Similarly here, the FDT might also be missing the size and then the next
kernel might reject the FDT.
Also, we directly return the FDT error code to the caller, which
wouldn't make sense since it probably expects -errno.
Not something this patchset has to fix, but I am pointing this out in
case someone (possibly also future me) is interested in fixing this up.
> WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
> size, false));
>
> @@ -817,7 +823,7 @@ void kho_remove_subtree(void *blob)
> const u64 *val;
> int len;
>
> - val = fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> + val = fdt_getprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME, &len);
> if (!val || len != sizeof(phys_addr_t))
> continue;
>
> @@ -1314,13 +1320,14 @@ EXPORT_SYMBOL_GPL(is_kho_boot);
> * kho_retrieve_subtree - retrieve a preserved sub blob by its name.
> * @name: the name of the sub blob passed to kho_add_subtree().
> * @phys: if found, the physical address of the sub blob is stored in @phys.
> + * @size: if not NULL and found, the size of the sub blob is stored in @size.
> *
> * Retrieve a preserved sub blob named @name and store its physical
> - * address in @phys.
> + * address in @phys and optionally its size in @size.
> *
> * Return: 0 on success, error code on failure
> */
> -int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> +int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
> {
> const void *fdt = kho_get_fdt();
> const u64 *val;
> @@ -1336,12 +1343,21 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> if (offset < 0)
> return -ENOENT;
>
> - val = fdt_getprop(fdt, offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> + val = fdt_getprop(fdt, offset, KHO_SUB_TREE_PROP_NAME, &len);
> if (!val || len != sizeof(*val))
> return -EINVAL;
>
> *phys = (phys_addr_t)*val;
>
> + if (size) {
> + val = fdt_getprop(fdt, offset, KHO_SUB_TREE_SIZE_PROP_NAME,
> + &len);
> + if (val && len == sizeof(*val))
> + *size = (size_t)*val;
> + else
> + *size = 0;
If the size property is invalid, is it a good idea to ignore it? Should
we instead consider the subnode to be broken and reject it entirely with
an error message? Because if a caller expects a blob of 16 bytes but
gets one with 0 bytes, it will likely error out anyway.
> + }
> +
> return 0;
> }
> EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs
2026-03-09 13:41 ` [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs Breno Leitao
2026-03-10 10:36 ` Mike Rapoport
@ 2026-03-13 9:23 ` Pratyush Yadav
1 sibling, 0 replies; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 9:23 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
On Mon, Mar 09 2026, Breno Leitao wrote:
> kho_in_debugfs_init() has two problems when displaying incoming
> sub-blobs in debugfs:
>
> 1. It uses the hardcoded property name "fdt" instead of
> KHO_SUB_TREE_PROP_NAME ("preserved-data"), so it never finds
> subtrees stored by the current kho_add_subtree().
>
> 2. It calls fdt_totalsize() to determine blob sizes, which assumes
> all blobs are FDTs. This breaks for non-FDT blobs like struct
> kho_kexec_metadata.
>
> Fix both issues by using KHO_SUB_TREE_PROP_NAME to find subtrees
> and reading the "blob-size" property from the FDT (persisted by
> kho_add_subtree()) instead of calling fdt_totalsize().
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 5/6] kho: kexec-metadata: track previous kernel chain
2026-03-09 13:41 ` [PATCH v8 5/6] kho: kexec-metadata: track previous kernel chain Breno Leitao
@ 2026-03-13 9:33 ` Pratyush Yadav
0 siblings, 0 replies; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 9:33 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
On Mon, Mar 09 2026, Breno Leitao wrote:
> 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 the following commits:
>
> * 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 kexec metadata is stored as a plain C struct (struct kho_kexec_metadata)
> rather than FDT format, for simplicity and direct field access. It is
> registered via kho_add_subtree() as a separate subtree, keeping it
> independent from the core KHO ABI. This design choice:
>
> - Keeps the core KHO ABI minimal and stable
> - Allows the metadata format to evolve independently
> - Avoids requiring version bumps for all KHO consumers (LUO, etc.)
> when the metadata format changes
>
> The struct kho_kexec_metadata contains two fields:
> - 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.
>
> Acked-by: SeongJae Park <sj@kernel.org>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> include/linux/kho/abi/kexec_handover.h | 31 ++++++++++++++
> kernel/liveupdate/kexec_handover.c | 75 ++++++++++++++++++++++++++++++++++
> 2 files changed, 106 insertions(+)
>
> diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
> index 7e847a2339b09..832390f96f49c 100644
> --- a/include/linux/kho/abi/kexec_handover.h
> +++ b/include/linux/kho/abi/kexec_handover.h
> @@ -14,6 +14,7 @@
> #include <linux/log2.h>
> #include <linux/math.h>
> #include <linux/types.h>
> +#include <linux/utsname.h>
>
> #include <asm/page.h>
>
> @@ -101,6 +102,36 @@
> /* The FDT property for the size of preserved data blobs. */
> #define KHO_SUB_TREE_SIZE_PROP_NAME "blob-size"
>
> +/**
> + * DOC: Kexec Metadata ABI
> + *
> + * The "kexec-metadata" subtree stores optional metadata about the kexec chain.
> + * It is registered via kho_add_subtree(), keeping it independent from the core
> + * KHO ABI. This allows the metadata format to evolve without affecting other
> + * KHO consumers.
> + *
> + * The metadata is stored as a plain C struct rather than FDT format for
> + * simplicity and direct field access.
> + */
> +
> +/**
> + * struct kho_kexec_metadata - Kexec metadata passed between kernels
> + * @previous_release: Kernel version string that initiated the kexec
> + * @kexec_count: Number of kexec boots since last cold boot
> + *
> + * This structure is preserved across kexec and allows the new kernel to
> + * identify which kernel it was booted from and how many kexec reboots
> + * have occurred.
> + *
> + * __NEW_UTS_LEN is part of uABI, so it safe to use it in here.
> + */
> +struct kho_kexec_metadata {
You need to have a version field here, as the first 4 or 8 bytes. And
you need to check it before parsing anything else in the struct.
Otherwise there will be no way to extend this struct since there won't
be a way to find out if the kernel can read the new format. You probably
should also check the size of the blob on retrieve to make sure it is
large enough to at least contain the version.
Also, since this follows ABI version independent of KHO, please move it
to a separate header, perhaps kexec_metadata.h. Right now we assume that
everything in this file is tied to the base KHO version.
Other than this, LGTM.
> + char previous_release[__NEW_UTS_LEN + 1];
> + u32 kexec_count;
> +} __packed;
> +
> +#define KHO_METADATA_NODE_NAME "kexec-metadata"
> +
> /**
> * DOC: Kexec Handover ABI for vmalloc Preservation
> *
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 1f22705d5d246..7bac80e9a29a4 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -18,6 +18,7 @@
> #include <linux/kexec.h>
> #include <linux/kexec_handover.h>
> #include <linux/kho_radix_tree.h>
> +#include <linux/utsname.h>
> #include <linux/kho/abi/kexec_handover.h>
> #include <linux/libfdt.h>
> #include <linux/list.h>
> @@ -1285,6 +1286,8 @@ EXPORT_SYMBOL_GPL(kho_restore_free);
> struct kho_in {
> phys_addr_t fdt_phys;
> phys_addr_t scratch_phys;
> + char previous_release[__NEW_UTS_LEN + 1];
> + u32 kexec_count;
> struct kho_debugfs dbg;
> };
>
> @@ -1408,6 +1411,74 @@ static __init int kho_out_fdt_setup(void)
> return err;
> }
>
> +static void __init kho_in_kexec_metadata(void)
> +{
> + struct kho_kexec_metadata *metadata;
> + phys_addr_t metadata_phys;
> + int err;
> +
> + err = kho_retrieve_subtree(KHO_METADATA_NODE_NAME, &metadata_phys,
> + NULL);
> + if (err)
> + /* This is fine, previous kernel didn't export metadata */
> + return;
> + metadata = phys_to_virt(metadata_phys);
> +
> + /*
> + * Copy data to the kernel structure that will persist during
> + * kernel lifetime.
> + */
> + kho_in.kexec_count = metadata->kexec_count;
> + strscpy(kho_in.previous_release, metadata->previous_release,
> + sizeof(kho_in.previous_release));
> +
> + pr_info("exec from: %s (count %u)\n", kho_in.previous_release,
> + kho_in.kexec_count);
> +}
> +
> +/*
> + * Create kexec metadata to pass kernel version and boot count to the
> + * next kernel. This keeps the core KHO ABI minimal and allows the
> + * metadata format to evolve independently.
> + */
> +static __init int kho_out_kexec_metadata(void)
> +{
> + struct kho_kexec_metadata *metadata;
> + int err;
> +
> + metadata = kho_alloc_preserve(sizeof(*metadata));
> + if (IS_ERR(metadata))
> + return PTR_ERR(metadata);
> +
> + strscpy(metadata->previous_release, init_uts_ns.name.release,
> + sizeof(metadata->previous_release));
> + /* kho_in.kexec_count is set to 0 on cold boot */
> + metadata->kexec_count = kho_in.kexec_count + 1;
> +
> + err = kho_add_subtree(KHO_METADATA_NODE_NAME, metadata,
> + sizeof(*metadata));
> + if (err)
> + kho_unpreserve_free(metadata);
> +
> + return err;
> +}
> +
> +static int __init kho_kexec_metadata_init(const void *fdt)
> +{
> + int err;
> +
> + if (fdt)
> + kho_in_kexec_metadata();
> +
> + /* Populate kexec metadata for the possible next kexec */
> + err = kho_out_kexec_metadata();
> + if (err)
> + pr_warn("failed to initialize kexec-metadata subtree: %d\n",
> + err);
> +
> + return err;
> +}
> +
> static __init int kho_init(void)
> {
> struct kho_radix_tree *tree = &kho_out.radix_tree;
> @@ -1441,6 +1512,10 @@ static __init int kho_init(void)
> if (err)
> goto err_free_fdt;
>
> + err = kho_kexec_metadata_init(fdt);
> + if (err)
> + goto err_free_fdt;
> +
> for (int i = 0; i < kho_scratch_cnt; i++) {
> unsigned long base_pfn = PHYS_PFN(kho_scratch[i].addr);
> unsigned long count = kho_scratch[i].size >> PAGE_SHIFT;
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 6/6] kho: document kexec-metadata tracking feature
2026-03-09 13:41 ` [PATCH v8 6/6] kho: document kexec-metadata tracking feature Breno Leitao
@ 2026-03-13 9:34 ` Pratyush Yadav
0 siblings, 0 replies; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 9:34 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
On Mon, Mar 09 2026, Breno Leitao wrote:
> Add documentation for the kexec-metadata feature that tracks the
> previous kernel version and kexec boot count across kexec reboots.
> This helps diagnose bugs that only reproduce when kexecing from
> specific kernel versions.
>
> Suggested-by: Mike Rapoport <rppt@kernel.org>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
` (5 preceding siblings ...)
2026-03-09 13:41 ` [PATCH v8 6/6] kho: document kexec-metadata tracking feature Breno Leitao
@ 2026-03-13 10:01 ` Pratyush Yadav
6 siblings, 0 replies; 19+ messages in thread
From: Pratyush Yadav @ 2026-03-13 10:01 UTC (permalink / raw)
To: Breno Leitao
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, Pratyush Yadav,
linux-kernel, kexec, linux-mm, usamaarif642, SeongJae Park,
kernel-team
Hi Breno,
On Mon, Mar 09 2026, Breno Leitao wrote:
> 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.
>
[...]
Thanks for cleaning up the KHO infrastructure. It is much appreciated!
I only have a couple comments. Once they are fixed this series should be
good to go.
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v8 3/6] kho: persist blob size in KHO FDT
2026-03-13 9:21 ` Pratyush Yadav
@ 2026-03-16 11:09 ` Breno Leitao
0 siblings, 0 replies; 19+ messages in thread
From: Breno Leitao @ 2026-03-16 11:09 UTC (permalink / raw)
To: Pratyush Yadav
Cc: Alexander Graf, Mike Rapoport, Pasha Tatashin, linux-kernel,
kexec, linux-mm, usamaarif642, SeongJae Park, kernel-team
On Fri, Mar 13, 2026 at 09:21:50AM +0000, Pratyush Yadav wrote:
> On Mon, Mar 09 2026, Breno Leitao wrote:
>
> > kho_add_subtree() accepts a size parameter but only forwards it to
> > debugfs. The size is not persisted in the KHO FDT, so it is lost across
> > kexec. This makes it impossible for the incoming kernel to determine the
> > blob size without understanding the blob format.
> >
> > Store the blob size as a "blob-size" property in the KHO FDT alongside
> > the "preserved-data" physical address. This allows the receiving kernel
> > to recover the size for any blob regardless of format.
> >
> > Also extend kho_retrieve_subtree() with an optional size output
> > parameter so callers can learn the blob size without needing to
> > understand the blob format. Update all callers to pass NULL for the
> > new parameter.
> >
> > Signed-off-by: Breno Leitao <leitao@debian.org>
> > ---
> [...]
> > diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> > index 54fe59fe43acd..1f22705d5d246 100644
> > --- a/kernel/liveupdate/kexec_handover.c
> > +++ b/kernel/liveupdate/kexec_handover.c
> > @@ -768,6 +768,7 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
> > {
> > phys_addr_t phys = virt_to_phys(blob);
> > void *root_fdt = kho_out.fdt;
> > + u64 size_u64 = size;
> > int err = -ENOMEM;
> > int off, fdt_err;
> >
> > @@ -784,11 +785,16 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
> > goto out_pack;
> > }
> >
> > - err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
> > + err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
> > &phys, sizeof(phys));
> > if (err < 0)
> > goto out_pack;
> >
> > + err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
> > + &size_u64, sizeof(size_u64));
> > + if (err < 0)
> > + goto out_pack;
> > +
>
> I noticed that the error handling here is a bit broken. We open the
> subnode for the subtree, but then if we fail to add the "preserved-data"
> property, we don't remove the subnode. So the next kernel gets an
> invalid FDT (per KHO ABI) and might as well refuse to parse it.
>
> Similarly here, the FDT might also be missing the size and then the next
> kernel might reject the FDT.
>
> Also, we directly return the FDT error code to the caller, which
> wouldn't make sense since it probably expects -errno.
>
> Not something this patchset has to fix, but I am pointing this out in
> case someone (possibly also future me) is interested in fixing this up.
That is a good point, do you mean a fix like the following?
commit 633d0cb01ed959676b60de8b1851dad1757d8fe5
Author: Breno Leitao <leitao@debian.org>
Date: Mon Mar 16 04:03:51 2026 -0700
kho: fix error handling in kho_add_subtree()
Fix two error handling issues in kho_add_subtree():
1. If fdt_setprop() fails after the subnode has been created, the
subnode is not removed. This leaves an incomplete node in the FDT
(missing "preserved-data" or "blob-size" properties), which violates
the KHO ABI and may cause the next kernel to reject the FDT.
2. The fdt_setprop() return value (an FDT error code) is stored
directly in err and returned to the caller, which expects -errno.
Fix both by storing fdt_setprop() results in fdt_err, jumping to a new
out_del_node label that removes the subnode on failure, and only setting
err = 0 on the success path.
Signed-off-by: Breno Leitao <leitao@debian.org>
Suggested-by: Pratyush Yadav <pratyush@kernel.org>
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 62b1b8a9aa337..8d2d30119f6d4 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -787,19 +787,24 @@ int kho_add_subtree(const char *name, void *blob, size_t size)
goto out_pack;
}
- err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
- &phys, sizeof(phys));
- if (err < 0)
- goto out_pack;
+ fdt_err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME,
+ &phys, sizeof(phys));
+ if (fdt_err < 0)
+ goto out_del_node;
- err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
- &size_u64, sizeof(size_u64));
- if (err < 0)
- goto out_pack;
+ fdt_err = fdt_setprop(root_fdt, off, KHO_SUB_TREE_SIZE_PROP_NAME,
+ &size_u64, sizeof(size_u64));
+ if (fdt_err < 0)
+ goto out_del_node;
WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
size, false));
+ err = 0;
+ goto out_pack;
+
+out_del_node:
+ fdt_del_node(root_fdt, off);
out_pack:
fdt_pack(root_fdt);
Given this is not strictly related to this patchset, I am planning to
send this fix separately.
> > WARN_ON_ONCE(kho_debugfs_blob_add(&kho_out.dbg, name, blob,
> > size, false));
> >
> > @@ -817,7 +823,7 @@ void kho_remove_subtree(void *blob)
> > const u64 *val;
> > int len;
> >
> > - val = fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> > + val = fdt_getprop(root_fdt, off, KHO_SUB_TREE_PROP_NAME, &len);
> > if (!val || len != sizeof(phys_addr_t))
> > continue;
> >
> > @@ -1314,13 +1320,14 @@ EXPORT_SYMBOL_GPL(is_kho_boot);
> > * kho_retrieve_subtree - retrieve a preserved sub blob by its name.
> > * @name: the name of the sub blob passed to kho_add_subtree().
> > * @phys: if found, the physical address of the sub blob is stored in @phys.
> > + * @size: if not NULL and found, the size of the sub blob is stored in @size.
> > *
> > * Retrieve a preserved sub blob named @name and store its physical
> > - * address in @phys.
> > + * address in @phys and optionally its size in @size.
> > *
> > * Return: 0 on success, error code on failure
> > */
> > -int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> > +int kho_retrieve_subtree(const char *name, phys_addr_t *phys, size_t *size)
> > {
> > const void *fdt = kho_get_fdt();
> > const u64 *val;
> > @@ -1336,12 +1343,21 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
> > if (offset < 0)
> > return -ENOENT;
> >
> > - val = fdt_getprop(fdt, offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
> > + val = fdt_getprop(fdt, offset, KHO_SUB_TREE_PROP_NAME, &len);
> > if (!val || len != sizeof(*val))
> > return -EINVAL;
> >
> > *phys = (phys_addr_t)*val;
> >
> > + if (size) {
> > + val = fdt_getprop(fdt, offset, KHO_SUB_TREE_SIZE_PROP_NAME,
> > + &len);
> > + if (val && len == sizeof(*val))
> > + *size = (size_t)*val;
> > + else
> > + *size = 0;
>
> If the size property is invalid, is it a good idea to ignore it? Should
> we instead consider the subnode to be broken and reject it entirely with
> an error message? Because if a caller expects a blob of 16 bytes but
> gets one with 0 bytes, it will likely error out anyway.
Ack, let me update this, then.
Thanks for the review,
--breno
^ permalink raw reply related [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-03-16 11:09 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 13:41 [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Breno Leitao
2026-03-09 13:41 ` [PATCH v8 1/6] kho: add size parameter to kho_add_subtree() Breno Leitao
2026-03-13 8:50 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 2/6] kho: rename fdt parameter to blob in kho_add/remove_subtree() Breno Leitao
2026-03-13 8:52 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 3/6] kho: persist blob size in KHO FDT Breno Leitao
2026-03-10 10:35 ` Mike Rapoport
2026-03-13 9:21 ` Pratyush Yadav
2026-03-16 11:09 ` Breno Leitao
2026-03-09 13:41 ` [PATCH v8 4/6] kho: fix kho_in_debugfs_init() to handle non-FDT blobs Breno Leitao
2026-03-10 10:36 ` Mike Rapoport
2026-03-12 11:11 ` Breno Leitao
2026-03-12 16:17 ` Mike Rapoport
2026-03-13 9:23 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 5/6] kho: kexec-metadata: track previous kernel chain Breno Leitao
2026-03-13 9:33 ` Pratyush Yadav
2026-03-09 13:41 ` [PATCH v8 6/6] kho: document kexec-metadata tracking feature Breno Leitao
2026-03-13 9:34 ` Pratyush Yadav
2026-03-13 10:01 ` [PATCH v8 0/6] kho: history: track previous kernel version and kexec boot count Pratyush Yadav
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox