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