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