linux/kernel/liveupdate/kexec_handover_debugfs.c
Breno Leitao 85e4139282 kho: persist blob size in KHO FDT
kho_add_subtree() accepts a size parameter but only forwards it to
debugfs.  The size is not persisted in the KHO FDT, so it is lost across
kexec.  This makes it impossible for the incoming kernel to determine the
blob size without understanding the blob format.

Store the blob size as a "blob-size" property in the KHO FDT alongside the
"preserved-data" physical address.  This allows the receiving kernel to
recover the size for any blob regardless of format.

Also extend kho_retrieve_subtree() with an optional size output parameter
so callers can learn the blob size without needing to understand the blob
format.  Update all callers to pass NULL for the new parameter.

Link: https://lore.kernel.org/20260316-kho-v9-3-ed6dcd951988@debian.org
Signed-off-by: Breno Leitao <leitao@debian.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
Cc: Alexander Graf <graf@amazon.com>
Cc: David Hildenbrand <david@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: "Liam R. Howlett" <Liam.Howlett@oracle.com>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: SeongJae Park <sj@kernel.org>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
2026-04-18 00:10:48 -07:00

205 lines
4.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* kexec_handover_debugfs.c - kexec handover debugfs interfaces
* Copyright (C) 2023 Alexander Graf <graf@amazon.com>
* Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <rppt@kernel.org>
* Copyright (C) 2025 Google LLC, Changyuan Lyu <changyuanl@google.com>
* Copyright (C) 2025 Google LLC, Pasha Tatashin <pasha.tatashin@soleen.com>
*/
#define pr_fmt(fmt) "KHO: " fmt
#include <linux/init.h>
#include <linux/io.h>
#include <linux/libfdt.h>
#include <linux/mm.h>
#include <linux/kho/abi/kexec_handover.h>
#include "kexec_handover_internal.h"
static struct dentry *debugfs_root;
struct fdt_debugfs {
struct list_head list;
struct debugfs_blob_wrapper wrapper;
struct dentry *file;
};
static int __kho_debugfs_blob_add(struct list_head *list, struct dentry *dir,
const char *name, const void *blob,
size_t size)
{
struct fdt_debugfs *f;
struct dentry *file;
f = kmalloc_obj(*f);
if (!f)
return -ENOMEM;
f->wrapper.data = (void *)blob;
f->wrapper.size = size;
file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
if (IS_ERR(file)) {
kfree(f);
return PTR_ERR(file);
}
f->file = file;
list_add(&f->list, list);
return 0;
}
int kho_debugfs_blob_add(struct kho_debugfs *dbg, const char *name,
const void *blob, size_t size, bool root)
{
struct dentry *dir;
if (root)
dir = dbg->dir;
else
dir = dbg->sub_fdt_dir;
return __kho_debugfs_blob_add(&dbg->fdt_list, dir, name, blob, size);
}
void kho_debugfs_blob_remove(struct kho_debugfs *dbg, void *blob)
{
struct fdt_debugfs *ff;
list_for_each_entry(ff, &dbg->fdt_list, list) {
if (ff->wrapper.data == blob) {
debugfs_remove(ff->file);
list_del(&ff->list);
kfree(ff);
break;
}
}
}
static int scratch_phys_show(struct seq_file *m, void *v)
{
for (int i = 0; i < kho_scratch_cnt; i++)
seq_printf(m, "0x%llx\n", kho_scratch[i].addr);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(scratch_phys);
static int scratch_len_show(struct seq_file *m, void *v)
{
for (int i = 0; i < kho_scratch_cnt; i++)
seq_printf(m, "0x%llx\n", kho_scratch[i].size);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(scratch_len);
__init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
{
struct dentry *dir, *sub_fdt_dir;
int err, child;
INIT_LIST_HEAD(&dbg->fdt_list);
dir = debugfs_create_dir("in", debugfs_root);
if (IS_ERR(dir)) {
err = PTR_ERR(dir);
goto err_out;
}
sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
if (IS_ERR(sub_fdt_dir)) {
err = PTR_ERR(sub_fdt_dir);
goto err_rmdir;
}
err = __kho_debugfs_blob_add(&dbg->fdt_list, dir, "fdt", fdt,
fdt_totalsize(fdt));
if (err)
goto err_rmdir;
fdt_for_each_subnode(child, fdt, 0) {
int len = 0;
const char *name = fdt_get_name(fdt, child, NULL);
const u64 *fdt_phys;
void *sub_fdt;
fdt_phys = fdt_getprop(fdt, child,
KHO_SUB_TREE_PROP_NAME, &len);
if (!fdt_phys)
continue;
if (len != sizeof(*fdt_phys)) {
pr_warn("node %s prop fdt has invalid length: %d\n",
name, len);
continue;
}
sub_fdt = phys_to_virt(*fdt_phys);
err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name,
sub_fdt, fdt_totalsize(sub_fdt));
if (err) {
pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
ERR_PTR(err));
continue;
}
}
dbg->dir = dir;
dbg->sub_fdt_dir = sub_fdt_dir;
return;
err_rmdir:
debugfs_remove_recursive(dir);
err_out:
/*
* Failure to create /sys/kernel/debug/kho/in does not prevent
* reviving state from KHO and setting up KHO for the next
* kexec.
*/
if (err) {
pr_err("failed exposing handover FDT in debugfs: %pe\n",
ERR_PTR(err));
}
}
__init int kho_out_debugfs_init(struct kho_debugfs *dbg)
{
struct dentry *dir, *f, *sub_fdt_dir;
INIT_LIST_HEAD(&dbg->fdt_list);
dir = debugfs_create_dir("out", debugfs_root);
if (IS_ERR(dir))
return -ENOMEM;
sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
if (IS_ERR(sub_fdt_dir))
goto err_rmdir;
f = debugfs_create_file("scratch_phys", 0400, dir, NULL,
&scratch_phys_fops);
if (IS_ERR(f))
goto err_rmdir;
f = debugfs_create_file("scratch_len", 0400, dir, NULL,
&scratch_len_fops);
if (IS_ERR(f))
goto err_rmdir;
dbg->dir = dir;
dbg->sub_fdt_dir = sub_fdt_dir;
return 0;
err_rmdir:
debugfs_remove_recursive(dir);
return -ENOENT;
}
__init int kho_debugfs_init(void)
{
debugfs_root = debugfs_create_dir("kho", NULL);
if (IS_ERR(debugfs_root))
return -ENOENT;
return 0;
}