Currently, sub-FDTs were tracked in a list (kho_out.sub_fdts) and the
final FDT is constructed entirely from scratch during kho_finalize().
We can maintain the FDT dynamically:
1. Initialize a valid, empty FDT in kho_init().
2. Use fdt_add_subnode and fdt_setprop in kho_add_subtree to
update the FDT immediately when a subsystem registers.
3. Use fdt_del_node in kho_remove_subtree to remove entries.
This removes the need for the intermediate sub_fdts list and the
reconstruction logic in kho_finalize(). kho_finalize() now
only needs to trigger memory map serialization.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/kexec_handover.c | 144 ++++++++++++++---------------
1 file changed, 68 insertions(+), 76 deletions(-)
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 8ab77cb85ca9..822da961d4c9 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -102,20 +102,11 @@ struct kho_mem_track {
struct khoser_mem_chunk;
-struct kho_sub_fdt {
- struct list_head l;
- const char *name;
- void *fdt;
-};
-
struct kho_out {
void *fdt;
bool finalized;
struct mutex lock; /* protects KHO FDT finalization */
- struct list_head sub_fdts;
- struct mutex fdts_lock;
-
struct kho_mem_track track;
struct kho_debugfs dbg;
};
@@ -125,8 +116,6 @@ static struct kho_out kho_out = {
.track = {
.orders = XARRAY_INIT(kho_out.track.orders, 0),
},
- .sub_fdts = LIST_HEAD_INIT(kho_out.sub_fdts),
- .fdts_lock = __MUTEX_INITIALIZER(kho_out.fdts_lock),
.finalized = false,
};
@@ -724,37 +713,67 @@ static void __init kho_reserve_scratch(void)
*/
int kho_add_subtree(const char *name, void *fdt)
{
- struct kho_sub_fdt *sub_fdt;
+ phys_addr_t phys = virt_to_phys(fdt);
+ void *root_fdt = kho_out.fdt;
+ int err = -ENOMEM;
+ int off, fdt_err;
- sub_fdt = kmalloc(sizeof(*sub_fdt), GFP_KERNEL);
- if (!sub_fdt)
- return -ENOMEM;
+ guard(mutex)(&kho_out.lock);
+
+ fdt_err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
+ if (fdt_err < 0)
+ return err;
- INIT_LIST_HEAD(&sub_fdt->l);
- sub_fdt->name = name;
- sub_fdt->fdt = fdt;
+ off = fdt_add_subnode(root_fdt, 0, name);
+ if (off < 0) {
+ if (off == -FDT_ERR_EXISTS)
+ err = -EEXIST;
+ goto out_pack;
+ }
+
+ err = fdt_setprop(root_fdt, off, PROP_SUB_FDT, &phys, sizeof(phys));
+ if (err < 0)
+ goto out_pack;
- guard(mutex)(&kho_out.fdts_lock);
- list_add_tail(&sub_fdt->l, &kho_out.sub_fdts);
WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
- return 0;
+out_pack:
+ fdt_pack(root_fdt);
+
+ return err;
}
EXPORT_SYMBOL_GPL(kho_add_subtree);
void kho_remove_subtree(void *fdt)
{
- struct kho_sub_fdt *sub_fdt;
+ phys_addr_t target_phys = virt_to_phys(fdt);
+ void *root_fdt = kho_out.fdt;
+ int off;
+ int err;
+
+ guard(mutex)(&kho_out.lock);
- guard(mutex)(&kho_out.fdts_lock);
- list_for_each_entry(sub_fdt, &kho_out.sub_fdts, l) {
- if (sub_fdt->fdt == fdt) {
- list_del(&sub_fdt->l);
- kfree(sub_fdt);
+ err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
+ if (err < 0)
+ return;
+
+ for (off = fdt_first_subnode(root_fdt, 0); off >= 0;
+ off = fdt_next_subnode(root_fdt, off)) {
+ const u64 *val;
+ int len;
+
+ val = fdt_getprop(root_fdt, off, PROP_SUB_FDT, &len);
+ if (!val || len != sizeof(phys_addr_t))
+ continue;
+
+ if ((phys_addr_t)*val == target_phys) {
+ fdt_del_node(root_fdt, off);
kho_debugfs_fdt_remove(&kho_out.dbg, fdt);
break;
}
}
+
+ fdt_pack(root_fdt);
}
EXPORT_SYMBOL_GPL(kho_remove_subtree);
@@ -1145,48 +1164,6 @@ void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
}
EXPORT_SYMBOL_GPL(kho_restore_vmalloc);
-static int __kho_finalize(void)
-{
- void *root = kho_out.fdt;
- struct kho_sub_fdt *fdt;
- u64 empty_mem_map = 0;
- int err;
-
- err = fdt_create(root, PAGE_SIZE);
- err |= fdt_finish_reservemap(root);
- err |= fdt_begin_node(root, "");
- err |= fdt_property_string(root, "compatible", KHO_FDT_COMPATIBLE);
- err |= fdt_property(root, PROP_PRESERVED_MEMORY_MAP, &empty_mem_map,
- sizeof(empty_mem_map));
- if (err)
- goto err_exit;
-
- mutex_lock(&kho_out.fdts_lock);
- list_for_each_entry(fdt, &kho_out.sub_fdts, l) {
- phys_addr_t phys = virt_to_phys(fdt->fdt);
-
- err |= fdt_begin_node(root, fdt->name);
- err |= fdt_property(root, PROP_SUB_FDT, &phys, sizeof(phys));
- err |= fdt_end_node(root);
- }
- mutex_unlock(&kho_out.fdts_lock);
-
- err |= fdt_end_node(root);
- err |= fdt_finish(root);
- if (err)
- goto err_exit;
-
- err = kho_mem_serialize(&kho_out);
- if (err)
- goto err_exit;
-
- return 0;
-
-err_exit:
- pr_err("Failed to convert KHO state tree: %d\n", err);
- return err;
-}
-
int kho_finalize(void)
{
int ret;
@@ -1195,12 +1172,7 @@ int kho_finalize(void)
return -EOPNOTSUPP;
guard(mutex)(&kho_out.lock);
- if (kho_out.finalized) {
- kho_update_memory_map(NULL);
- kho_out.finalized = false;
- }
-
- ret = __kho_finalize();
+ ret = kho_mem_serialize(&kho_out);
if (ret)
return ret;
@@ -1285,6 +1257,26 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
}
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
+static __init int kho_out_fdt_setup(void)
+{
+ void *root = kho_out.fdt;
+ u64 empty_mem_map = 0;
+ int err;
+
+ err = fdt_create(root, PAGE_SIZE);
+ err |= fdt_finish_reservemap(root);
+ err |= fdt_begin_node(root, "");
+ err |= fdt_property_string(root, "compatible", KHO_FDT_COMPATIBLE);
+ err |= fdt_property(root, PROP_PRESERVED_MEMORY_MAP, &empty_mem_map,
+ sizeof(empty_mem_map));
+ err |= fdt_end_node(root);
+ err |= fdt_finish(root);
+ if (err)
+ return err;
+
+ return kho_preserve_folio(virt_to_folio(kho_out.fdt));
+}
+
static __init int kho_init(void)
{
int err = 0;
@@ -1309,7 +1301,7 @@ static __init int kho_init(void)
if (err)
goto err_free_fdt;
- err = kho_preserve_folio(virt_to_folio(kho_out.fdt));
+ err = kho_out_fdt_setup();
if (err)
goto err_free_fdt;
--
2.52.0.rc1.455.g30608eb744-goog
On Fri, Nov 14 2025, Pasha Tatashin wrote: > Currently, sub-FDTs were tracked in a list (kho_out.sub_fdts) and the > final FDT is constructed entirely from scratch during kho_finalize(). > > We can maintain the FDT dynamically: > 1. Initialize a valid, empty FDT in kho_init(). > 2. Use fdt_add_subnode and fdt_setprop in kho_add_subtree to > update the FDT immediately when a subsystem registers. > 3. Use fdt_del_node in kho_remove_subtree to remove entries. > > This removes the need for the intermediate sub_fdts list and the > reconstruction logic in kho_finalize(). kho_finalize() now > only needs to trigger memory map serialization. > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> Reviewed-by: Pratyush Yadav <pratyush@kernel.org> [...] -- Regards, Pratyush Yadav
On Fri, Nov 14, 2025 at 10:53:54AM -0500, Pasha Tatashin wrote:
> Currently, sub-FDTs were tracked in a list (kho_out.sub_fdts) and the
> final FDT is constructed entirely from scratch during kho_finalize().
>
> We can maintain the FDT dynamically:
> 1. Initialize a valid, empty FDT in kho_init().
> 2. Use fdt_add_subnode and fdt_setprop in kho_add_subtree to
> update the FDT immediately when a subsystem registers.
> 3. Use fdt_del_node in kho_remove_subtree to remove entries.
>
> This removes the need for the intermediate sub_fdts list and the
> reconstruction logic in kho_finalize(). kho_finalize() now
> only needs to trigger memory map serialization.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
> kernel/liveupdate/kexec_handover.c | 144 ++++++++++++++---------------
> 1 file changed, 68 insertions(+), 76 deletions(-)
>
> diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> index 8ab77cb85ca9..822da961d4c9 100644
> --- a/kernel/liveupdate/kexec_handover.c
> +++ b/kernel/liveupdate/kexec_handover.c
> @@ -724,37 +713,67 @@ static void __init kho_reserve_scratch(void)
> */
> int kho_add_subtree(const char *name, void *fdt)
> {
> - struct kho_sub_fdt *sub_fdt;
> + phys_addr_t phys = virt_to_phys(fdt);
> + void *root_fdt = kho_out.fdt;
> + int err = -ENOMEM;
> + int off, fdt_err;
>
> - sub_fdt = kmalloc(sizeof(*sub_fdt), GFP_KERNEL);
> - if (!sub_fdt)
> - return -ENOMEM;
> + guard(mutex)(&kho_out.lock);
> +
> + fdt_err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
> + if (fdt_err < 0)
> + return err;
> - INIT_LIST_HEAD(&sub_fdt->l);
> - sub_fdt->name = name;
> - sub_fdt->fdt = fdt;
> + off = fdt_add_subnode(root_fdt, 0, name);
fdt_err = fdt_add_subnode();
and then we don't need off
> + if (off < 0) {
> + if (off == -FDT_ERR_EXISTS)
> + err = -EEXIST;
Is it really -ENOMEM for other FDT_ERR values?
> + goto out_pack;
> + }
> +
> + err = fdt_setprop(root_fdt, off, PROP_SUB_FDT, &phys, sizeof(phys));
> + if (err < 0)
> + goto out_pack;
>
> - guard(mutex)(&kho_out.fdts_lock);
> - list_add_tail(&sub_fdt->l, &kho_out.sub_fdts);
> WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
>
> - return 0;
> +out_pack:
> + fdt_pack(root_fdt);
> +
> + return err;
> }
> EXPORT_SYMBOL_GPL(kho_add_subtree);
--
Sincerely yours,
Mike.
On Fri, Nov 14, 2025 at 11:15 AM Mike Rapoport <rppt@kernel.org> wrote:
>
> On Fri, Nov 14, 2025 at 10:53:54AM -0500, Pasha Tatashin wrote:
> > Currently, sub-FDTs were tracked in a list (kho_out.sub_fdts) and the
> > final FDT is constructed entirely from scratch during kho_finalize().
> >
> > We can maintain the FDT dynamically:
> > 1. Initialize a valid, empty FDT in kho_init().
> > 2. Use fdt_add_subnode and fdt_setprop in kho_add_subtree to
> > update the FDT immediately when a subsystem registers.
> > 3. Use fdt_del_node in kho_remove_subtree to remove entries.
> >
> > This removes the need for the intermediate sub_fdts list and the
> > reconstruction logic in kho_finalize(). kho_finalize() now
> > only needs to trigger memory map serialization.
> >
> > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> > ---
> > kernel/liveupdate/kexec_handover.c | 144 ++++++++++++++---------------
> > 1 file changed, 68 insertions(+), 76 deletions(-)
> >
> > diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
> > index 8ab77cb85ca9..822da961d4c9 100644
> > --- a/kernel/liveupdate/kexec_handover.c
> > +++ b/kernel/liveupdate/kexec_handover.c
> > @@ -724,37 +713,67 @@ static void __init kho_reserve_scratch(void)
> > */
> > int kho_add_subtree(const char *name, void *fdt)
> > {
> > - struct kho_sub_fdt *sub_fdt;
> > + phys_addr_t phys = virt_to_phys(fdt);
> > + void *root_fdt = kho_out.fdt;
> > + int err = -ENOMEM;
> > + int off, fdt_err;
> >
> > - sub_fdt = kmalloc(sizeof(*sub_fdt), GFP_KERNEL);
> > - if (!sub_fdt)
> > - return -ENOMEM;
> > + guard(mutex)(&kho_out.lock);
> > +
> > + fdt_err = fdt_open_into(root_fdt, root_fdt, PAGE_SIZE);
> > + if (fdt_err < 0)
> > + return err;
> > - INIT_LIST_HEAD(&sub_fdt->l);
> > - sub_fdt->name = name;
> > - sub_fdt->fdt = fdt;
> > + off = fdt_add_subnode(root_fdt, 0, name);
>
> fdt_err = fdt_add_subnode();
>
> and then we don't need off
>
> > + if (off < 0) {
> > + if (off == -FDT_ERR_EXISTS)
> > + err = -EEXIST;
>
> Is it really -ENOMEM for other FDT_ERR values?
In practice, yes. There are some other errors like format mismatch,
magic values etc, but all of them are internal FDT problems. The only
error that really matters to users is the -ENOMEM one.
Pasha
>
> > + goto out_pack;
> > + }
> > +
> > + err = fdt_setprop(root_fdt, off, PROP_SUB_FDT, &phys, sizeof(phys));
> > + if (err < 0)
> > + goto out_pack;
> >
> > - guard(mutex)(&kho_out.fdts_lock);
> > - list_add_tail(&sub_fdt->l, &kho_out.sub_fdts);
> > WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
> >
> > - return 0;
> > +out_pack:
> > + fdt_pack(root_fdt);
> > +
> > + return err;
> > }
> > EXPORT_SYMBOL_GPL(kho_add_subtree);
>
> --
> Sincerely yours,
> Mike.
© 2016 - 2026 Red Hat, Inc.