Contributors: 8
Author Tokens Token Proportion Commits Commit Proportion
Pavel Tatashin 696 75.57% 2 15.38%
Alexander Graf 115 12.49% 2 15.38%
Breno Leitão 80 8.69% 4 30.77%
Mike Rapoport 16 1.74% 1 7.69%
Zhu Yanjun 9 0.98% 1 7.69%
Jason Miu 3 0.33% 1 7.69%
Thomas Gleixner 1 0.11% 1 7.69%
Kees Cook 1 0.11% 1 7.69%
Total 921 13


// 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 *blob_phys;
		const u64 *blob_size;
		void *blob;

		blob_phys = fdt_getprop(fdt, child,
					KHO_SUB_TREE_PROP_NAME, &len);
		if (!blob_phys)
			continue;
		if (len != sizeof(*blob_phys)) {
			pr_warn("node %s prop %s has invalid length: %d\n",
				name, KHO_SUB_TREE_PROP_NAME, len);
			continue;
		}

		blob_size = fdt_getprop(fdt, child,
					KHO_SUB_TREE_SIZE_PROP_NAME, &len);
		if (!blob_size || len != sizeof(*blob_size)) {
			pr_warn("node %s missing or invalid %s property\n",
				name, KHO_SUB_TREE_SIZE_PROP_NAME);
			continue;
		}

		blob = phys_to_virt(*blob_phys);
		err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name,
					     blob, *blob_size);
		if (err) {
			pr_warn("failed to add blob %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;
}