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
__kho_debugfs_fdt_add(struct list_head * list,struct dentry * dir,const char * name,const void * fdt)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
kho_debugfs_fdt_add(struct kho_debugfs * dbg,const char * name,const void * fdt,bool root)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
kho_debugfs_fdt_remove(struct kho_debugfs * dbg,void * fdt)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
kho_out_finalize_get(void * data,u64 * val)78 static int kho_out_finalize_get(void *data, u64 *val)
79 {
80 *val = kho_finalized();
81
82 return 0;
83 }
84
kho_out_finalize_set(void * data,u64 val)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
scratch_phys_show(struct seq_file * m,void * v)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
scratch_len_show(struct seq_file * m,void * v)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
kho_in_debugfs_init(struct kho_debugfs * dbg,const void * fdt)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
kho_out_debugfs_init(struct kho_debugfs * dbg)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
kho_debugfs_init(void)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