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
18static struct dentry *debugfs_root;
19
20struct fdt_debugfs {
21 struct list_head list;
22 struct debugfs_blob_wrapper wrapper;
23 struct dentry *file;
24};
25
26static 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(ptr: file)) {
41 kfree(objp: f);
42 return PTR_ERR(ptr: file);
43 }
44
45 f->file = file;
46 list_add(new: &f->list, head: list);
47
48 return 0;
49}
50
51int 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(list: &dbg->fdt_list, dir, name, fdt);
62}
63
64void 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(entry: &ff->list);
72 kfree(objp: ff);
73 break;
74 }
75 }
76}
77
78static int kho_out_finalize_get(void *data, u64 *val)
79{
80 *val = kho_finalized();
81
82 return 0;
83}
84
85static int kho_out_finalize_set(void *data, u64 val)
86{
87 if (val)
88 return kho_finalize();
89 else
90 return -EINVAL;
91}
92
93DEFINE_DEBUGFS_ATTRIBUTE(kho_out_finalize_fops, kho_out_finalize_get,
94 kho_out_finalize_set, "%llu\n");
95
96static 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}
103DEFINE_SHOW_ATTRIBUTE(scratch_phys);
104
105static 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}
112DEFINE_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(list: &dbg->fdt_list);
120
121 dir = debugfs_create_dir("in", debugfs_root);
122 if (IS_ERR(ptr: dir)) {
123 err = PTR_ERR(ptr: dir);
124 goto err_out;
125 }
126
127 sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
128 if (IS_ERR(ptr: sub_fdt_dir)) {
129 err = PTR_ERR(ptr: sub_fdt_dir);
130 goto err_rmdir;
131 }
132
133 err = __kho_debugfs_fdt_add(list: &dbg->fdt_list, dir, name: "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, nodeoffset: child, NULL);
140 const u64 *fdt_phys;
141
142 fdt_phys = fdt_getprop(fdt, nodeoffset: child, name: "fdt", lenp: &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(list: &dbg->fdt_list, dir: sub_fdt_dir, name,
151 phys_to_virt(address: *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;
163err_rmdir:
164 debugfs_remove_recursive(dir);
165err_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(list: &dbg->fdt_list);
182
183 dir = debugfs_create_dir("out", debugfs_root);
184 if (IS_ERR(ptr: dir))
185 return -ENOMEM;
186
187 sub_fdt_dir = debugfs_create_dir("sub_fdts", dir);
188 if (IS_ERR(ptr: 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(ptr: f))
194 goto err_rmdir;
195
196 f = debugfs_create_file("scratch_len", 0400, dir, NULL,
197 &scratch_len_fops);
198 if (IS_ERR(ptr: f))
199 goto err_rmdir;
200
201 f = debugfs_create_file("finalize", 0600, dir, NULL,
202 &kho_out_finalize_fops);
203 if (IS_ERR(ptr: f))
204 goto err_rmdir;
205
206 dbg->dir = dir;
207 dbg->sub_fdt_dir = sub_fdt_dir;
208 return 0;
209
210err_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(ptr: debugfs_root))
219 return -ENOENT;
220 return 0;
221}
222

source code of linux/kernel/liveupdate/kexec_handover_debugfs.c