Thomas Gleixner | 1802d0b | 2019-05-27 08:55:21 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-only |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 2 | /* |
| 3 | * Remote Processor Framework |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 4 | */ |
| 5 | |
| 6 | #include <linux/remoteproc.h> |
Michael S. Tsirkin | bf89a7c | 2020-04-06 20:39:37 -0400 | [diff] [blame] | 7 | #include <linux/slab.h> |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 8 | |
| 9 | #include "remoteproc_internal.h" |
| 10 | |
| 11 | #define to_rproc(d) container_of(d, struct rproc, dev) |
| 12 | |
Rishabh Bhatnagar | 526b9e0 | 2020-10-02 11:09:04 -0700 | [diff] [blame] | 13 | static ssize_t recovery_show(struct device *dev, |
| 14 | struct device_attribute *attr, char *buf) |
| 15 | { |
| 16 | struct rproc *rproc = to_rproc(dev); |
| 17 | |
Raghavendra Rao Ananta | 145e1da | 2021-03-03 13:47:02 -0800 | [diff] [blame] | 18 | return sysfs_emit(buf, "%s", rproc->recovery_disabled ? "disabled\n" : "enabled\n"); |
Rishabh Bhatnagar | 526b9e0 | 2020-10-02 11:09:04 -0700 | [diff] [blame] | 19 | } |
| 20 | |
| 21 | /* |
| 22 | * By writing to the 'recovery' sysfs entry, we control the behavior of the |
| 23 | * recovery mechanism dynamically. The default value of this entry is "enabled". |
| 24 | * |
| 25 | * The 'recovery' sysfs entry supports these commands: |
| 26 | * |
| 27 | * enabled: When enabled, the remote processor will be automatically |
| 28 | * recovered whenever it crashes. Moreover, if the remote |
| 29 | * processor crashes while recovery is disabled, it will |
| 30 | * be automatically recovered too as soon as recovery is enabled. |
| 31 | * |
| 32 | * disabled: When disabled, a remote processor will remain in a crashed |
| 33 | * state if it crashes. This is useful for debugging purposes; |
| 34 | * without it, debugging a crash is substantially harder. |
| 35 | * |
| 36 | * recover: This function will trigger an immediate recovery if the |
| 37 | * remote processor is in a crashed state, without changing |
| 38 | * or checking the recovery state (enabled/disabled). |
| 39 | * This is useful during debugging sessions, when one expects |
| 40 | * additional crashes to happen after enabling recovery. In this |
| 41 | * case, enabling recovery will make it hard to debug subsequent |
| 42 | * crashes, so it's recommended to keep recovery disabled, and |
| 43 | * instead use the "recover" command as needed. |
| 44 | */ |
| 45 | static ssize_t recovery_store(struct device *dev, |
| 46 | struct device_attribute *attr, |
| 47 | const char *buf, size_t count) |
| 48 | { |
| 49 | struct rproc *rproc = to_rproc(dev); |
| 50 | |
| 51 | if (sysfs_streq(buf, "enabled")) { |
| 52 | /* change the flag and begin the recovery process if needed */ |
| 53 | rproc->recovery_disabled = false; |
| 54 | rproc_trigger_recovery(rproc); |
| 55 | } else if (sysfs_streq(buf, "disabled")) { |
| 56 | rproc->recovery_disabled = true; |
| 57 | } else if (sysfs_streq(buf, "recover")) { |
| 58 | /* begin the recovery process without changing the flag */ |
| 59 | rproc_trigger_recovery(rproc); |
| 60 | } else { |
| 61 | return -EINVAL; |
| 62 | } |
| 63 | |
| 64 | return count; |
| 65 | } |
| 66 | static DEVICE_ATTR_RW(recovery); |
| 67 | |
Rishabh Bhatnagar | f75c604 | 2020-10-02 11:09:03 -0700 | [diff] [blame] | 68 | /* |
| 69 | * A coredump-configuration-to-string lookup table, for exposing a |
| 70 | * human readable configuration via sysfs. Always keep in sync with |
| 71 | * enum rproc_coredump_mechanism |
| 72 | */ |
| 73 | static const char * const rproc_coredump_str[] = { |
| 74 | [RPROC_COREDUMP_DISABLED] = "disabled", |
| 75 | [RPROC_COREDUMP_ENABLED] = "enabled", |
| 76 | [RPROC_COREDUMP_INLINE] = "inline", |
| 77 | }; |
| 78 | |
| 79 | /* Expose the current coredump configuration via debugfs */ |
| 80 | static ssize_t coredump_show(struct device *dev, |
| 81 | struct device_attribute *attr, char *buf) |
| 82 | { |
| 83 | struct rproc *rproc = to_rproc(dev); |
| 84 | |
Raghavendra Rao Ananta | 145e1da | 2021-03-03 13:47:02 -0800 | [diff] [blame] | 85 | return sysfs_emit(buf, "%s\n", rproc_coredump_str[rproc->dump_conf]); |
Rishabh Bhatnagar | f75c604 | 2020-10-02 11:09:03 -0700 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | /* |
| 89 | * By writing to the 'coredump' sysfs entry, we control the behavior of the |
| 90 | * coredump mechanism dynamically. The default value of this entry is "default". |
| 91 | * |
| 92 | * The 'coredump' sysfs entry supports these commands: |
| 93 | * |
| 94 | * disabled: This is the default coredump mechanism. Recovery will proceed |
| 95 | * without collecting any dump. |
| 96 | * |
| 97 | * default: When the remoteproc crashes the entire coredump will be |
| 98 | * copied to a separate buffer and exposed to userspace. |
| 99 | * |
| 100 | * inline: The coredump will not be copied to a separate buffer and the |
| 101 | * recovery process will have to wait until data is read by |
| 102 | * userspace. But this avoid usage of extra memory. |
| 103 | */ |
| 104 | static ssize_t coredump_store(struct device *dev, |
| 105 | struct device_attribute *attr, |
| 106 | const char *buf, size_t count) |
| 107 | { |
| 108 | struct rproc *rproc = to_rproc(dev); |
| 109 | |
| 110 | if (rproc->state == RPROC_CRASHED) { |
| 111 | dev_err(&rproc->dev, "can't change coredump configuration\n"); |
| 112 | return -EBUSY; |
| 113 | } |
| 114 | |
| 115 | if (sysfs_streq(buf, "disabled")) { |
| 116 | rproc->dump_conf = RPROC_COREDUMP_DISABLED; |
| 117 | } else if (sysfs_streq(buf, "enabled")) { |
| 118 | rproc->dump_conf = RPROC_COREDUMP_ENABLED; |
| 119 | } else if (sysfs_streq(buf, "inline")) { |
| 120 | rproc->dump_conf = RPROC_COREDUMP_INLINE; |
| 121 | } else { |
| 122 | dev_err(&rproc->dev, "Invalid coredump configuration\n"); |
| 123 | return -EINVAL; |
| 124 | } |
| 125 | |
| 126 | return count; |
| 127 | } |
| 128 | static DEVICE_ATTR_RW(coredump); |
| 129 | |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 130 | /* Expose the loaded / running firmware name via sysfs */ |
| 131 | static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, |
| 132 | char *buf) |
| 133 | { |
| 134 | struct rproc *rproc = to_rproc(dev); |
Mathieu Poirier | 4a4dca1 | 2020-07-14 13:50:35 -0600 | [diff] [blame] | 135 | const char *firmware = rproc->firmware; |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 136 | |
Mathieu Poirier | 4a4dca1 | 2020-07-14 13:50:35 -0600 | [diff] [blame] | 137 | /* |
| 138 | * If the remote processor has been started by an external |
| 139 | * entity we have no idea of what image it is running. As such |
| 140 | * simply display a generic string rather then rproc->firmware. |
Mathieu Poirier | 4a4dca1 | 2020-07-14 13:50:35 -0600 | [diff] [blame] | 141 | */ |
Mathieu Poirier | 76f4c87 | 2021-03-12 09:24:40 -0700 | [diff] [blame] | 142 | if (rproc->state == RPROC_ATTACHED) |
Mathieu Poirier | 4a4dca1 | 2020-07-14 13:50:35 -0600 | [diff] [blame] | 143 | firmware = "unknown"; |
| 144 | |
| 145 | return sprintf(buf, "%s\n", firmware); |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 146 | } |
| 147 | |
| 148 | /* Change firmware name via sysfs */ |
| 149 | static ssize_t firmware_store(struct device *dev, |
| 150 | struct device_attribute *attr, |
| 151 | const char *buf, size_t count) |
| 152 | { |
| 153 | struct rproc *rproc = to_rproc(dev); |
Suman Anna | 4c1ad56 | 2020-11-20 21:20:42 -0600 | [diff] [blame] | 154 | int err; |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 155 | |
Suman Anna | 4c1ad56 | 2020-11-20 21:20:42 -0600 | [diff] [blame] | 156 | err = rproc_set_firmware(rproc, buf); |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 157 | |
| 158 | return err ? err : count; |
| 159 | } |
| 160 | static DEVICE_ATTR_RW(firmware); |
| 161 | |
| 162 | /* |
| 163 | * A state-to-string lookup table, for exposing a human readable state |
| 164 | * via sysfs. Always keep in sync with enum rproc_state |
| 165 | */ |
| 166 | static const char * const rproc_state_string[] = { |
| 167 | [RPROC_OFFLINE] = "offline", |
| 168 | [RPROC_SUSPENDED] = "suspended", |
| 169 | [RPROC_RUNNING] = "running", |
| 170 | [RPROC_CRASHED] = "crashed", |
Sarangdhar Joshi | 608d792 | 2017-01-23 17:53:18 -0800 | [diff] [blame] | 171 | [RPROC_DELETED] = "deleted", |
Mathieu Poirier | 4196d18 | 2021-03-12 09:24:39 -0700 | [diff] [blame] | 172 | [RPROC_ATTACHED] = "attached", |
Mathieu Poirier | e2e5c55 | 2020-07-14 13:50:27 -0600 | [diff] [blame] | 173 | [RPROC_DETACHED] = "detached", |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 174 | [RPROC_LAST] = "invalid", |
| 175 | }; |
| 176 | |
| 177 | /* Expose the state of the remote processor via sysfs */ |
| 178 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, |
| 179 | char *buf) |
| 180 | { |
| 181 | struct rproc *rproc = to_rproc(dev); |
| 182 | unsigned int state; |
| 183 | |
| 184 | state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state; |
| 185 | return sprintf(buf, "%s\n", rproc_state_string[state]); |
| 186 | } |
| 187 | |
| 188 | /* Change remote processor state via sysfs */ |
| 189 | static ssize_t state_store(struct device *dev, |
| 190 | struct device_attribute *attr, |
| 191 | const char *buf, size_t count) |
| 192 | { |
| 193 | struct rproc *rproc = to_rproc(dev); |
| 194 | int ret = 0; |
| 195 | |
| 196 | if (sysfs_streq(buf, "start")) { |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 197 | ret = rproc_boot(rproc); |
| 198 | if (ret) |
| 199 | dev_err(&rproc->dev, "Boot failed: %d\n", ret); |
| 200 | } else if (sysfs_streq(buf, "stop")) { |
Suman Anna | c13b780 | 2022-02-13 14:12:42 -0600 | [diff] [blame] | 201 | ret = rproc_shutdown(rproc); |
Mathieu Poirier | 5daaeb5 | 2021-03-12 09:24:52 -0700 | [diff] [blame] | 202 | } else if (sysfs_streq(buf, "detach")) { |
Mathieu Poirier | 5daaeb5 | 2021-03-12 09:24:52 -0700 | [diff] [blame] | 203 | ret = rproc_detach(rproc); |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 204 | } else { |
| 205 | dev_err(&rproc->dev, "Unrecognised option: %s\n", buf); |
| 206 | ret = -EINVAL; |
| 207 | } |
| 208 | return ret ? ret : count; |
| 209 | } |
| 210 | static DEVICE_ATTR_RW(state); |
| 211 | |
Suman Anna | 6ed756a | 2019-08-09 17:20:57 -0500 | [diff] [blame] | 212 | /* Expose the name of the remote processor via sysfs */ |
| 213 | static ssize_t name_show(struct device *dev, struct device_attribute *attr, |
| 214 | char *buf) |
| 215 | { |
| 216 | struct rproc *rproc = to_rproc(dev); |
| 217 | |
| 218 | return sprintf(buf, "%s\n", rproc->name); |
| 219 | } |
| 220 | static DEVICE_ATTR_RO(name); |
| 221 | |
Puranjay Mohan | 26c9da5 | 2022-02-16 13:42:23 +0530 | [diff] [blame] | 222 | static umode_t rproc_is_visible(struct kobject *kobj, struct attribute *attr, |
| 223 | int n) |
| 224 | { |
| 225 | struct device *dev = kobj_to_dev(kobj); |
| 226 | struct rproc *rproc = to_rproc(dev); |
| 227 | umode_t mode = attr->mode; |
| 228 | |
| 229 | if (rproc->sysfs_read_only && (attr == &dev_attr_recovery.attr || |
| 230 | attr == &dev_attr_firmware.attr || |
| 231 | attr == &dev_attr_state.attr || |
| 232 | attr == &dev_attr_coredump.attr)) |
| 233 | mode = 0444; |
| 234 | |
| 235 | return mode; |
| 236 | } |
| 237 | |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 238 | static struct attribute *rproc_attrs[] = { |
Rishabh Bhatnagar | f75c604 | 2020-10-02 11:09:03 -0700 | [diff] [blame] | 239 | &dev_attr_coredump.attr, |
Rishabh Bhatnagar | 526b9e0 | 2020-10-02 11:09:04 -0700 | [diff] [blame] | 240 | &dev_attr_recovery.attr, |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 241 | &dev_attr_firmware.attr, |
| 242 | &dev_attr_state.attr, |
Suman Anna | 6ed756a | 2019-08-09 17:20:57 -0500 | [diff] [blame] | 243 | &dev_attr_name.attr, |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 244 | NULL |
| 245 | }; |
| 246 | |
| 247 | static const struct attribute_group rproc_devgroup = { |
Puranjay Mohan | 26c9da5 | 2022-02-16 13:42:23 +0530 | [diff] [blame] | 248 | .attrs = rproc_attrs, |
| 249 | .is_visible = rproc_is_visible, |
Matt Redfearn | 2aefbef | 2016-10-19 13:05:47 +0100 | [diff] [blame] | 250 | }; |
| 251 | |
| 252 | static const struct attribute_group *rproc_devgroups[] = { |
| 253 | &rproc_devgroup, |
| 254 | NULL |
| 255 | }; |
| 256 | |
| 257 | struct class rproc_class = { |
| 258 | .name = "remoteproc", |
| 259 | .dev_groups = rproc_devgroups, |
| 260 | }; |
| 261 | |
| 262 | int __init rproc_init_sysfs(void) |
| 263 | { |
| 264 | /* create remoteproc device class for sysfs */ |
| 265 | int err = class_register(&rproc_class); |
| 266 | |
| 267 | if (err) |
| 268 | pr_err("remoteproc: unable to register class\n"); |
| 269 | return err; |
| 270 | } |
| 271 | |
| 272 | void __exit rproc_exit_sysfs(void) |
| 273 | { |
| 274 | class_unregister(&rproc_class); |
| 275 | } |