mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-03-22 07:27:12 +08:00
kho: make debugfs interface optional
Patch series "liveupdate: Rework KHO for in-kernel users", v9. This series refactors the KHO framework to better support in-kernel users like the upcoming LUO. The current design, which relies on a notifier chain and debugfs for control, is too restrictive for direct programmatic use. The core of this rework is the removal of the notifier chain in favor of a direct registration API. This decouples clients from the shutdown-time finalization sequence, allowing them to manage their preserved state more flexibly and at any time. In support of this new model, this series also: - Makes the debugfs interface optional. - Introduces APIs to unpreserve memory and fixes a bug in the abort path where client state was being incorrectly discarded. Note that this is an interim step, as a more comprehensive fix is planned as part of the stateless KHO work [1]. - Moves all KHO code into a new kernel/liveupdate/ directory to consolidate live update components. This patch (of 9): Currently, KHO is controlled via debugfs interface, but once LUO is introduced, it can control KHO, and the debug interface becomes optional. Add a separate config CONFIG_KEXEC_HANDOVER_DEBUGFS that enables the debugfs interface, and allows to inspect the tree. Move all debugfs related code to a new file to keep the .c files clear of ifdefs. Link: https://lkml.kernel.org/r/20251101142325.1326536-1-pasha.tatashin@soleen.com Link: https://lkml.kernel.org/r/20251101142325.1326536-2-pasha.tatashin@soleen.com Link: https://lore.kernel.org/all/20251020100306.2709352-1-jasonmiu@google.com [1] Co-developed-by: Mike Rapoport (Microsoft) <rppt@kernel.org> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> Reviewed-by: Pratyush Yadav <pratyush@kernel.org> Cc: Alexander Graf <graf@amazon.com> Cc: Christian Brauner <brauner@kernel.org> Cc: Jason Gunthorpe <jgg@ziepe.ca> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Masahiro Yamada <masahiroy@kernel.org> Cc: Miguel Ojeda <ojeda@kernel.org> Cc: Randy Dunlap <rdunlap@infradead.org> Cc: Tejun Heo <tj@kernel.org> Cc: Changyuan Lyu <changyuanl@google.com> Cc: Jason Gunthorpe <jgg@nvidia.com> Cc: Simon Horman <horms@kernel.org> Cc: Zhu Yanjun <yanjun.zhu@linux.dev> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
committed by
Andrew Morton
parent
e6fbd1759c
commit
03d3963464
@@ -13799,7 +13799,7 @@ S: Maintained
|
||||
F: Documentation/admin-guide/mm/kho.rst
|
||||
F: Documentation/core-api/kho/*
|
||||
F: include/linux/kexec_handover.h
|
||||
F: kernel/kexec_handover.c
|
||||
F: kernel/kexec_handover*
|
||||
F: lib/test_kho.c
|
||||
F: tools/testing/selftests/kho/
|
||||
|
||||
|
||||
@@ -100,7 +100,6 @@ config KEXEC_HANDOVER
|
||||
depends on !DEFERRED_STRUCT_PAGE_INIT
|
||||
select MEMBLOCK_KHO_SCRATCH
|
||||
select KEXEC_FILE
|
||||
select DEBUG_FS
|
||||
select LIBFDT
|
||||
select CMA
|
||||
help
|
||||
@@ -118,6 +117,17 @@ config KEXEC_HANDOVER_DEBUG
|
||||
scenarios and the extra code might be adding overhead it is
|
||||
only optionally enabled.
|
||||
|
||||
config KEXEC_HANDOVER_DEBUGFS
|
||||
bool "kexec handover debugfs interface"
|
||||
default KEXEC_HANDOVER
|
||||
depends on KEXEC_HANDOVER
|
||||
select DEBUG_FS
|
||||
help
|
||||
Allow to control kexec handover device tree via debugfs
|
||||
interface, i.e. finalize the state or aborting the finalization.
|
||||
Also, enables inspecting the KHO fdt trees with the debugfs binary
|
||||
blobs.
|
||||
|
||||
config CRASH_DUMP
|
||||
bool "kernel crash dumps"
|
||||
default ARCH_DEFAULT_CRASH_DUMP
|
||||
|
||||
@@ -84,6 +84,7 @@ obj-$(CONFIG_KEXEC_FILE) += kexec_file.o
|
||||
obj-$(CONFIG_KEXEC_ELF) += kexec_elf.o
|
||||
obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o
|
||||
obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o
|
||||
obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS) += kexec_handover_debugfs.o
|
||||
obj-$(CONFIG_BACKTRACE_SELF_TEST) += backtracetest.o
|
||||
obj-$(CONFIG_COMPAT) += compat.o
|
||||
obj-$(CONFIG_CGROUPS) += cgroup/
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/cma.h>
|
||||
#include <linux/count_zeros.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/kexec.h>
|
||||
#include <linux/kexec_handover.h>
|
||||
#include <linux/libfdt.h>
|
||||
@@ -30,6 +29,7 @@
|
||||
*/
|
||||
#include "../mm/internal.h"
|
||||
#include "kexec_internal.h"
|
||||
#include "kexec_handover_internal.h"
|
||||
|
||||
#define KHO_FDT_COMPATIBLE "kho-v1"
|
||||
#define PROP_PRESERVED_MEMORY_MAP "preserved-memory-map"
|
||||
@@ -105,8 +105,6 @@ struct khoser_mem_chunk;
|
||||
|
||||
struct kho_serialization {
|
||||
struct page *fdt;
|
||||
struct list_head fdt_list;
|
||||
struct dentry *sub_fdt_dir;
|
||||
struct kho_mem_track track;
|
||||
/* First chunk of serialized preserved memory map */
|
||||
struct khoser_mem_chunk *preserved_mem_map;
|
||||
@@ -114,20 +112,16 @@ struct kho_serialization {
|
||||
|
||||
struct kho_out {
|
||||
struct blocking_notifier_head chain_head;
|
||||
|
||||
struct dentry *dir;
|
||||
|
||||
struct mutex lock; /* protects KHO FDT finalization */
|
||||
|
||||
struct kho_serialization ser;
|
||||
bool finalized;
|
||||
struct kho_debugfs dbg;
|
||||
};
|
||||
|
||||
static struct kho_out kho_out = {
|
||||
.chain_head = BLOCKING_NOTIFIER_INIT(kho_out.chain_head),
|
||||
.lock = __MUTEX_INITIALIZER(kho_out.lock),
|
||||
.ser = {
|
||||
.fdt_list = LIST_HEAD_INIT(kho_out.ser.fdt_list),
|
||||
.track = {
|
||||
.orders = XARRAY_INIT(kho_out.ser.track.orders, 0),
|
||||
},
|
||||
@@ -674,37 +668,6 @@ err_disable_kho:
|
||||
kho_enable = false;
|
||||
}
|
||||
|
||||
struct fdt_debugfs {
|
||||
struct list_head list;
|
||||
struct debugfs_blob_wrapper wrapper;
|
||||
struct dentry *file;
|
||||
};
|
||||
|
||||
static int kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
|
||||
const char *name, const void *fdt)
|
||||
{
|
||||
struct fdt_debugfs *f;
|
||||
struct dentry *file;
|
||||
|
||||
f = kmalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
f->wrapper.data = (void *)fdt;
|
||||
f->wrapper.size = fdt_totalsize(fdt);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* kho_add_subtree - record the physical address of a sub FDT in KHO root tree.
|
||||
* @ser: serialization control object passed by KHO notifiers.
|
||||
@@ -716,7 +679,8 @@ static int kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
|
||||
* by KHO for the new kernel to retrieve it after kexec.
|
||||
*
|
||||
* A debugfs blob entry is also created at
|
||||
* ``/sys/kernel/debug/kho/out/sub_fdts/@name``.
|
||||
* ``/sys/kernel/debug/kho/out/sub_fdts/@name`` when kernel is configured with
|
||||
* CONFIG_KEXEC_HANDOVER_DEBUGFS
|
||||
*
|
||||
* Return: 0 on success, error code on failure
|
||||
*/
|
||||
@@ -733,7 +697,7 @@ int kho_add_subtree(struct kho_serialization *ser, const char *name, void *fdt)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return kho_debugfs_fdt_add(&ser->fdt_list, ser->sub_fdt_dir, name, fdt);
|
||||
return kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kho_add_subtree);
|
||||
|
||||
@@ -1065,30 +1029,7 @@ err_free_pages_array:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kho_restore_vmalloc);
|
||||
|
||||
/* Handling for debug/kho/out */
|
||||
|
||||
static struct dentry *debugfs_root;
|
||||
|
||||
static int kho_out_update_debugfs_fdt(void)
|
||||
{
|
||||
int err = 0;
|
||||
struct fdt_debugfs *ff, *tmp;
|
||||
|
||||
if (kho_out.finalized) {
|
||||
err = kho_debugfs_fdt_add(&kho_out.ser.fdt_list, kho_out.dir,
|
||||
"fdt", page_to_virt(kho_out.ser.fdt));
|
||||
} else {
|
||||
list_for_each_entry_safe(ff, tmp, &kho_out.ser.fdt_list, list) {
|
||||
debugfs_remove(ff->file);
|
||||
list_del(&ff->list);
|
||||
kfree(ff);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int kho_abort(void)
|
||||
static int __kho_abort(void)
|
||||
{
|
||||
int err;
|
||||
unsigned long order;
|
||||
@@ -1121,7 +1062,28 @@ static int kho_abort(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int kho_finalize(void)
|
||||
int kho_abort(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!kho_enable)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
guard(mutex)(&kho_out.lock);
|
||||
if (!kho_out.finalized)
|
||||
return -ENOENT;
|
||||
|
||||
ret = __kho_abort();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
kho_out.finalized = false;
|
||||
kho_debugfs_cleanup(&kho_out.dbg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __kho_finalize(void)
|
||||
{
|
||||
int err = 0;
|
||||
u64 *preserved_mem_map;
|
||||
@@ -1164,118 +1126,46 @@ static int kho_finalize(void)
|
||||
abort:
|
||||
if (err) {
|
||||
pr_err("Failed to convert KHO state tree: %d\n", err);
|
||||
kho_abort();
|
||||
__kho_abort();
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int kho_out_finalize_get(void *data, u64 *val)
|
||||
int kho_finalize(void)
|
||||
{
|
||||
mutex_lock(&kho_out.lock);
|
||||
*val = kho_out.finalized;
|
||||
mutex_unlock(&kho_out.lock);
|
||||
int ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (!kho_enable)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
static int kho_out_finalize_set(void *data, u64 _val)
|
||||
{
|
||||
int ret = 0;
|
||||
bool val = !!_val;
|
||||
|
||||
mutex_lock(&kho_out.lock);
|
||||
|
||||
if (val == kho_out.finalized) {
|
||||
if (kho_out.finalized)
|
||||
ret = -EEXIST;
|
||||
else
|
||||
ret = -ENOENT;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (val)
|
||||
ret = kho_finalize();
|
||||
else
|
||||
ret = kho_abort();
|
||||
guard(mutex)(&kho_out.lock);
|
||||
if (kho_out.finalized)
|
||||
return -EEXIST;
|
||||
|
||||
ret = __kho_finalize();
|
||||
if (ret)
|
||||
goto unlock;
|
||||
return ret;
|
||||
|
||||
kho_out.finalized = val;
|
||||
ret = kho_out_update_debugfs_fdt();
|
||||
kho_out.finalized = true;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&kho_out.lock);
|
||||
return ret;
|
||||
return kho_debugfs_fdt_add(&kho_out.dbg, "fdt",
|
||||
page_to_virt(kho_out.ser.fdt), true);
|
||||
}
|
||||
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(fops_kho_out_finalize, kho_out_finalize_get,
|
||||
kho_out_finalize_set, "%llu\n");
|
||||
|
||||
static int scratch_phys_show(struct seq_file *m, void *v)
|
||||
bool kho_finalized(void)
|
||||
{
|
||||
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);
|
||||
|
||||
static __init int kho_out_debugfs_init(void)
|
||||
{
|
||||
struct dentry *dir, *f, *sub_fdt_dir;
|
||||
|
||||
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;
|
||||
|
||||
f = debugfs_create_file("finalize", 0600, dir, NULL,
|
||||
&fops_kho_out_finalize);
|
||||
if (IS_ERR(f))
|
||||
goto err_rmdir;
|
||||
|
||||
kho_out.dir = dir;
|
||||
kho_out.ser.sub_fdt_dir = sub_fdt_dir;
|
||||
return 0;
|
||||
|
||||
err_rmdir:
|
||||
debugfs_remove_recursive(dir);
|
||||
return -ENOENT;
|
||||
guard(mutex)(&kho_out.lock);
|
||||
return kho_out.finalized;
|
||||
}
|
||||
|
||||
struct kho_in {
|
||||
struct dentry *dir;
|
||||
phys_addr_t fdt_phys;
|
||||
phys_addr_t scratch_phys;
|
||||
struct list_head fdt_list;
|
||||
struct kho_debugfs dbg;
|
||||
};
|
||||
|
||||
static struct kho_in kho_in = {
|
||||
.fdt_list = LIST_HEAD_INIT(kho_in.fdt_list),
|
||||
};
|
||||
|
||||
static const void *kho_get_fdt(void)
|
||||
@@ -1339,56 +1229,6 @@ int kho_retrieve_subtree(const char *name, phys_addr_t *phys)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kho_retrieve_subtree);
|
||||
|
||||
/* Handling for debugfs/kho/in */
|
||||
|
||||
static __init int kho_in_debugfs_init(const void *fdt)
|
||||
{
|
||||
struct dentry *sub_fdt_dir;
|
||||
int err, child;
|
||||
|
||||
kho_in.dir = debugfs_create_dir("in", debugfs_root);
|
||||
if (IS_ERR(kho_in.dir))
|
||||
return PTR_ERR(kho_in.dir);
|
||||
|
||||
sub_fdt_dir = debugfs_create_dir("sub_fdts", kho_in.dir);
|
||||
if (IS_ERR(sub_fdt_dir)) {
|
||||
err = PTR_ERR(sub_fdt_dir);
|
||||
goto err_rmdir;
|
||||
}
|
||||
|
||||
err = kho_debugfs_fdt_add(&kho_in.fdt_list, kho_in.dir, "fdt", 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;
|
||||
|
||||
fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
|
||||
if (!fdt_phys)
|
||||
continue;
|
||||
if (len != sizeof(*fdt_phys)) {
|
||||
pr_warn("node `%s`'s prop `fdt` has invalid length: %d\n",
|
||||
name, len);
|
||||
continue;
|
||||
}
|
||||
err = kho_debugfs_fdt_add(&kho_in.fdt_list, sub_fdt_dir, name,
|
||||
phys_to_virt(*fdt_phys));
|
||||
if (err) {
|
||||
pr_warn("failed to add fdt `%s` to debugfs: %d\n", name,
|
||||
err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_rmdir:
|
||||
debugfs_remove_recursive(kho_in.dir);
|
||||
return err;
|
||||
}
|
||||
|
||||
static __init int kho_init(void)
|
||||
{
|
||||
int err = 0;
|
||||
@@ -1403,27 +1243,16 @@ static __init int kho_init(void)
|
||||
goto err_free_scratch;
|
||||
}
|
||||
|
||||
debugfs_root = debugfs_create_dir("kho", NULL);
|
||||
if (IS_ERR(debugfs_root)) {
|
||||
err = -ENOENT;
|
||||
err = kho_debugfs_init();
|
||||
if (err)
|
||||
goto err_free_fdt;
|
||||
}
|
||||
|
||||
err = kho_out_debugfs_init();
|
||||
err = kho_out_debugfs_init(&kho_out.dbg);
|
||||
if (err)
|
||||
goto err_free_fdt;
|
||||
|
||||
if (fdt) {
|
||||
err = kho_in_debugfs_init(fdt);
|
||||
/*
|
||||
* 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: %d\n",
|
||||
err);
|
||||
|
||||
kho_in_debugfs_init(&kho_in.dbg, fdt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
216
kernel/kexec_handover_debugfs.c
Normal file
216
kernel/kexec_handover_debugfs.c
Normal file
@@ -0,0 +1,216 @@
|
||||
// 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 "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_fdt_add(struct list_head *list, struct dentry *dir,
|
||||
const char *name, const void *fdt)
|
||||
{
|
||||
struct fdt_debugfs *f;
|
||||
struct dentry *file;
|
||||
|
||||
f = kmalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
f->wrapper.data = (void *)fdt;
|
||||
f->wrapper.size = fdt_totalsize(fdt);
|
||||
|
||||
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_fdt_add(struct kho_debugfs *dbg, const char *name,
|
||||
const void *fdt, bool root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
if (root)
|
||||
dir = dbg->dir;
|
||||
else
|
||||
dir = dbg->sub_fdt_dir;
|
||||
|
||||
return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt);
|
||||
}
|
||||
|
||||
void kho_debugfs_cleanup(struct kho_debugfs *dbg)
|
||||
{
|
||||
struct fdt_debugfs *ff, *tmp;
|
||||
|
||||
list_for_each_entry_safe(ff, tmp, &dbg->fdt_list, list) {
|
||||
debugfs_remove(ff->file);
|
||||
list_del(&ff->list);
|
||||
kfree(ff);
|
||||
}
|
||||
}
|
||||
|
||||
static int kho_out_finalize_get(void *data, u64 *val)
|
||||
{
|
||||
*val = kho_finalized();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kho_out_finalize_set(void *data, u64 val)
|
||||
{
|
||||
if (val)
|
||||
return kho_finalize();
|
||||
else
|
||||
return kho_abort();
|
||||
}
|
||||
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(kho_out_finalize_fops, kho_out_finalize_get,
|
||||
kho_out_finalize_set, "%llu\n");
|
||||
|
||||
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_fdt_add(&dbg->fdt_list, dir, "fdt", 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;
|
||||
|
||||
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: %d\n", name,
|
||||
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: %d\n", 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;
|
||||
|
||||
f = debugfs_create_file("finalize", 0600, dir, NULL,
|
||||
&kho_out_finalize_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;
|
||||
}
|
||||
@@ -3,11 +3,46 @@
|
||||
#define LINUX_KEXEC_HANDOVER_INTERNAL_H
|
||||
|
||||
#include <linux/kexec_handover.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifdef CONFIG_KEXEC_HANDOVER_DEBUGFS
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
struct kho_debugfs {
|
||||
struct dentry *dir;
|
||||
struct dentry *sub_fdt_dir;
|
||||
struct list_head fdt_list;
|
||||
};
|
||||
|
||||
#else
|
||||
struct kho_debugfs {};
|
||||
#endif
|
||||
|
||||
extern struct kho_scratch *kho_scratch;
|
||||
extern unsigned int kho_scratch_cnt;
|
||||
|
||||
bool kho_finalized(void);
|
||||
int kho_finalize(void);
|
||||
int kho_abort(void);
|
||||
|
||||
#ifdef CONFIG_KEXEC_HANDOVER_DEBUGFS
|
||||
int kho_debugfs_init(void);
|
||||
void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
|
||||
int kho_out_debugfs_init(struct kho_debugfs *dbg);
|
||||
int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
|
||||
const void *fdt, bool root);
|
||||
void kho_debugfs_cleanup(struct kho_debugfs *dbg);
|
||||
#else
|
||||
static inline int kho_debugfs_init(void) { return 0; }
|
||||
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_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
|
||||
const void *fdt, bool root) { return 0; }
|
||||
static inline void kho_debugfs_cleanup(struct kho_debugfs *dbg) {}
|
||||
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
|
||||
|
||||
#ifdef CONFIG_KEXEC_HANDOVER_DEBUG
|
||||
bool kho_scratch_overlap(phys_addr_t phys, size_t size);
|
||||
#else
|
||||
|
||||
@@ -59,6 +59,7 @@ function build_kernel() {
|
||||
tee "$kconfig" > "$kho_config" <<EOF
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_KEXEC_HANDOVER=y
|
||||
CONFIG_KEXEC_HANDOVER_DEBUGFS=y
|
||||
CONFIG_TEST_KEXEC_HANDOVER=y
|
||||
CONFIG_DEBUG_KERNEL=y
|
||||
CONFIG_DEBUG_VM=y
|
||||
|
||||
Reference in New Issue
Block a user