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