xref: /linux/kernel/liveupdate/kexec_handover_debugfs.c (revision 7b8e9264f55a9c320f398e337d215e68cca50131)
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 "kexec_handover_internal.h"
17 
18 static struct dentry *debugfs_root;
19 
20 struct fdt_debugfs {
21 	struct list_head list;
22 	struct debugfs_blob_wrapper wrapper;
23 	struct dentry *file;
24 };
25 
26 static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
27 				 const char *name, const void *fdt)
28 {
29 	struct fdt_debugfs *f;
30 	struct dentry *file;
31 
32 	f = kmalloc(sizeof(*f), GFP_KERNEL);
33 	if (!f)
34 		return -ENOMEM;
35 
36 	f->wrapper.data = (void *)fdt;
37 	f->wrapper.size = fdt_totalsize(fdt);
38 
39 	file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
40 	if (IS_ERR(file)) {
41 		kfree(f);
42 		return PTR_ERR(file);
43 	}
44 
45 	f->file = file;
46 	list_add(&f->list, list);
47 
48 	return 0;
49 }
50 
51 int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
52 			const void *fdt, bool root)
53 {
54 	struct dentry *dir;
55 
56 	if (root)
57 		dir = dbg->dir;
58 	else
59 		dir = dbg->sub_fdt_dir;
60 
61 	return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt);
62 }
63 
64 void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt)
65 {
66 	struct fdt_debugfs *ff;
67 
68 	list_for_each_entry(ff, &dbg->fdt_list, list) {
69 		if (ff->wrapper.data == fdt) {
70 			debugfs_remove(ff->file);
71 			list_del(&ff->list);
72 			kfree(ff);
73 			break;
74 		}
75 	}
76 }
77 
78 static int kho_out_finalize_get(void *data, u64 *val)
79 {
80 	*val = kho_finalized();
81 
82 	return 0;
83 }
84 
85 static int kho_out_finalize_set(void *data, u64 val)
86 {
87 	if (val)
88 		return kho_finalize();
89 	else
90 		return -EINVAL;
91 }
92 
93 DEFINE_DEBUGFS_ATTRIBUTE(kho_out_finalize_fops, kho_out_finalize_get,
94 			 kho_out_finalize_set, "%llu\n");
95 
96 static int scratch_phys_show(struct seq_file *m, void *v)
97 {
98 	for (int i = 0; i < kho_scratch_cnt; i++)
99 		seq_printf(m, "0x%llx\n", kho_scratch[i].addr);
100 
101 	return 0;
102 }
103 DEFINE_SHOW_ATTRIBUTE(scratch_phys);
104 
105 static int scratch_len_show(struct seq_file *m, void *v)
106 {
107 	for (int i = 0; i < kho_scratch_cnt; i++)
108 		seq_printf(m, "0x%llx\n", kho_scratch[i].size);
109 
110 	return 0;
111 }
112 DEFINE_SHOW_ATTRIBUTE(scratch_len);
113 
114 __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
115 {
116 	struct dentry *dir, *sub_fdt_dir;
117 	int err, child;
118 
119 	INIT_LIST_HEAD(&dbg->fdt_list);
120 
121 	dir = debugfs_create_dir("in", debugfs_root);
122 	if (IS_ERR(dir)) {
123 		err = PTR_ERR(dir);
124 		goto err_out;
125 	}
126 
127 	sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
128 	if (IS_ERR(sub_fdt_dir)) {
129 		err = PTR_ERR(sub_fdt_dir);
130 		goto err_rmdir;
131 	}
132 
133 	err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt);
134 	if (err)
135 		goto err_rmdir;
136 
137 	fdt_for_each_subnode(child, fdt, 0) {
138 		int len = 0;
139 		const char *name = fdt_get_name(fdt, child, NULL);
140 		const u64 *fdt_phys;
141 
142 		fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
143 		if (!fdt_phys)
144 			continue;
145 		if (len != sizeof(*fdt_phys)) {
146 			pr_warn("node %s prop fdt has invalid length: %d\n",
147 				name, len);
148 			continue;
149 		}
150 		err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
151 					    phys_to_virt(*fdt_phys));
152 		if (err) {
153 			pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
154 				ERR_PTR(err));
155 			continue;
156 		}
157 	}
158 
159 	dbg->dir = dir;
160 	dbg->sub_fdt_dir = sub_fdt_dir;
161 
162 	return;
163 err_rmdir:
164 	debugfs_remove_recursive(dir);
165 err_out:
166 	/*
167 	 * Failure to create /sys/kernel/debug/kho/in does not prevent
168 	 * reviving state from KHO and setting up KHO for the next
169 	 * kexec.
170 	 */
171 	if (err) {
172 		pr_err("failed exposing handover FDT in debugfs: %pe\n",
173 		       ERR_PTR(err));
174 	}
175 }
176 
177 __init int kho_out_debugfs_init(struct kho_debugfs *dbg)
178 {
179 	struct dentry *dir, *f, *sub_fdt_dir;
180 
181 	INIT_LIST_HEAD(&dbg->fdt_list);
182 
183 	dir = debugfs_create_dir("out", debugfs_root);
184 	if (IS_ERR(dir))
185 		return -ENOMEM;
186 
187 	sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
188 	if (IS_ERR(sub_fdt_dir))
189 		goto err_rmdir;
190 
191 	f = debugfs_create_file("scratch_phys", 0400, dir, NULL,
192 				&scratch_phys_fops);
193 	if (IS_ERR(f))
194 		goto err_rmdir;
195 
196 	f = debugfs_create_file("scratch_len", 0400, dir, NULL,
197 				&scratch_len_fops);
198 	if (IS_ERR(f))
199 		goto err_rmdir;
200 
201 	f = debugfs_create_file("finalize", 0600, dir, NULL,
202 				&kho_out_finalize_fops);
203 	if (IS_ERR(f))
204 		goto err_rmdir;
205 
206 	dbg->dir = dir;
207 	dbg->sub_fdt_dir = sub_fdt_dir;
208 	return 0;
209 
210 err_rmdir:
211 	debugfs_remove_recursive(dir);
212 	return -ENOENT;
213 }
214 
215 __init int kho_debugfs_init(void)
216 {
217 	debugfs_root = debugfs_create_dir("kho", NULL);
218 	if (IS_ERR(debugfs_root))
219 		return -ENOENT;
220 	return 0;
221 }
222