Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/2] Add support for multiple versions to KHO
@ 2026-07-31 21:52 Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 1/4] kho: Move subtree blob logic to separate function Logan Odell
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Logan Odell @ 2026-07-31 21:52 UTC (permalink / raw)
  To: graf, rppt, pasha.tatashin
  Cc: pratyush, akpm, kexec, linux-mm, linux-kernel, Logan Odell

The format of the preserved-data blob that LUO saves to KHO is expected
to change as new features are added, bugs are fixed, etc. Currently,
LUO handles this by embedding a compatibility string into struct luo_ser
which can be used to compare two kernels. This works if the changes
between kernels are fundamentally incompatible. In a case where a new
feature is added, we may want to support both versions in the new
kernel to retain compatibility.

This series adds a KHO interface to embed multiple versions under a
single node in a subtree. 

We go from something like:

       / (Root Node)
       └── LUO (Legacy Parent Node)
           ├── preserved-data = <0xphys_luo>
           └── blob-size = <0xsize_luo>

To something like this:

       / (Root Node)
       └── LUO (Versioned Parent Node)
           ├── 1 (Version 1 Node)
           │   ├── preserved-data = <0xphys_luo_v1>
           │   └── blob-size = <0xsize_luo_v1>
           └── 2 (Version 2 Node)
               ├── preserved-data = <0xphys_luo_v2>
               └── blob-size = <0xsize_luo_v2>

We can also support mixed mode, allowing backwards compatibility in case
of a roll-back to a pre-multi version kernel:

       / (Root Node)
       └── LUO (Versioned Parent Node)
           ├── preserved-data = <0xphys_luo>
           ├── blob-size = <0xphys_luo>
           ├── 1 (Version 1 Node)
           │   ├── preserved-data = <0xphys_luo_v1>
           │   └── blob-size = <0xsize_luo_v1>
           └── 2 (Version 2 Node)
               ├── preserved-data = <0xphys_luo_v2>
               └── blob-size = <0xsize_luo_v2>

The first patch moves some common functionality for preserving a node
into a separate function. The second patch adds our new APIs. The third
patch adds support for multiple versions to the debugfs entries. The
forth patch adds new test cases to test_kho.

Logan Odell (4):
  kho: Move subtree blob logic to separate function
  kho: Add support for multiple versions in subtrees
  kho: Add support for multiple versions to debugfs
  kho: Add test cases for versioned subtrees

 include/linux/kexec_handover.h             |  14 ++
 include/linux/kho/abi/kexec_handover.h     |  16 ++
 kernel/liveupdate/kexec_handover.c         | 234 +++++++++++++++++-
 kernel/liveupdate/kexec_handover_debugfs.c |  57 ++++-
 lib/test_kho.c                             | 266 +++++++++++++++++++--
 5 files changed, 540 insertions(+), 47 deletions(-)

-- 
2.55.0.508.g3f0d502094-goog



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH 1/4] kho: Move subtree blob logic to separate function
  2026-07-31 21:52 [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
@ 2026-07-31 21:52 ` Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 2/4] kho: Add support for multiple versions in subtrees Logan Odell
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Logan Odell @ 2026-07-31 21:52 UTC (permalink / raw)
  To: graf, rppt, pasha.tatashin
  Cc: pratyush, akpm, kexec, linux-mm, linux-kernel, Logan Odell

Move the logic to set the blob data in a subtree node into a separate
function to allow for re-use. No functional change.

Signed-off-by: Logan Odell <loganodell@google.com>
---
 kernel/liveupdate/kexec_handover.c | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index cc68a3692905..29a05cec2625 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -722,6 +722,21 @@ static void __init kho_reserve_scratch(void)
 	kho_enable = false;
 }
 
+static int kho_add_subtree_node(const char *name, void *root_fdt, void *fdt, int off)
+{
+	int err;
+	phys_addr_t phys = virt_to_phys(fdt);
+
+	err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
+			  &phys, sizeof(phys));
+	if (err)
+		return err;
+
+	WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
+
+	return 0;
+}
+
 /**
  * kho_add_subtree - record the physical address of a sub FDT in KHO root tree.
  * @name: name of the sub tree.
@@ -739,7 +754,6 @@ static void __init kho_reserve_scratch(void)
  */
 int kho_add_subtree(const char *name, void *fdt)
 {
-	phys_addr_t phys = virt_to_phys(fdt);
 	void *root_fdt = kho_out.fdt;
 	int err = -ENOMEM;
 	int off, fdt_err;
@@ -757,12 +771,7 @@ int kho_add_subtree(const char *name, void *fdt)
 		goto out_pack;
 	}
 
-	err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
-			  &phys, sizeof(phys));
-	if (err < 0)
-		goto out_pack;
-
-	WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
+	err = kho_add_subtree_node(name, root_fdt, fdt, off);
 
 out_pack:
 	fdt_pack(root_fdt);
-- 
2.55.0.508.g3f0d502094-goog



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 2/4] kho: Add support for multiple versions in subtrees
  2026-07-31 21:52 [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 1/4] kho: Move subtree blob logic to separate function Logan Odell
@ 2026-07-31 21:52 ` Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 3/4] kho: Add support for multiple versions to debugfs Logan Odell
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Logan Odell @ 2026-07-31 21:52 UTC (permalink / raw)
  To: graf, rppt, pasha.tatashin
  Cc: pratyush, akpm, kexec, linux-mm, linux-kernel, Logan Odell

Add explicit version support to KHO by creating subnodes with
the version number. The first caller to kho_add_subtree_version will
create the parent node and versioned subnode. Subsequent calls with a
different version will add more subnodes with the version as the name.
Remove is updated to handle cleanup when the final version is removed.

Example Tree:

       / (Root Node)
       ├── compatible = "kho-v1"
       ├── preserved-memory-map = <0xphys_map>
       ├── some_legacy_node (Legacy Node)
       │   ├── preserved-data = <0xphys_legacy>
       │   └── blob-size = <0xsize_legacy>
       └── LUO (Versioned Parent Node)
           ├── 1 (Version 1 Node)
           │   ├── preserved-data = <0xphys_luo_v1>
           │   └── blob-size = <0xsize_luo_v1>
           └── 2 (Version 2 Node)
               ├── preserved-data = <0xphys_luo_v2>
               └── blob-size = <0xsize_luo_v2>

Signed-off-by: Logan Odell <loganodell@google.com>
---
 include/linux/kexec_handover.h         |  14 ++
 include/linux/kho/abi/kexec_handover.h |  16 ++
 kernel/liveupdate/kexec_handover.c     | 215 ++++++++++++++++++++++++-
 3 files changed, 237 insertions(+), 8 deletions(-)

diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index ac4129d1d741..1d47ab76b02b 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -33,8 +33,11 @@ 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_version(const char *name, int version, void *fdt);
 void kho_remove_subtree(void *fdt);
+void kho_remove_subtree_version(const char *name, int version, void *fdt);
 int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
+int kho_retrieve_subtree_version(const char *name, int version, phys_addr_t *phys);
 
 void kho_memory_init(void);
 
@@ -102,13 +105,24 @@ static inline int kho_add_subtree(const char *name, void *fdt)
 	return -EOPNOTSUPP;
 }
 
+static inline int kho_add_subtree_version(const char *name, int version, void *fdt)
+{
+	return -EOPNOTSUPP;
+}
+
 static inline void kho_remove_subtree(void *fdt) { }
+static inline void kho_remove_subtree_version(const char *name, int version, void *fdt) { }
 
 static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
 {
 	return -EOPNOTSUPP;
 }
 
+static inline int kho_retrieve_subtree_version(const char *name, int version, phys_addr_t *phys)
+{
+	return -EOPNOTSUPP;
+}
+
 static inline void kho_memory_init(void) { }
 
 static inline void kho_populate(phys_addr_t fdt_phys, u64 fdt_len,
diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
index 2201a0d2c159..55acb73b1d27 100644
--- a/include/linux/kho/abi/kexec_handover.h
+++ b/include/linux/kho/abi/kexec_handover.h
@@ -73,6 +73,22 @@
  *
  *       Physical address pointing to a subnode FDT blob that is also
  *       being preserved.
+ *
+ *   Versioned Subnodes (<subnode-name-N>):
+ *     Alternatively, a subnode can group multiple versions of the same
+ *     data. In this case, the subnode <subnode-name-N> does not contain
+ *     an 'fdt' property directly. Instead, it contains child nodes named
+ *     after the version number (e.g., '1', '2'), which in turn contain
+ *     the 'fdt' property.
+ *
+ *     <subnode-name-N> {
+ *         <version-1> {
+ *             fdt = <0x...>;
+ *         };
+ *         <version-2> {
+ *             fdt = <0x...>;
+ *         };
+ *     };
  */
 
 /* The compatible string for the KHO FDT root node. */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 29a05cec2625..6dc7c328a6e2 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -20,6 +20,7 @@
 #include <linux/list.h>
 #include <linux/memblock.h>
 #include <linux/page-isolation.h>
+#include <linux/slab.h>
 #include <linux/unaligned.h>
 #include <linux/vmalloc.h>
 
@@ -722,15 +723,31 @@ static void __init kho_reserve_scratch(void)
 	kho_enable = false;
 }
 
+static int fdt_err_to_errno(int fdt_err)
+{
+	switch (fdt_err) {
+	case 0:
+		return 0;
+	case -FDT_ERR_NOSPACE:
+		return -ENOSPC;
+	case -FDT_ERR_EXISTS:
+		return -EEXIST;
+	case -FDT_ERR_NOTFOUND:
+		return -ENOENT;
+	default:
+		return -EINVAL;
+	}
+}
+
 static int kho_add_subtree_node(const char *name, void *root_fdt, void *fdt, int off)
 {
 	int err;
-	phys_addr_t phys = virt_to_phys(fdt);
+	u64 phys = virt_to_phys(fdt);
 
 	err = fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
 			  &phys, sizeof(phys));
 	if (err)
-		return err;
+		return fdt_err_to_errno(err);
 
 	WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
 
@@ -762,12 +779,11 @@ int kho_add_subtree(const char *name, void *fdt)
 
 	fdt_err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
 	if (fdt_err < 0)
-		return err;
+		return fdt_err_to_errno(fdt_err);
 
 	off = fdt_add_subnode(root_fdt, 0, name);
 	if (off < 0) {
-		if (off == -FDT_ERR_EXISTS)
-			err = -EEXIST;
+		err = fdt_err_to_errno(off);
 		goto out_pack;
 	}
 
@@ -780,6 +796,93 @@ int kho_add_subtree(const char *name, void *fdt)
 }
 EXPORT_SYMBOL_GPL(kho_add_subtree);
 
+/**
+ * kho_add_subtree_version - record physical address of a sub FDT in KHO root tree with version.
+ * @name: name of the sub tree group.
+ * @version: version of the sub tree.
+ * @fdt: the sub tree blob.
+ *
+ * Finds or creates a child node named @name in KHO root FDT, and then
+ * creates a child node named @version under @name, and records
+ * the physical address of @fdt there.
+ *
+ * A debugfs blob entry is also created at
+ * ``/sys/kernel/debug/kho/out/sub_fdts/@name-@version`` when kernel is configured with
+ * CONFIG_KEXEC_HANDOVER_DEBUGFS
+ *
+ * Return: 0 on success, error code on failure
+ */
+int kho_add_subtree_version(const char *name, int version, void *fdt)
+{
+	void *root_fdt = kho_out.fdt;
+	int err = -ENOMEM;
+	int off, sub_off, fdt_err;
+	char version_str[12];
+	char *dbg_name = NULL;
+	bool created_parent = false;
+
+	mutex_lock(&kho_out.lock);
+
+	fdt_err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
+	if (fdt_err < 0) {
+		err = fdt_err_to_errno(fdt_err);
+		goto out;
+	}
+
+	off = fdt_subnode_offset(root_fdt, 0, name);
+	if (off == -FDT_ERR_NOTFOUND) {
+		off = fdt_add_subnode(root_fdt, 0, name);
+		if (off < 0) {
+			err = fdt_err_to_errno(off);
+			goto out_pack;
+		}
+		created_parent = true;
+	} else if (off < 0) {
+		err = fdt_err_to_errno(off);
+		goto out_pack;
+	}
+
+	snprintf(version_str, sizeof(version_str), "%d", version);
+	sub_off = fdt_add_subnode(root_fdt, off, version_str);
+	if (sub_off < 0) {
+		err = fdt_err_to_errno(sub_off);
+		goto err_del_parent;
+	}
+
+	dbg_name = kasprintf(GFP_KERNEL, "%s-%s", name, version_str);
+	if (!dbg_name) {
+		err = -ENOMEM;
+		goto err_del_subnode;
+	}
+
+	err = kho_add_subtree_node(dbg_name, root_fdt, fdt, sub_off);
+	if (err)
+		goto err_free_dbg_name;
+
+	kfree(dbg_name);
+
+out_pack:
+	fdt_pack(root_fdt);
+out:
+	mutex_unlock(&kho_out.lock);
+	return err;
+
+err_free_dbg_name:
+	kfree(dbg_name);
+err_del_subnode:
+	if (created_parent)
+		fdt_del_node(root_fdt, off);
+	else
+		fdt_del_node(root_fdt, sub_off);
+	goto out_pack;
+
+err_del_parent:
+	if (created_parent)
+		fdt_del_node(root_fdt, off);
+	goto out_pack;
+}
+EXPORT_SYMBOL_GPL(kho_add_subtree_version);
+
 void kho_remove_subtree(void *fdt)
 {
 	phys_addr_t target_phys = virt_to_phys(fdt);
@@ -799,10 +902,10 @@ void kho_remove_subtree(void *fdt)
 		int len;
 
 		val = fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
-		if (!val || len != sizeof(phys_addr_t))
+		if (!val || len != sizeof(*val))
 			continue;
 
-		if ((phys_addr_t)*val == target_phys) {
+		if ((phys_addr_t)get_unaligned(val) == target_phys) {
 			fdt_del_node(root_fdt, off);
 			kho_debugfs_fdt_remove(&kho_out.dbg, fdt);
 			break;
@@ -813,6 +916,59 @@ void kho_remove_subtree(void *fdt)
 }
 EXPORT_SYMBOL_GPL(kho_remove_subtree);
 
+/**
+ * kho_remove_subtree_version - remove a versioned sub FDT from KHO root tree.
+ * @name: name of the sub tree group.
+ * @version: version of the sub tree.
+ * @fdt: the sub tree blob to remove.
+ *
+ * Removes the @version subnode under @name node and its associated debugfs entry.
+ * If @name node becomes empty, it is also removed.
+ */
+void kho_remove_subtree_version(const char *name, int version, void *fdt)
+{
+	phys_addr_t target_phys = virt_to_phys(fdt);
+	void *root_fdt = kho_out.fdt;
+	int off, sub_off;
+	int err;
+	char version_str[12];
+
+	mutex_lock(&kho_out.lock);
+
+	err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
+	if (err < 0)
+		goto out;
+
+	off = fdt_subnode_offset(root_fdt, 0, name);
+	if (off < 0)
+		goto out_pack;
+
+	snprintf(version_str, sizeof(version_str), "%d", version);
+	sub_off = fdt_subnode_offset(root_fdt, off, version_str);
+	if (sub_off < 0)
+		goto out_pack;
+
+	{
+		const u64 *val;
+		int len;
+
+		val = fdt_getprop(root_fdt, sub_off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+		if (val && len == sizeof(*val) && (phys_addr_t)get_unaligned(val) == target_phys) {
+			fdt_del_node(root_fdt, sub_off);
+			kho_debugfs_fdt_remove(&kho_out.dbg, fdt);
+
+			if (fdt_first_subnode(root_fdt, off) == -FDT_ERR_NOTFOUND)
+				fdt_del_node(root_fdt, off);
+		}
+	}
+
+out_pack:
+	fdt_pack(root_fdt);
+out:
+	mutex_unlock(&kho_out.lock);
+}
+EXPORT_SYMBOL_GPL(kho_remove_subtree_version);
+
 /**
  * kho_preserve_folio - preserve a folio across kexec.
  * @folio: folio to preserve.
@@ -1336,12 +1492,55 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
 	if (!val || len != sizeof(*val))
 		return -EINVAL;
 
-	*phys = (phys_addr_t)*val;
+	*phys = (phys_addr_t)get_unaligned(val);
 
 	return 0;
 }
 EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
 
+/**
+ * kho_retrieve_subtree_version - retrieve a preserved versioned sub FDT.
+ * @name: the name of the sub FDT group passed to kho_add_subtree_version().
+ * @version: the version of the sub FDT.
+ * @phys: if found, the physical address of the sub FDT is stored in @phys.
+ *
+ * Retrieve a preserved sub FDT named @version under @name and store its
+ * physical address in @phys.
+ *
+ * Return: 0 on success, error code on failure
+ */
+int kho_retrieve_subtree_version(const char *name, int version, phys_addr_t *phys)
+{
+	const void *fdt = kho_get_fdt();
+	const u64 *val;
+	int offset, sub_offset, len;
+	char version_str[12];
+
+	if (!fdt)
+		return -ENOENT;
+
+	if (!phys)
+		return -EINVAL;
+
+	offset = fdt_subnode_offset(fdt, 0, name);
+	if (offset < 0)
+		return -ENOENT;
+
+	snprintf(version_str, sizeof(version_str), "%d", version);
+	sub_offset = fdt_subnode_offset(fdt, offset, version_str);
+	if (sub_offset < 0)
+		return -ENOENT;
+
+	val = fdt_getprop(fdt, sub_offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+	if (!val || len != sizeof(*val))
+		return -EINVAL;
+
+	*phys = (phys_addr_t)get_unaligned(val);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kho_retrieve_subtree_version);
+
 static __init int kho_out_fdt_setup(void)
 {
 	void *root = kho_out.fdt;
-- 
2.55.0.508.g3f0d502094-goog



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 3/4] kho: Add support for multiple versions to debugfs
  2026-07-31 21:52 [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 1/4] kho: Move subtree blob logic to separate function Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 2/4] kho: Add support for multiple versions in subtrees Logan Odell
@ 2026-07-31 21:52 ` Logan Odell
  2026-07-31 21:52 ` [RFC PATCH 4/4] kho: Add test cases for versioned subtrees Logan Odell
  2026-07-31 21:56 ` [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
  4 siblings, 0 replies; 6+ messages in thread
From: Logan Odell @ 2026-07-31 21:52 UTC (permalink / raw)
  To: graf, rppt, pasha.tatashin
  Cc: pratyush, akpm, kexec, linux-mm, linux-kernel, Logan Odell

Update kho_in_debugfs_init to search for versioned subnodes
(depth 2) and expose them as name-version in the input debugfs.
This ensures versioned subtrees are visible to userspace for debugging,
matching the behavior of the output side.

Signed-off-by: Logan Odell <loganodell@google.com>
---
 kernel/liveupdate/kexec_handover_debugfs.c | 57 +++++++++++++++++-----
 1 file changed, 44 insertions(+), 13 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index 2f93939168ab..2ca055219900 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/unaligned.h>
 #include "kexec_handover_internal.h"
 
 static struct dentry *debugfs_root;
@@ -140,19 +141,49 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
 		const u64 *fdt_phys;
 
 		fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
-		if (!fdt_phys)
-			continue;
-		if (len != sizeof(*fdt_phys)) {
-			pr_warn("node %s prop fdt has invalid length: %d\n",
-				name, len);
-			continue;
-		}
-		err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
-					    phys_to_virt(*fdt_phys));
-		if (err) {
-			pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
-				ERR_PTR(err));
-			continue;
+		if (fdt_phys) {
+			if (len != sizeof(*fdt_phys)) {
+				pr_warn("node %s prop fdt has invalid length: %d\n",
+					name, len);
+				continue;
+			}
+			err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
+						    phys_to_virt(get_unaligned(fdt_phys)));
+			if (err) {
+				pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
+					ERR_PTR(err));
+			}
+		} else {
+			int grandchild;
+
+			fdt_for_each_subnode(grandchild, fdt, child) {
+				const char *gc_name = fdt_get_name(fdt, grandchild, NULL);
+				const u64 *gc_fdt_phys;
+				char *combined_name;
+
+				gc_fdt_phys = fdt_getprop(fdt, grandchild, "fdt", &len);
+				if (!gc_fdt_phys)
+					continue;
+
+				if (len != sizeof(*gc_fdt_phys)) {
+					pr_warn("node %s/%s prop fdt has invalid length: %d\n",
+						name, gc_name, len);
+					continue;
+				}
+
+				combined_name = kasprintf(GFP_KERNEL, "%s-%s", name, gc_name);
+				if (!combined_name)
+					continue;
+
+				err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir,
+							combined_name,
+							phys_to_virt(get_unaligned(gc_fdt_phys)));
+				kfree(combined_name);
+				if (err) {
+					pr_warn("failed to add fdt %s-%s to debugfs: %pe\n",
+						name, gc_name, ERR_PTR(err));
+				}
+			}
 		}
 	}
 
-- 
2.55.0.508.g3f0d502094-goog



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 4/4] kho: Add test cases for versioned subtrees
  2026-07-31 21:52 [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
                   ` (2 preceding siblings ...)
  2026-07-31 21:52 ` [RFC PATCH 3/4] kho: Add support for multiple versions to debugfs Logan Odell
@ 2026-07-31 21:52 ` Logan Odell
  2026-07-31 21:56 ` [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
  4 siblings, 0 replies; 6+ messages in thread
From: Logan Odell @ 2026-07-31 21:52 UTC (permalink / raw)
  To: graf, rppt, pasha.tatashin
  Cc: pratyush, akpm, kexec, linux-mm, linux-kernel, Logan Odell

Add new test cases to test_kho.c to verify saving and restoring
multiple versions of a subtree. The test preserves versions 1 and 2
of "kho_test_ver" and retrieves them, verifying their contents.
It also ensures no memory leaks occur during the restore process.

Signed-off-by: Logan Odell <loganodell@google.com>
---
 lib/test_kho.c | 266 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 245 insertions(+), 21 deletions(-)

diff --git a/lib/test_kho.c b/lib/test_kho.c
index 7ef9e4061869..db531da63576 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -310,52 +310,276 @@ static int kho_test_restore(phys_addr_t fdt_phys)
 	return 0;
 }
 
+static void kho_test_cleanup(void)
+{
+	/* unpreserve and free the data stored in folios */
+	kho_test_unpreserve_data(&kho_test_state);
+	for (int i = 0; i < kho_test_state.nr_folios; i++)
+		folio_put(kho_test_state.folios[i]);
+
+	kvfree(kho_test_state.folios);
+
+	/* Unpreserve and release the FDT folio */
+	kho_unpreserve_folio(kho_test_state.fdt);
+	folio_put(kho_test_state.fdt);
+}
+
+#define KHO_TEST_VER_NAME	"kho_test_ver"
+#define KHO_TEST_VER_1		1
+#define KHO_TEST_VER_2		2
+#define KHO_TEST_VER_MAGIC_1	0x11111111
+#define KHO_TEST_VER_MAGIC_2	0x22222222
+
+struct kho_test_versioned_state {
+	struct folio *fdt1;
+	struct folio *fdt2;
+};
+
+static struct kho_test_versioned_state kho_test_ver_state;
+
+static int kho_test_prepare_ver_fdt(struct folio *folio, int version, unsigned int magic)
+{
+	void *fdt = folio_address(folio);
+	int err;
+
+	err = fdt_create(fdt, PAGE_SIZE);
+	err |= fdt_finish_reservemap(fdt);
+	err |= fdt_begin_node(fdt, "");
+	err |= fdt_property(fdt, "compatible", KHO_TEST_COMPAT, sizeof(KHO_TEST_COMPAT));
+	err |= fdt_property(fdt, "magic", &magic, sizeof(magic));
+	err |= fdt_property(fdt, "version", &version, sizeof(version));
+	err |= fdt_end_node(fdt);
+	err |= fdt_finish(fdt);
+
+	return err;
+}
+
+static int kho_test_ver_save(void)
+{
+	struct kho_test_versioned_state *state = &kho_test_ver_state;
+	int err;
+
+	state->fdt1 = folio_alloc(GFP_KERNEL, 0);
+	if (!state->fdt1)
+		return -ENOMEM;
+
+	state->fdt2 = folio_alloc(GFP_KERNEL, 0);
+	if (!state->fdt2) {
+		err = -ENOMEM;
+		goto err_free_fdt1;
+	}
+
+	err = kho_preserve_folio(state->fdt1);
+	if (err)
+		goto err_free_fdt2;
+
+	err = kho_preserve_folio(state->fdt2);
+	if (err)
+		goto err_unpreserve_fdt1;
+
+	err = kho_test_prepare_ver_fdt(state->fdt1, KHO_TEST_VER_1, KHO_TEST_VER_MAGIC_1);
+	if (err)
+		goto err_unpreserve_fdt2;
+
+	err = kho_test_prepare_ver_fdt(state->fdt2, KHO_TEST_VER_2, KHO_TEST_VER_MAGIC_2);
+	if (err)
+		goto err_unpreserve_fdt2;
+
+	err = kho_add_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_1,
+				      folio_address(state->fdt1));
+	if (err)
+		goto err_unpreserve_fdt2;
+
+	err = kho_add_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_2,
+				      folio_address(state->fdt2));
+	if (err)
+		goto err_remove_ver1;
+
+	pr_info("KHO versioned test save succeeded\n");
+	return 0;
+
+err_remove_ver1:
+	kho_remove_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_1, folio_address(state->fdt1));
+err_unpreserve_fdt2:
+	kho_unpreserve_folio(state->fdt2);
+err_unpreserve_fdt1:
+	kho_unpreserve_folio(state->fdt1);
+err_free_fdt2:
+	folio_put(state->fdt2);
+	state->fdt2 = NULL;
+err_free_fdt1:
+	folio_put(state->fdt1);
+	state->fdt1 = NULL;
+	return err;
+}
+
+static int kho_test_ver_restore_one(phys_addr_t fdt_phys, int expected_version,
+				    unsigned int expected_magic)
+{
+	void *fdt = phys_to_virt(fdt_phys);
+	const unsigned int *magic;
+	const int *version;
+	int node, len;
+
+	node = fdt_path_offset(fdt, "/");
+	if (node < 0)
+		return -EINVAL;
+
+	if (fdt_node_check_compatible(fdt, node, KHO_TEST_COMPAT))
+		return -EINVAL;
+
+	magic = fdt_getprop(fdt, node, "magic", &len);
+	if (!magic || len != sizeof(*magic) || *magic != expected_magic)
+		return -EINVAL;
+
+	version = fdt_getprop(fdt, node, "version", &len);
+	if (!version || len != sizeof(*version) || *version != expected_version)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int kho_test_ver_restore(void)
+{
+	phys_addr_t fdt_phys1, fdt_phys2;
+	struct folio *fdt1 = NULL, *fdt2 = NULL;
+	int err;
+
+	err = kho_retrieve_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_1, &fdt_phys1);
+	if (err) {
+		if (err != -ENOENT)
+			pr_err("Failed to retrieve version 1: %d\n", err);
+		return err;
+	}
+
+	fdt1 = kho_restore_folio(fdt_phys1);
+	if (!fdt1) {
+		pr_err("Failed to restore FDT 1 folio\n");
+		return -EINVAL;
+	}
+
+	err = kho_retrieve_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_2, &fdt_phys2);
+	if (err) {
+		pr_err("Failed to retrieve version 2: %d\n", err);
+		goto out_put_fdt1;
+	}
+
+	fdt2 = kho_restore_folio(fdt_phys2);
+	if (!fdt2) {
+		pr_err("Failed to restore FDT 2 folio\n");
+		err = -EINVAL;
+		goto out_put_fdt1;
+	}
+
+	err = kho_test_ver_restore_one(fdt_phys1, KHO_TEST_VER_1, KHO_TEST_VER_MAGIC_1);
+	if (err) {
+		pr_err("Version 1 verification failed: %d\n", err);
+		goto out_put_fdt2;
+	}
+
+	err = kho_test_ver_restore_one(fdt_phys2, KHO_TEST_VER_2, KHO_TEST_VER_MAGIC_2);
+	if (err) {
+		pr_err("Version 2 verification failed: %d\n", err);
+		goto out_put_fdt2;
+	}
+
+	pr_info("KHO versioned test restore succeeded\n");
+
+out_put_fdt2:
+	folio_put(fdt2);
+out_put_fdt1:
+	folio_put(fdt1);
+	return err;
+}
+
+static void kho_test_ver_cleanup(void)
+{
+	struct kho_test_versioned_state *state = &kho_test_ver_state;
+
+	if (state->fdt1) {
+		kho_unpreserve_folio(state->fdt1);
+		folio_put(state->fdt1);
+		state->fdt1 = NULL;
+	}
+	if (state->fdt2) {
+		kho_unpreserve_folio(state->fdt2);
+		folio_put(state->fdt2);
+		state->fdt2 = NULL;
+	}
+}
+
+static void kho_test_ver_exit(void)
+{
+	struct kho_test_versioned_state *state = &kho_test_ver_state;
+
+	if (state->fdt1)
+		kho_remove_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_1,
+					   folio_address(state->fdt1));
+	if (state->fdt2)
+		kho_remove_subtree_version(KHO_TEST_VER_NAME, KHO_TEST_VER_2,
+					   folio_address(state->fdt2));
+	kho_test_ver_cleanup();
+}
+
 static int __init kho_test_init(void)
 {
 	phys_addr_t fdt_phys;
-	int err;
+	int err, ver_err;
+	bool restored = false;
 
 	if (!kho_is_enabled())
 		return 0;
 
+	// Try to restore non-versioned
 	err = kho_retrieve_subtree(KHO_TEST_FDT, &fdt_phys);
 	if (!err) {
 		err = kho_test_restore(fdt_phys);
-		if (err)
+		if (err) {
 			pr_err("KHO restore failed\n");
-		else
-			pr_info("KHO restore succeeded\n");
-
+			return err;
+		}
+		pr_info("KHO restore succeeded\n");
+		restored = true;
+	} else if (err != -ENOENT) {
+		pr_warn("failed to retrieve %s FDT: %d\n", KHO_TEST_FDT, err);
 		return err;
 	}
 
-	if (err != -ENOENT) {
-		pr_warn("failed to retrieve %s FDT: %d\n", KHO_TEST_FDT, err);
-		return err;
+	// Try to restore versioned
+	ver_err = kho_test_ver_restore();
+	if (!ver_err) {
+		restored = true;
+	} else if (ver_err != -ENOENT) {
+		pr_warn("KHO versioned restore failed: %d\n", ver_err);
+		return ver_err;
 	}
 
-	return kho_test_save();
-}
-module_init(kho_test_init);
+	if (restored)
+		return 0;
 
-static void kho_test_cleanup(void)
-{
-	/* unpreserve and free the data stored in folios */
-	kho_test_unpreserve_data(&kho_test_state);
-	for (int i = 0; i < kho_test_state.nr_folios; i++)
-		folio_put(kho_test_state.folios[i]);
+	// If we got here, it means both returned -ENOENT (first boot).
+	// So we save both.
 
-	kvfree(kho_test_state.folios);
+	err = kho_test_save();
+	if (err)
+		return err;
 
-	/* Unpreserve and release the FDT folio */
-	kho_unpreserve_folio(kho_test_state.fdt);
-	folio_put(kho_test_state.fdt);
+	ver_err = kho_test_ver_save();
+	if (ver_err) {
+		kho_remove_subtree(folio_address(kho_test_state.fdt));
+		kho_test_cleanup();
+		return ver_err;
+	}
+
+	return 0;
 }
+module_init(kho_test_init);
 
 static void __exit kho_test_exit(void)
 {
 	kho_remove_subtree(folio_address(kho_test_state.fdt));
 	kho_test_cleanup();
+	kho_test_ver_exit();
 }
 module_exit(kho_test_exit);
 
-- 
2.55.0.508.g3f0d502094-goog



^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH 0/2] Add support for multiple versions to KHO
  2026-07-31 21:52 [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
                   ` (3 preceding siblings ...)
  2026-07-31 21:52 ` [RFC PATCH 4/4] kho: Add test cases for versioned subtrees Logan Odell
@ 2026-07-31 21:56 ` Logan Odell
  4 siblings, 0 replies; 6+ messages in thread
From: Logan Odell @ 2026-07-31 21:56 UTC (permalink / raw)
  To: graf, rppt, pasha.tatashin; +Cc: pratyush, akpm, kexec, linux-mm, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 2888 bytes --]

0/2 is a typo, should be 0/4 :-)

On Fri, Jul 31, 2026 at 2:53 PM Logan Odell <loganodell@google.com> wrote:

> The format of the preserved-data blob that LUO saves to KHO is expected
> to change as new features are added, bugs are fixed, etc. Currently,
> LUO handles this by embedding a compatibility string into struct luo_ser
> which can be used to compare two kernels. This works if the changes
> between kernels are fundamentally incompatible. In a case where a new
> feature is added, we may want to support both versions in the new
> kernel to retain compatibility.
>
> This series adds a KHO interface to embed multiple versions under a
> single node in a subtree.
>
> We go from something like:
>
>        / (Root Node)
>        └── LUO (Legacy Parent Node)
>            ├── preserved-data = <0xphys_luo>
>            └── blob-size = <0xsize_luo>
>
> To something like this:
>
>        / (Root Node)
>        └── LUO (Versioned Parent Node)
>            ├── 1 (Version 1 Node)
>            │   ├── preserved-data = <0xphys_luo_v1>
>            │   └── blob-size = <0xsize_luo_v1>
>            └── 2 (Version 2 Node)
>                ├── preserved-data = <0xphys_luo_v2>
>                └── blob-size = <0xsize_luo_v2>
>
> We can also support mixed mode, allowing backwards compatibility in case
> of a roll-back to a pre-multi version kernel:
>
>        / (Root Node)
>        └── LUO (Versioned Parent Node)
>            ├── preserved-data = <0xphys_luo>
>            ├── blob-size = <0xphys_luo>
>            ├── 1 (Version 1 Node)
>            │   ├── preserved-data = <0xphys_luo_v1>
>            │   └── blob-size = <0xsize_luo_v1>
>            └── 2 (Version 2 Node)
>                ├── preserved-data = <0xphys_luo_v2>
>                └── blob-size = <0xsize_luo_v2>
>
> The first patch moves some common functionality for preserving a node
> into a separate function. The second patch adds our new APIs. The third
> patch adds support for multiple versions to the debugfs entries. The
> forth patch adds new test cases to test_kho.
>
> Logan Odell (4):
>   kho: Move subtree blob logic to separate function
>   kho: Add support for multiple versions in subtrees
>   kho: Add support for multiple versions to debugfs
>   kho: Add test cases for versioned subtrees
>
>  include/linux/kexec_handover.h             |  14 ++
>  include/linux/kho/abi/kexec_handover.h     |  16 ++
>  kernel/liveupdate/kexec_handover.c         | 234 +++++++++++++++++-
>  kernel/liveupdate/kexec_handover_debugfs.c |  57 ++++-
>  lib/test_kho.c                             | 266 +++++++++++++++++++--
>  5 files changed, 540 insertions(+), 47 deletions(-)
>
> --
> 2.55.0.508.g3f0d502094-goog
>
>

[-- Attachment #2: Type: text/html, Size: 3577 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-07-31 21:57 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 21:52 [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell
2026-07-31 21:52 ` [RFC PATCH 1/4] kho: Move subtree blob logic to separate function Logan Odell
2026-07-31 21:52 ` [RFC PATCH 2/4] kho: Add support for multiple versions in subtrees Logan Odell
2026-07-31 21:52 ` [RFC PATCH 3/4] kho: Add support for multiple versions to debugfs Logan Odell
2026-07-31 21:52 ` [RFC PATCH 4/4] kho: Add test cases for versioned subtrees Logan Odell
2026-07-31 21:56 ` [RFC PATCH 0/2] Add support for multiple versions to KHO Logan Odell

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox