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 *fdt_phys; 126 void *sub_fdt; 127 128 fdt_phys = fdt_getprop(fdt, child, 129 KHO_SUB_TREE_PROP_NAME, &len); 130 if (!fdt_phys) 131 continue; 132 if (len != sizeof(*fdt_phys)) { 133 pr_warn("node %s prop fdt has invalid length: %d\n", 134 name, len); 135 continue; 136 } 137 sub_fdt = phys_to_virt(*fdt_phys); 138 err = __kho_debugfs_blob_add(&dbg->fdt_list, sub_fdt_dir, name, 139 sub_fdt, fdt_totalsize(sub_fdt)); 140 if (err) { 141 pr_warn("failed to add fdt %s to debugfs: %pe\n", name, 142 ERR_PTR(err)); 143 continue; 144 } 145 } 146 147 dbg->dir = dir; 148 dbg->sub_fdt_dir = sub_fdt_dir; 149 150 return; 151 err_rmdir: 152 debugfs_remove_recursive(dir); 153 err_out: 154 /* 155 * Failure to create /sys/kernel/debug/kho/in does not prevent 156 * reviving state from KHO and setting up KHO for the next 157 * kexec. 158 */ 159 if (err) { 160 pr_err("failed exposing handover FDT in debugfs: %pe\n", 161 ERR_PTR(err)); 162 } 163 } 164 165 __init int kho_out_debugfs_init(struct kho_debugfs *dbg) 166 { 167 struct dentry *dir, *f, *sub_fdt_dir; 168 169 INIT_LIST_HEAD(&dbg->fdt_list); 170 171 dir = debugfs_create_dir("out", debugfs_root); 172 if (IS_ERR(dir)) 173 return -ENOMEM; 174 175 sub_fdt_dir = debugfs_create_dir("sub_fdts", dir); 176 if (IS_ERR(sub_fdt_dir)) 177 goto err_rmdir; 178 179 f = debugfs_create_file("scratch_phys", 0400, dir, NULL, 180 &scratch_phys_fops); 181 if (IS_ERR(f)) 182 goto err_rmdir; 183 184 f = debugfs_create_file("scratch_len", 0400, dir, NULL, 185 &scratch_len_fops); 186 if (IS_ERR(f)) 187 goto err_rmdir; 188 189 dbg->dir = dir; 190 dbg->sub_fdt_dir = sub_fdt_dir; 191 return 0; 192 193 err_rmdir: 194 debugfs_remove_recursive(dir); 195 return -ENOENT; 196 } 197 198 __init int kho_debugfs_init(void) 199 { 200 debugfs_root = debugfs_create_dir("kho", NULL); 201 if (IS_ERR(debugfs_root)) 202 return -ENOENT; 203 return 0; 204 } 205