Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

kho: add size parameter to kho_add_subtree()

Patch series "kho: history: track previous kernel version and kexec boot
count", v9.

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)


This patch (of 6):

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.

Link: https://lore.kernel.org/20260323110747.193569-1-duanchenghao@kylinos.cn
Link: https://lore.kernel.org/20260316-kho-v9-1-ed6dcd951988@debian.org
Signed-off-by: Breno Leitao <leitao@debian.org>
Suggested-by: Pratyush Yadav <pratyush@kernel.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
Cc: Alexander Graf <graf@amazon.com>
Cc: David Hildenbrand <david@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: "Liam R. Howlett" <Liam.Howlett@oracle.com>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: SeongJae Park <sj@kernel.org>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Breno Leitao and committed by
Andrew Morton
d9e4142e e9d973ef

+24 -16
+2 -2
include/linux/kexec_handover.h
··· 32 32 struct folio *kho_restore_folio(phys_addr_t phys); 33 33 struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages); 34 34 void *kho_restore_vmalloc(const struct kho_vmalloc *preservation); 35 - int kho_add_subtree(const char *name, void *fdt); 35 + int kho_add_subtree(const char *name, void *fdt, size_t size); 36 36 void kho_remove_subtree(void *fdt); 37 37 int kho_retrieve_subtree(const char *name, phys_addr_t *phys); 38 38 ··· 97 97 return NULL; 98 98 } 99 99 100 - static inline int kho_add_subtree(const char *name, void *fdt) 100 + static inline int kho_add_subtree(const char *name, void *fdt, size_t size) 101 101 { 102 102 return -EOPNOTSUPP; 103 103 }
+5 -3
kernel/liveupdate/kexec_handover.c
··· 727 727 * kho_add_subtree - record the physical address of a sub FDT in KHO root tree. 728 728 * @name: name of the sub tree. 729 729 * @fdt: the sub tree blob. 730 + * @size: size of the blob in bytes. 730 731 * 731 732 * Creates a new child node named @name in KHO root FDT and records 732 733 * the physical address of @fdt. The pages of @fdt must also be preserved ··· 739 738 * 740 739 * Return: 0 on success, error code on failure 741 740 */ 742 - int kho_add_subtree(const char *name, void *fdt) 741 + int kho_add_subtree(const char *name, void *fdt, size_t size) 743 742 { 744 743 phys_addr_t phys = virt_to_phys(fdt); 745 744 void *root_fdt = kho_out.fdt; ··· 764 763 if (err < 0) 765 764 goto out_pack; 766 765 767 - WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false)); 766 + WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, size, false)); 768 767 769 768 out_pack: 770 769 fdt_pack(root_fdt); ··· 1432 1431 } 1433 1432 1434 1433 WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, "fdt", 1435 - kho_out.fdt, true)); 1434 + kho_out.fdt, 1435 + fdt_totalsize(kho_out.fdt), true)); 1436 1436 1437 1437 return 0; 1438 1438
+9 -6
kernel/liveupdate/kexec_handover_debugfs.c
··· 25 25 }; 26 26 27 27 static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir, 28 - const char *name, const void *fdt) 28 + const char *name, const void *fdt, size_t size) 29 29 { 30 30 struct fdt_debugfs *f; 31 31 struct dentry *file; ··· 35 35 return -ENOMEM; 36 36 37 37 f->wrapper.data = (void *)fdt; 38 - f->wrapper.size = fdt_totalsize(fdt); 38 + f->wrapper.size = size; 39 39 40 40 file = debugfs_create_blob(name, 0400, dir, &f->wrapper); 41 41 if (IS_ERR(file)) { ··· 50 50 } 51 51 52 52 int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name, 53 - const void *fdt, bool root) 53 + const void *fdt, size_t size, bool root) 54 54 { 55 55 struct dentry *dir; 56 56 ··· 59 59 else 60 60 dir = dbg->sub_fdt_dir; 61 61 62 - return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt); 62 + return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt, size); 63 63 } 64 64 65 65 void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt) ··· 113 113 goto err_rmdir; 114 114 } 115 115 116 - err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt); 116 + err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt, 117 + fdt_totalsize(fdt)); 117 118 if (err) 118 119 goto err_rmdir; 119 120 ··· 122 121 int len = 0; 123 122 const char *name = fdt_get_name(fdt, child, NULL); 124 123 const u64 *fdt_phys; 124 + void *sub_fdt; 125 125 126 126 fdt_phys = fdt_getprop(fdt, child, KHO_FDT_SUB_TREE_PROP_NAME, &len); 127 127 if (!fdt_phys) ··· 132 130 name, len); 133 131 continue; 134 132 } 133 + sub_fdt = phys_to_virt(*fdt_phys); 135 134 err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name, 136 - phys_to_virt(*fdt_phys)); 135 + sub_fdt, fdt_totalsize(sub_fdt)); 137 136 if (err) { 138 137 pr_warn("failed to add fdt %s to debugfs: %pe\n", name, 139 138 ERR_PTR(err));
+3 -2
kernel/liveupdate/kexec_handover_internal.h
··· 27 27 void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt); 28 28 int kho_out_debugfs_init(struct kho_debugfs *dbg); 29 29 int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name, 30 - const void *fdt, bool root); 30 + const void *fdt, size_t size, bool root); 31 31 void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt); 32 32 #else 33 33 static inline int kho_debugfs_init(void) { return 0; } ··· 35 35 const void *fdt) { } 36 36 static inline int kho_out_debugfs_init(struct kho_debugfs *dbg) { return 0; } 37 37 static inline int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name, 38 - const void *fdt, bool root) { return 0; } 38 + const void *fdt, size_t size, 39 + bool root) { return 0; } 39 40 static inline void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, 40 41 void *fdt) { } 41 42 #endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
+2 -1
kernel/liveupdate/luo_core.c
··· 172 172 if (err) 173 173 goto exit_free; 174 174 175 - err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out); 175 + err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out, 176 + fdt_totalsize(fdt_out)); 176 177 if (err) 177 178 goto exit_free; 178 179 luo_global.fdt_out = fdt_out;
+2 -1
lib/test_kho.c
··· 143 143 if (err) 144 144 goto err_unpreserve_data; 145 145 146 - err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt)); 146 + err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt), 147 + fdt_totalsize(folio_address(state->fdt))); 147 148 if (err) 148 149 goto err_unpreserve_data; 149 150
+1 -1
mm/memblock.c
··· 2510 2510 if (err) 2511 2511 goto err_unpreserve_fdt; 2512 2512 2513 - err = kho_add_subtree(MEMBLOCK_KHO_FDT, fdt); 2513 + err = kho_add_subtree(MEMBLOCK_KHO_FDT, fdt, fdt_totalsize(fdt)); 2514 2514 if (err) 2515 2515 goto err_unpreserve_fdt; 2516 2516