[RFC PATCH 2/4] kho: Add support for multiple versions in subtrees
Logan Odell <[email protected]> Fri, 31 Jul 2026 14:52:22 -0700
| Newsgroups | org.infradead.lists.kexec,org.kernel.vger.linux-kernel,org.kvack.linux-mm |
|---|---|
| Message-ID | <[email protected]> |
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)
=E2=94=9C=E2=94=80=E2=94=80 compatible =3D "kho-v1"
=E2=94=9C=E2=94=80=E2=94=80 preserved-memory-map =3D <0xphys_map>
=E2=94=9C=E2=94=80=E2=94=80 some_legacy_node (Legacy Node)
=E2=94=82 =E2=94=9C=E2=94=80=E2=94=80 preserved-data =3D <0xphys_l=
egacy>
=E2=94=82 =E2=94=94=E2=94=80=E2=94=80 blob-size =3D <0xsize_legacy=
>
=E2=94=94=E2=94=80=E2=94=80 LUO (Versioned Parent Node)
=E2=94=9C=E2=94=80=E2=94=80 1 (Version 1 Node)
=E2=94=82 =E2=94=9C=E2=94=80=E2=94=80 preserved-data =3D <0xph=
ys_luo_v1>
=E2=94=82 =E2=94=94=E2=94=80=E2=94=80 blob-size =3D <0xsize_lu=
o_v1>
=E2=94=94=E2=94=80=E2=94=80 2 (Version 2 Node)
=E2=94=9C=E2=94=80=E2=94=80 preserved-data =3D <0xphys_luo_v=
2>
=E2=94=94=E2=94=80=E2=94=80 blob-size =3D <0xsize_luo_v2>
Signed-off-by: Logan Odell <[email protected]>
---
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);
=20
void kho_memory_init(void);
=20
@@ -102,13 +105,24 @@ static inline int kho_add_subtree(const char *name, v=
oid *fdt)
return -EOPNOTSUPP;
}
=20
+static inline int kho_add_subtree_version(const char *name, int version, v=
oid *fdt)
+{
+ return -EOPNOTSUPP;
+}
+
static inline void kho_remove_subtree(void *fdt) { }
+static inline void kho_remove_subtree_version(const char *name, int versio=
n, void *fdt) { }
=20
static inline int kho_retrieve_subtree(const char *name, phys_addr_t *phys=
)
{
return -EOPNOTSUPP;
}
=20
+static inline int kho_retrieve_subtree_version(const char *name, int versi=
on, phys_addr_t *phys)
+{
+ return -EOPNOTSUPP;
+}
+
static inline void kho_memory_init(void) { }
=20
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 =3D <0x...>;
+ * };
+ * <version-2> {
+ * fdt =3D <0x...>;
+ * };
+ * };
*/
=20
/* The compatible string for the KHO FDT root node. */
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_h=
andover.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>
=20
@@ -722,15 +723,31 @@ static void __init kho_reserve_scratch(void)
kho_enable =3D false;
}
=20
+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 *fd=
t, int off)
{
int err;
- phys_addr_t phys =3D virt_to_phys(fdt);
+ u64 phys =3D virt_to_phys(fdt);
=20
err =3D fdt_setprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME,
&phys, sizeof(phys));
if (err)
- return err;
+ return fdt_err_to_errno(err);
=20
WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
=20
@@ -762,12 +779,11 @@ int kho_add_subtree(const char *name, void *fdt)
=20
fdt_err =3D fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
if (fdt_err < 0)
- return err;
+ return fdt_err_to_errno(fdt_err);
=20
off =3D fdt_add_subnode(root_fdt, 0, name);
if (off < 0) {
- if (off =3D=3D -FDT_ERR_EXISTS)
- err =3D -EEXIST;
+ err =3D fdt_err_to_errno(off);
goto out_pack;
}
=20
@@ -780,6 +796,93 @@ int kho_add_subtree(const char *name, void *fdt)
}
EXPORT_SYMBOL_GPL(kho_add_subtree);
=20
+/**
+ * kho_add_subtree_version - record physical address of a sub FDT in KHO r=
oot 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 co=
nfigured 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 =3D kho_out.fdt;
+ int err =3D -ENOMEM;
+ int off, sub_off, fdt_err;
+ char version_str[12];
+ char *dbg_name =3D NULL;
+ bool created_parent =3D false;
+
+ mutex_lock(&kho_out.lock);
+
+ fdt_err =3D fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
+ if (fdt_err < 0) {
+ err =3D fdt_err_to_errno(fdt_err);
+ goto out;
+ }
+
+ off =3D fdt_subnode_offset(root_fdt, 0, name);
+ if (off =3D=3D -FDT_ERR_NOTFOUND) {
+ off =3D fdt_add_subnode(root_fdt, 0, name);
+ if (off < 0) {
+ err =3D fdt_err_to_errno(off);
+ goto out_pack;
+ }
+ created_parent =3D true;
+ } else if (off < 0) {
+ err =3D fdt_err_to_errno(off);
+ goto out_pack;
+ }
+
+ snprintf(version_str, sizeof(version_str), "%d", version);
+ sub_off =3D fdt_add_subnode(root_fdt, off, version_str);
+ if (sub_off < 0) {
+ err =3D fdt_err_to_errno(sub_off);
+ goto err_del_parent;
+ }
+
+ dbg_name =3D kasprintf(GFP_KERNEL, "%s-%s", name, version_str);
+ if (!dbg_name) {
+ err =3D -ENOMEM;
+ goto err_del_subnode;
+ }
+
+ err =3D 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 =3D virt_to_phys(fdt);
@@ -799,10 +902,10 @@ void kho_remove_subtree(void *fdt)
int len;
=20
val =3D fdt_getprop(root_fdt, off, KHO_FDT_SUB_TREE_PROP_NAME, &len);
- if (!val || len !=3D sizeof(phys_addr_t))
+ if (!val || len !=3D sizeof(*val))
continue;
=20
- if ((phys_addr_t)*val =3D=3D target_phys) {
+ if ((phys_addr_t)get_unaligned(val) =3D=3D 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);
=20
+/**
+ * kho_remove_subtree_version - remove a versioned sub FDT from KHO root t=
ree.
+ * @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 debugf=
s 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 =3D virt_to_phys(fdt);
+ void *root_fdt =3D kho_out.fdt;
+ int off, sub_off;
+ int err;
+ char version_str[12];
+
+ mutex_lock(&kho_out.lock);
+
+ err =3D fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
+ if (err < 0)
+ goto out;
+
+ off =3D fdt_subnode_offset(root_fdt, 0, name);
+ if (off < 0)
+ goto out_pack;
+
+ snprintf(version_str, sizeof(version_str), "%d", version);
+ sub_off =3D fdt_subnode_offset(root_fdt, off, version_str);
+ if (sub_off < 0)
+ goto out_pack;
+
+ {
+ const u64 *val;
+ int len;
+
+ val =3D fdt_getprop(root_fdt, sub_off, KHO_FDT_SUB_TREE_PROP_NAME, &len)=
;
+ if (val && len =3D=3D sizeof(*val) && (phys_addr_t)get_unaligned(val) =
=3D=3D target_phys) {
+ fdt_del_node(root_fdt, sub_off);
+ kho_debugfs_fdt_remove(&kho_out.dbg, fdt);
+
+ if (fdt_first_subnode(root_fdt, off) =3D=3D -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_add=
r_t *phys)
if (!val || len !=3D sizeof(*val))
return -EINVAL;
=20
- *phys =3D (phys_addr_t)*val;
+ *phys =3D (phys_addr_t)get_unaligned(val);
=20
return 0;
}
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
=20
+/**
+ * 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 =3D kho_get_fdt();
+ const u64 *val;
+ int offset, sub_offset, len;
+ char version_str[12];
+
+ if (!fdt)
+ return -ENOENT;
+
+ if (!phys)
+ return -EINVAL;
+
+ offset =3D fdt_subnode_offset(fdt, 0, name);
+ if (offset < 0)
+ return -ENOENT;
+
+ snprintf(version_str, sizeof(version_str), "%d", version);
+ sub_offset =3D fdt_subnode_offset(fdt, offset, version_str);
+ if (sub_offset < 0)
+ return -ENOENT;
+
+ val =3D fdt_getprop(fdt, sub_offset, KHO_FDT_SUB_TREE_PROP_NAME, &len);
+ if (!val || len !=3D sizeof(*val))
+ return -EINVAL;
+
+ *phys =3D (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 =3D kho_out.fdt;
--=20
2.55.0.508.g3f0d502094-goog