| 1 | |
| 2 | |
| 3 | * kexec_handover_debugfs.c - kexec handover debugfs interfaces
|
| 4 | * Copyright (C) 2023 Alexander Graf <[email protected]>
|
| 5 | * Copyright (C) 2025 Microsoft Corporation, Mike Rapoport <[email protected]>
|
| 6 | * Copyright (C) 2025 Google LLC, Changyuan Lyu <[email protected]>
|
| 7 | * Copyright (C) 2025 Google LLC, Pasha Tatashin <[email protected]>
|
| 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 | }
|