Add support for multiple versions to KHO
Subsystems (4)
cover Add support for multiple versions to KHO
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(-)
Signature · 1 line
2.55.0.508.g3f0d502094-goog
RFC PATCH 1/4 kho: Move subtree blob logic to separate function
Move the logic to set the blob data in a subtree node into a separate function to allow for re-use. No functional change.
--- kernel/liveupdate/kexec_handover.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)
kernel/liveupdate/kexec_handover.c +16 -7
@@ -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);
Signature · 1 line
2.55.0.508.g3f0d502094-goog
RFC PATCH 2/4 kho: Add support for multiple versions in subtrees
In reply to Logan OdellAdd 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>--- 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(-)
include/linux/kexec_handover.h +14 -0
@@ -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,
include/linux/kho/abi/kexec_handover.h +16 -0
@@ -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. */
kernel/liveupdate/kexec_handover.c +207 -8
274 lines not on this page · raw message
Signature · 1 line
2.55.0.508.g3f0d502094-goog
RFC PATCH 3/4 kho: Add support for multiple versions to debugfs
In reply to Logan OdellUpdate 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.
--- kernel/liveupdate/kexec_handover_debugfs.c | 57 +++++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-)
kernel/liveupdate/kexec_handover_debugfs.c +44 -13
@@ -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)); + } + } } }
Signature · 1 line
2.55.0.508.g3f0d502094-goog
RFC PATCH 4/4 kho: Add test cases for versioned subtrees
In reply to Logan OdellAdd 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.
--- lib/test_kho.c | 266 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 245 insertions(+), 21 deletions(-)
lib/test_kho.c +245 -21
@@ -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);
Signature · 1 line
2.55.0.508.g3f0d502094-goog
0/2 is a typo, should be 0/4 :-)
Quoted 65 lines · 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_serwhich 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 subtreesinclude/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