| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Remote Processor Framework |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 11 | 12 | * Suman Anna <s-anna@ti.com> |
|---|
| 12 | 13 | * Robert Tivy <rtivy@ti.com> |
|---|
| 13 | 14 | * Armando Uribe De Leon <x0095078@ti.com> |
|---|
| 14 | | - * |
|---|
| 15 | | - * This program is free software; you can redistribute it and/or |
|---|
| 16 | | - * modify it under the terms of the GNU General Public License |
|---|
| 17 | | - * version 2 as published by the Free Software Foundation. |
|---|
| 18 | | - * |
|---|
| 19 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 20 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 21 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 22 | | - * GNU General Public License for more details. |
|---|
| 23 | 15 | */ |
|---|
| 24 | 16 | |
|---|
| 25 | 17 | #define pr_fmt(fmt) "%s: " fmt, __func__ |
|---|
| .. | .. |
|---|
| 36 | 28 | static struct dentry *rproc_dbg; |
|---|
| 37 | 29 | |
|---|
| 38 | 30 | /* |
|---|
| 31 | + * A coredump-configuration-to-string lookup table, for exposing a |
|---|
| 32 | + * human readable configuration via debugfs. Always keep in sync with |
|---|
| 33 | + * enum rproc_coredump_mechanism |
|---|
| 34 | + */ |
|---|
| 35 | +static const char * const rproc_coredump_str[] = { |
|---|
| 36 | + [RPROC_COREDUMP_DISABLED] = "disabled", |
|---|
| 37 | + [RPROC_COREDUMP_ENABLED] = "enabled", |
|---|
| 38 | + [RPROC_COREDUMP_INLINE] = "inline", |
|---|
| 39 | +}; |
|---|
| 40 | + |
|---|
| 41 | +/* Expose the current coredump configuration via debugfs */ |
|---|
| 42 | +static ssize_t rproc_coredump_read(struct file *filp, char __user *userbuf, |
|---|
| 43 | + size_t count, loff_t *ppos) |
|---|
| 44 | +{ |
|---|
| 45 | + struct rproc *rproc = filp->private_data; |
|---|
| 46 | + char buf[20]; |
|---|
| 47 | + int len; |
|---|
| 48 | + |
|---|
| 49 | + len = scnprintf(buf, sizeof(buf), "%s\n", |
|---|
| 50 | + rproc_coredump_str[rproc->dump_conf]); |
|---|
| 51 | + |
|---|
| 52 | + return simple_read_from_buffer(userbuf, count, ppos, buf, len); |
|---|
| 53 | +} |
|---|
| 54 | + |
|---|
| 55 | +/* |
|---|
| 56 | + * By writing to the 'coredump' debugfs entry, we control the behavior of the |
|---|
| 57 | + * coredump mechanism dynamically. The default value of this entry is "disabled". |
|---|
| 58 | + * |
|---|
| 59 | + * The 'coredump' debugfs entry supports these commands: |
|---|
| 60 | + * |
|---|
| 61 | + * disabled: By default coredump collection is disabled. Recovery will |
|---|
| 62 | + * proceed without collecting any dump. |
|---|
| 63 | + * |
|---|
| 64 | + * enabled: When the remoteproc crashes the entire coredump will be copied |
|---|
| 65 | + * to a separate buffer and exposed to userspace. |
|---|
| 66 | + * |
|---|
| 67 | + * inline: The coredump will not be copied to a separate buffer and the |
|---|
| 68 | + * recovery process will have to wait until data is read by |
|---|
| 69 | + * userspace. But this avoid usage of extra memory. |
|---|
| 70 | + */ |
|---|
| 71 | +static ssize_t rproc_coredump_write(struct file *filp, |
|---|
| 72 | + const char __user *user_buf, size_t count, |
|---|
| 73 | + loff_t *ppos) |
|---|
| 74 | +{ |
|---|
| 75 | + struct rproc *rproc = filp->private_data; |
|---|
| 76 | + int ret, err = 0; |
|---|
| 77 | + char buf[20]; |
|---|
| 78 | + |
|---|
| 79 | + if (count < 1 || count > sizeof(buf)) |
|---|
| 80 | + return -EINVAL; |
|---|
| 81 | + |
|---|
| 82 | + ret = copy_from_user(buf, user_buf, count); |
|---|
| 83 | + if (ret) |
|---|
| 84 | + return -EFAULT; |
|---|
| 85 | + |
|---|
| 86 | + /* remove end of line */ |
|---|
| 87 | + if (buf[count - 1] == '\n') |
|---|
| 88 | + buf[count - 1] = '\0'; |
|---|
| 89 | + |
|---|
| 90 | + if (rproc->state == RPROC_CRASHED) { |
|---|
| 91 | + dev_err(&rproc->dev, "can't change coredump configuration\n"); |
|---|
| 92 | + err = -EBUSY; |
|---|
| 93 | + goto out; |
|---|
| 94 | + } |
|---|
| 95 | + |
|---|
| 96 | + if (!strncmp(buf, "disabled", count)) { |
|---|
| 97 | + rproc->dump_conf = RPROC_COREDUMP_DISABLED; |
|---|
| 98 | + } else if (!strncmp(buf, "enabled", count)) { |
|---|
| 99 | + rproc->dump_conf = RPROC_COREDUMP_ENABLED; |
|---|
| 100 | + } else if (!strncmp(buf, "inline", count)) { |
|---|
| 101 | + rproc->dump_conf = RPROC_COREDUMP_INLINE; |
|---|
| 102 | + } else { |
|---|
| 103 | + dev_err(&rproc->dev, "Invalid coredump configuration\n"); |
|---|
| 104 | + err = -EINVAL; |
|---|
| 105 | + } |
|---|
| 106 | +out: |
|---|
| 107 | + return err ? err : count; |
|---|
| 108 | +} |
|---|
| 109 | + |
|---|
| 110 | +static const struct file_operations rproc_coredump_fops = { |
|---|
| 111 | + .read = rproc_coredump_read, |
|---|
| 112 | + .write = rproc_coredump_write, |
|---|
| 113 | + .open = simple_open, |
|---|
| 114 | + .llseek = generic_file_llseek, |
|---|
| 115 | +}; |
|---|
| 116 | + |
|---|
| 117 | +/* |
|---|
| 39 | 118 | * Some remote processors may support dumping trace logs into a shared |
|---|
| 40 | 119 | * memory buffer. We expose this trace buffer using debugfs, so users |
|---|
| 41 | 120 | * can easily tell what's going on remotely. |
|---|
| .. | .. |
|---|
| 47 | 126 | static ssize_t rproc_trace_read(struct file *filp, char __user *userbuf, |
|---|
| 48 | 127 | size_t count, loff_t *ppos) |
|---|
| 49 | 128 | { |
|---|
| 50 | | - struct rproc_mem_entry *trace = filp->private_data; |
|---|
| 51 | | - int len = strnlen(trace->va, trace->len); |
|---|
| 129 | + struct rproc_debug_trace *data = filp->private_data; |
|---|
| 130 | + struct rproc_mem_entry *trace = &data->trace_mem; |
|---|
| 131 | + void *va; |
|---|
| 132 | + char buf[100]; |
|---|
| 133 | + int len; |
|---|
| 52 | 134 | |
|---|
| 53 | | - return simple_read_from_buffer(userbuf, count, ppos, trace->va, len); |
|---|
| 135 | + va = rproc_da_to_va(data->rproc, trace->da, trace->len, NULL); |
|---|
| 136 | + |
|---|
| 137 | + if (!va) { |
|---|
| 138 | + len = scnprintf(buf, sizeof(buf), "Trace %s not available\n", |
|---|
| 139 | + trace->name); |
|---|
| 140 | + va = buf; |
|---|
| 141 | + } else { |
|---|
| 142 | + len = strnlen(va, trace->len); |
|---|
| 143 | + } |
|---|
| 144 | + |
|---|
| 145 | + return simple_read_from_buffer(userbuf, count, ppos, va, len); |
|---|
| 54 | 146 | } |
|---|
| 55 | 147 | |
|---|
| 56 | 148 | static const struct file_operations trace_rproc_ops = { |
|---|
| .. | .. |
|---|
| 133 | 225 | buf[count - 1] = '\0'; |
|---|
| 134 | 226 | |
|---|
| 135 | 227 | if (!strncmp(buf, "enabled", count)) { |
|---|
| 228 | + /* change the flag and begin the recovery process if needed */ |
|---|
| 136 | 229 | rproc->recovery_disabled = false; |
|---|
| 137 | | - /* if rproc has crashed, trigger recovery */ |
|---|
| 138 | | - if (rproc->state == RPROC_CRASHED) |
|---|
| 139 | | - rproc_trigger_recovery(rproc); |
|---|
| 230 | + rproc_trigger_recovery(rproc); |
|---|
| 140 | 231 | } else if (!strncmp(buf, "disabled", count)) { |
|---|
| 141 | 232 | rproc->recovery_disabled = true; |
|---|
| 142 | 233 | } else if (!strncmp(buf, "recover", count)) { |
|---|
| 143 | | - /* if rproc has crashed, trigger recovery */ |
|---|
| 144 | | - if (rproc->state == RPROC_CRASHED) |
|---|
| 145 | | - rproc_trigger_recovery(rproc); |
|---|
| 234 | + /* begin the recovery process without changing the flag */ |
|---|
| 235 | + rproc_trigger_recovery(rproc); |
|---|
| 236 | + } else { |
|---|
| 237 | + return -EINVAL; |
|---|
| 146 | 238 | } |
|---|
| 147 | 239 | |
|---|
| 148 | 240 | return count; |
|---|
| .. | .. |
|---|
| 151 | 243 | static const struct file_operations rproc_recovery_ops = { |
|---|
| 152 | 244 | .read = rproc_recovery_read, |
|---|
| 153 | 245 | .write = rproc_recovery_write, |
|---|
| 246 | + .open = simple_open, |
|---|
| 247 | + .llseek = generic_file_llseek, |
|---|
| 248 | +}; |
|---|
| 249 | + |
|---|
| 250 | +/* expose the crash trigger via debugfs */ |
|---|
| 251 | +static ssize_t |
|---|
| 252 | +rproc_crash_write(struct file *filp, const char __user *user_buf, |
|---|
| 253 | + size_t count, loff_t *ppos) |
|---|
| 254 | +{ |
|---|
| 255 | + struct rproc *rproc = filp->private_data; |
|---|
| 256 | + unsigned int type; |
|---|
| 257 | + int ret; |
|---|
| 258 | + |
|---|
| 259 | + ret = kstrtouint_from_user(user_buf, count, 0, &type); |
|---|
| 260 | + if (ret < 0) |
|---|
| 261 | + return ret; |
|---|
| 262 | + |
|---|
| 263 | + rproc_report_crash(rproc, type); |
|---|
| 264 | + |
|---|
| 265 | + return count; |
|---|
| 266 | +} |
|---|
| 267 | + |
|---|
| 268 | +static const struct file_operations rproc_crash_ops = { |
|---|
| 269 | + .write = rproc_crash_write, |
|---|
| 154 | 270 | .open = simple_open, |
|---|
| 155 | 271 | .llseek = generic_file_llseek, |
|---|
| 156 | 272 | }; |
|---|
| .. | .. |
|---|
| 240 | 356 | return 0; |
|---|
| 241 | 357 | } |
|---|
| 242 | 358 | |
|---|
| 243 | | -static int rproc_rsc_table_open(struct inode *inode, struct file *file) |
|---|
| 244 | | -{ |
|---|
| 245 | | - return single_open(file, rproc_rsc_table_show, inode->i_private); |
|---|
| 246 | | -} |
|---|
| 247 | | - |
|---|
| 248 | | -static const struct file_operations rproc_rsc_table_ops = { |
|---|
| 249 | | - .open = rproc_rsc_table_open, |
|---|
| 250 | | - .read = seq_read, |
|---|
| 251 | | - .llseek = seq_lseek, |
|---|
| 252 | | - .release = single_release, |
|---|
| 253 | | -}; |
|---|
| 359 | +DEFINE_SHOW_ATTRIBUTE(rproc_rsc_table); |
|---|
| 254 | 360 | |
|---|
| 255 | 361 | /* Expose carveout content via debugfs */ |
|---|
| 256 | 362 | static int rproc_carveouts_show(struct seq_file *seq, void *p) |
|---|
| .. | .. |
|---|
| 260 | 366 | |
|---|
| 261 | 367 | list_for_each_entry(carveout, &rproc->carveouts, node) { |
|---|
| 262 | 368 | seq_puts(seq, "Carveout memory entry:\n"); |
|---|
| 369 | + seq_printf(seq, "\tName: %s\n", carveout->name); |
|---|
| 263 | 370 | seq_printf(seq, "\tVirtual address: %pK\n", carveout->va); |
|---|
| 264 | 371 | seq_printf(seq, "\tDMA address: %pad\n", &carveout->dma); |
|---|
| 265 | 372 | seq_printf(seq, "\tDevice address: 0x%x\n", carveout->da); |
|---|
| 266 | | - seq_printf(seq, "\tLength: 0x%x Bytes\n\n", carveout->len); |
|---|
| 373 | + seq_printf(seq, "\tLength: 0x%zx Bytes\n\n", carveout->len); |
|---|
| 267 | 374 | } |
|---|
| 268 | 375 | |
|---|
| 269 | 376 | return 0; |
|---|
| 270 | 377 | } |
|---|
| 271 | 378 | |
|---|
| 272 | | -static int rproc_carveouts_open(struct inode *inode, struct file *file) |
|---|
| 273 | | -{ |
|---|
| 274 | | - return single_open(file, rproc_carveouts_show, inode->i_private); |
|---|
| 275 | | -} |
|---|
| 276 | | - |
|---|
| 277 | | -static const struct file_operations rproc_carveouts_ops = { |
|---|
| 278 | | - .open = rproc_carveouts_open, |
|---|
| 279 | | - .read = seq_read, |
|---|
| 280 | | - .llseek = seq_lseek, |
|---|
| 281 | | - .release = single_release, |
|---|
| 282 | | -}; |
|---|
| 379 | +DEFINE_SHOW_ATTRIBUTE(rproc_carveouts); |
|---|
| 283 | 380 | |
|---|
| 284 | 381 | void rproc_remove_trace_file(struct dentry *tfile) |
|---|
| 285 | 382 | { |
|---|
| .. | .. |
|---|
| 287 | 384 | } |
|---|
| 288 | 385 | |
|---|
| 289 | 386 | struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc, |
|---|
| 290 | | - struct rproc_mem_entry *trace) |
|---|
| 387 | + struct rproc_debug_trace *trace) |
|---|
| 291 | 388 | { |
|---|
| 292 | 389 | struct dentry *tfile; |
|---|
| 293 | 390 | |
|---|
| .. | .. |
|---|
| 303 | 400 | |
|---|
| 304 | 401 | void rproc_delete_debug_dir(struct rproc *rproc) |
|---|
| 305 | 402 | { |
|---|
| 306 | | - if (!rproc->dbg_dir) |
|---|
| 307 | | - return; |
|---|
| 308 | | - |
|---|
| 309 | 403 | debugfs_remove_recursive(rproc->dbg_dir); |
|---|
| 310 | 404 | } |
|---|
| 311 | 405 | |
|---|
| .. | .. |
|---|
| 322 | 416 | |
|---|
| 323 | 417 | debugfs_create_file("name", 0400, rproc->dbg_dir, |
|---|
| 324 | 418 | rproc, &rproc_name_ops); |
|---|
| 325 | | - debugfs_create_file("recovery", 0400, rproc->dbg_dir, |
|---|
| 419 | + debugfs_create_file("recovery", 0600, rproc->dbg_dir, |
|---|
| 326 | 420 | rproc, &rproc_recovery_ops); |
|---|
| 421 | + debugfs_create_file("crash", 0200, rproc->dbg_dir, |
|---|
| 422 | + rproc, &rproc_crash_ops); |
|---|
| 327 | 423 | debugfs_create_file("resource_table", 0400, rproc->dbg_dir, |
|---|
| 328 | | - rproc, &rproc_rsc_table_ops); |
|---|
| 424 | + rproc, &rproc_rsc_table_fops); |
|---|
| 329 | 425 | debugfs_create_file("carveout_memories", 0400, rproc->dbg_dir, |
|---|
| 330 | | - rproc, &rproc_carveouts_ops); |
|---|
| 426 | + rproc, &rproc_carveouts_fops); |
|---|
| 427 | + debugfs_create_file("coredump", 0600, rproc->dbg_dir, |
|---|
| 428 | + rproc, &rproc_coredump_fops); |
|---|
| 331 | 429 | } |
|---|
| 332 | 430 | |
|---|
| 333 | 431 | void __init rproc_init_debugfs(void) |
|---|