xref: /linux/kernel/liveupdate/kexec_handover_debugfs.c (revision aec2f682d47c54ef434b2d440992626d80b1ebdc)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * kexec_handover_debugfs.c - kexec handover debugfs interfaces
4  * Copyright (C) 2023 Alexander Graf <graf@amazon.com>
5  * Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <rppt@kernel.org>
6  * Copyright (C) 2025 Google LLC, Changyuan Lyu <changyuanl@google.com>
7  * Copyright (C) 2025 Google LLC, Pasha Tatashin <pasha.tatashin@soleen.com>
8  */
9 
10 #define pr_fmt(fmt) "KHO: " fmt
11 
12 #include <linux/init.h>
13 #include <linux/io.h>
14 #include <linux/libfdt.h>
15 #include <linux/mm.h>
16 #include <linux/kho/abi/kexec_handover.h>
17 #include "kexec_handover_internal.h"
18 
19 static struct dentry *debugfs_root;
20 
21 struct fdt_debugfs {
22 	struct list_head list;
23 	struct debugfs_blob_wrapper wrapper;
24 	struct dentry *file;
25 };
26 
27 static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
28 				 const char *name, const void *fdt)
29 {
30 	struct fdt_debugfs *f;
31 	struct dentry *file;
32 
33 	f = kmalloc_obj(*f);
34 	if (!f)
35 		return -ENOMEM;
36 
37 	f->wrapper.data = (void *)fdt;
38 	f->wrapper.size = fdt_totalsize(fdt);
39 
40 	file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
41 	if (IS_ERR(file)) {
42 		kfree(f);
43 		return PTR_ERR(file);
44 	}
45 
46 	f->file = file;
47 	list_add(&f->list, list);
48 
49 	return 0;
50 }
51 
52 int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
53 			const void *fdt, bool root)
54 {
55 	struct dentry *dir;
56 
57 	if (root)
58 		dir = dbg->dir;
59 	else
60 		dir = dbg->sub_fdt_dir;
61 
62 	return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt);
63 }
64 
65 void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt)
66 {
67 	struct fdt_debugfs *ff;
68 
69 	list_for_each_entry(ff, &dbg->fdt_list, list) {
70 		if (ff->wrapper.data == fdt) {
71 			debugfs_remove(ff->file);
72 			list_del(&ff->list);
73 			kfree(ff);
74 			break;
75 		}
76 	}
77 }
78 
79 static int scratch_phys_show(struct seq_file *m, void *v)
80 {
81 	for (int i = 0; i < kho_scratch_cnt; i++)
82 		seq_printf(m, "0x%llx\n", kho_scratch[i].addr);
83 
84 	return 0;
85 }
86 DEFINE_SHOW_ATTRIBUTE(scratch_phys);
87 
88 static int scratch_len_show(struct seq_file *m, void *v)
89 {
90 	for (int i = 0; i < kho_scratch_cnt; i++)
91 		seq_printf(m, "0x%llx\n", kho_scratch[i].size);
92 
93 	return 0;
94 }
95 DEFINE_SHOW_ATTRIBUTE(scratch_len);
96 
97 __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
98 {
99 	struct dentry *dir, *sub_fdt_dir;
100 	int err, child;
101 
102 	INIT_LIST_HEAD(&dbg->fdt_list);
103 
104 	dir = debugfs_create_dir("in", debugfs_root);
105 	if (IS_ERR(dir)) {
106 		err = PTR_ERR(dir);
107 		goto err_out;
108 	}
109 
110 	sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
111 	if (IS_ERR(sub_fdt_dir)) {
112 		err = PTR_ERR(sub_fdt_dir);
113 		goto err_rmdir;
114 	}
115 
116 	err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt);
117 	if (err)
118 		goto err_rmdir;
119 
120 	fdt_for_each_subnode(child, fdt, 0) {
121 		int len = 0;
122 		const char *name = fdt_get_name(fdt, child, NULL);
123 		const u64 *fdt_phys;
124 
125 		fdt_phys = fdt_getprop(fdt, child, KHO_FDT_SUB_TREE_PROP_NAME, &len);
126 		if (!fdt_phys)
127 			continue;
128 		if (len != sizeof(*fdt_phys)) {
129 			pr_warn("node %s prop fdt has invalid length: %d\n",
130 				name, len);
131 			continue;
132 		}
133 		err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
134 					    phys_to_virt(*fdt_phys));
135 		if (err) {
136 			pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
137 				ERR_PTR(err));
138 			continue;
139 		}
140 	}
141 
142 	dbg->dir = dir;
143 	dbg->sub_fdt_dir = sub_fdt_dir;
144 
145 	return;
146 err_rmdir:
147 	debugfs_remove_recursive(dir);
148 err_out:
149 	/*
150 	 * Failure to create /sys/kernel/debug/kho/in does not prevent
151 	 * reviving state from KHO and setting up KHO for the next
152 	 * kexec.
153 	 */
154 	if (err) {
155 		pr_err("failed exposing handover FDT in debugfs: %pe\n",
156 		       ERR_PTR(err));
157 	}
158 }
159 
160 __init int kho_out_debugfs_init(struct kho_debugfs *dbg)
161 {
162 	struct dentry *dir, *f, *sub_fdt_dir;
163 
164 	INIT_LIST_HEAD(&dbg->fdt_list);
165 
166 	dir = debugfs_create_dir("out", debugfs_root);
167 	if (IS_ERR(dir))
168 		return -ENOMEM;
169 
170 	sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
171 	if (IS_ERR(sub_fdt_dir))
172 		goto err_rmdir;
173 
174 	f = debugfs_create_file("scratch_phys", 0400, dir, NULL,
175 				&scratch_phys_fops);
176 	if (IS_ERR(f))
177 		goto err_rmdir;
178 
179 	f = debugfs_create_file("scratch_len", 0400, dir, NULL,
180 				&scratch_len_fops);
181 	if (IS_ERR(f))
182 		goto err_rmdir;
183 
184 	dbg->dir = dir;
185 	dbg->sub_fdt_dir = sub_fdt_dir;
186 	return 0;
187 
188 err_rmdir:
189 	debugfs_remove_recursive(dir);
190 	return -ENOENT;
191 }
192 
193 __init int kho_debugfs_init(void)
194 {
195 	debugfs_root = debugfs_create_dir("kho", NULL);
196 	if (IS_ERR(debugfs_root))
197 		return -ENOENT;
198 	return 0;
199 }
200