[PATCH v1 1/8] kho: expose physical address of outgoing FDT

From: Pasha Tatashin

Date: Sun Oct 18 2026 - 22:14:06 EST


Expose the physical address of the outgoing KHO FDT via
/sys/kernel/debug/kho/out/fdt_phys (analogous to bootmem_phys) so
that userspace tooling can locate the root KHO FDT in physical memory.

Also add kho_debugfs_cleanup() and remove the KHO debugfs directory
if kho_init() fails after initializing debugfs, ensuring fdt_phys
does not retain a dangling pointer to a freed FDT.

This is going to be used by the upcoming KHO read-only mode.

Signed-off-by: Pasha Tatashin <pasha.tatashin@xxxxxxxxxx>
---
Documentation/admin-guide/mm/kho.rst | 3 +++
kernel/liveupdate/kexec_handover.c | 10 ++++++----
kernel/liveupdate/kexec_handover_debugfs.c | 22 ++++++++++++++++++++-
kernel/liveupdate/kexec_handover_internal.h | 7 +++++--
4 files changed, 35 insertions(+), 7 deletions(-)

diff --git a/Documentation/admin-guide/mm/kho.rst b/Documentation/admin-guide/mm/kho.rst
index 902d171a780a..2ba884c620c5 100644
--- a/Documentation/admin-guide/mm/kho.rst
+++ b/Documentation/admin-guide/mm/kho.rst
@@ -96,6 +96,9 @@ stabilized.
current KHO state in this file. Kexec user space tooling can use this
as input file for the KHO payload image.

+``/sys/kernel/debug/kho/out/fdt_phys``
+ Physical address of the outgoing KHO FDT blob.
+
``/sys/kernel/debug/kho/out/bootmem_len``
Lengths of KHO bootmem regions, which are physically contiguous
memory regions that will always stay available for future kexec
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 6f1f55fe3181..ce5983abf181 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1851,17 +1851,17 @@ static __init int kho_init(void)
if (err)
goto err_free_fdt;

- err = kho_out_debugfs_init(&kho_out.dbg);
+ err = kho_out_debugfs_init(&kho_out.dbg, kho_out.fdt);
if (err)
- goto err_free_fdt;
+ goto err_cleanup_debugfs;

err = kho_out_fdt_setup();
if (err)
- goto err_free_fdt;
+ goto err_cleanup_debugfs;

err = kho_kexec_metadata_init(fdt);
if (err)
- goto err_free_fdt;
+ goto err_cleanup_debugfs;

if (fdt) {
kho_in_debugfs_init(&kho_in.dbg, fdt);
@@ -1893,6 +1893,8 @@ static __init int kho_init(void)

return 0;

+err_cleanup_debugfs:
+ kho_debugfs_cleanup();
err_free_fdt:
kho_unpreserve_free(kho_out.fdt);
err_free_kho_radix_tree:
diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index 7d74477da0b7..db3830829398 100644
--- a/kernel/liveupdate/kexec_handover_debugfs.c
+++ b/kernel/liveupdate/kexec_handover_debugfs.c
@@ -95,6 +95,15 @@ static int bootmem_len_show(struct seq_file *m, void *v)
}
DEFINE_SHOW_ATTRIBUTE(bootmem_len);

+static int fdt_phys_show(struct seq_file *m, void *v)
+{
+ void *fdt = m->private;
+
+ seq_printf(m, "0x%llx\n", (unsigned long long)virt_to_phys(fdt));
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(fdt_phys);
+
__init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
{
struct dentry *dir, *sub_fdt_dir;
@@ -172,7 +181,7 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
}
}

-__init int kho_out_debugfs_init(struct kho_debugfs *dbg)
+__init int kho_out_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
{
struct dentry *dir, *f, *sub_fdt_dir;

@@ -196,6 +205,11 @@ __init int kho_out_debugfs_init(struct kho_debugfs *dbg)
if (IS_ERR(f))
goto err_rmdir;

+ f = debugfs_create_file("fdt_phys", 0400, dir, (void *)fdt,
+ &fdt_phys_fops);
+ if (IS_ERR(f))
+ goto err_rmdir;
+
dbg->dir = dir;
dbg->sub_fdt_dir = sub_fdt_dir;
return 0;
@@ -212,3 +226,9 @@ __init int kho_debugfs_init(void)
return -ENOENT;
return 0;
}
+
+__init void kho_debugfs_cleanup(void)
+{
+ debugfs_remove_recursive(debugfs_root);
+ debugfs_root = NULL;
+}
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index e9685e1ef6d7..38181076f603 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -24,16 +24,19 @@ extern unsigned int kho_bootmem_cnt;

#ifdef CONFIG_KEXEC_HANDOVER_DEBUGFS
int kho_debugfs_init(void);
+void kho_debugfs_cleanup(void);
void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
-int kho_out_debugfs_init(struct kho_debugfs *dbg);
+int kho_out_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
int kho_debugfs_blob_add(struct kho_debugfs *dbg, const char *name,
const void *blob, size_t size, bool root);
void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob);
#else
static inline int kho_debugfs_init(void) { return 0; }
+static inline void kho_debugfs_cleanup(void) { }
static inline void kho_in_debugfs_init(struct kho_debugfs *dbg,
const void *fdt) { }
-static inline int kho_out_debugfs_init(struct kho_debugfs *dbg) { return 0; }
+static inline int kho_out_debugfs_init(struct kho_debugfs *dbg,
+ const void *fdt) { return 0; }
static inline int kho_debugfs_blob_add(struct kho_debugfs *dbg,
const char *name, const void *blob,
size_t size, bool root) { return 0; }
--
2.56.0.176.g3575e65682-goog