1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Remote Processor Framework |
4 | */ |
5 | |
6 | #include <linux/remoteproc.h> |
7 | #include <linux/slab.h> |
8 | |
9 | #include "remoteproc_internal.h" |
10 | |
11 | #define to_rproc(d) container_of(d, struct rproc, dev) |
12 | |
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 | |
18 | return sysfs_emit(buf, fmt: "%s" , rproc->recovery_disabled ? "disabled\n" : "enabled\n" ); |
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(s1: buf, s2: "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(s1: buf, s2: "disabled" )) { |
56 | rproc->recovery_disabled = true; |
57 | } else if (sysfs_streq(s1: buf, s2: "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 | |
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 | |
85 | return sysfs_emit(buf, fmt: "%s\n" , rproc_coredump_str[rproc->dump_conf]); |
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(s1: buf, s2: "disabled" )) { |
116 | rproc->dump_conf = RPROC_COREDUMP_DISABLED; |
117 | } else if (sysfs_streq(s1: buf, s2: "enabled" )) { |
118 | rproc->dump_conf = RPROC_COREDUMP_ENABLED; |
119 | } else if (sysfs_streq(s1: buf, s2: "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 | |
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); |
135 | const char *firmware = rproc->firmware; |
136 | |
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. |
141 | */ |
142 | if (rproc->state == RPROC_ATTACHED) |
143 | firmware = "unknown" ; |
144 | |
145 | return sprintf(buf, fmt: "%s\n" , firmware); |
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); |
154 | int err; |
155 | |
156 | err = rproc_set_firmware(rproc, fw_name: buf); |
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" , |
171 | [RPROC_DELETED] = "deleted" , |
172 | [RPROC_ATTACHED] = "attached" , |
173 | [RPROC_DETACHED] = "detached" , |
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, fmt: "%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(s1: buf, s2: "start" )) { |
197 | ret = rproc_boot(rproc); |
198 | if (ret) |
199 | dev_err(&rproc->dev, "Boot failed: %d\n" , ret); |
200 | } else if (sysfs_streq(s1: buf, s2: "stop" )) { |
201 | ret = rproc_shutdown(rproc); |
202 | } else if (sysfs_streq(s1: buf, s2: "detach" )) { |
203 | ret = rproc_detach(rproc); |
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 | |
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, fmt: "%s\n" , rproc->name); |
219 | } |
220 | static DEVICE_ATTR_RO(name); |
221 | |
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 | |
238 | static struct attribute *rproc_attrs[] = { |
239 | &dev_attr_coredump.attr, |
240 | &dev_attr_recovery.attr, |
241 | &dev_attr_firmware.attr, |
242 | &dev_attr_state.attr, |
243 | &dev_attr_name.attr, |
244 | NULL |
245 | }; |
246 | |
247 | static const struct attribute_group rproc_devgroup = { |
248 | .attrs = rproc_attrs, |
249 | .is_visible = rproc_is_visible, |
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(class: &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(class: &rproc_class); |
275 | } |
276 | |