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