1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/firmware.h> |
4 | #include <linux/module.h> |
5 | #include <linux/slab.h> |
6 | |
7 | #include "sysfs_upload.h" |
8 | |
9 | /* |
10 | * Support for user-space to initiate a firmware upload to a device. |
11 | */ |
12 | |
13 | static const char * const fw_upload_prog_str[] = { |
14 | [FW_UPLOAD_PROG_IDLE] = "idle" , |
15 | [FW_UPLOAD_PROG_RECEIVING] = "receiving" , |
16 | [FW_UPLOAD_PROG_PREPARING] = "preparing" , |
17 | [FW_UPLOAD_PROG_TRANSFERRING] = "transferring" , |
18 | [FW_UPLOAD_PROG_PROGRAMMING] = "programming" |
19 | }; |
20 | |
21 | static const char * const fw_upload_err_str[] = { |
22 | [FW_UPLOAD_ERR_NONE] = "none" , |
23 | [FW_UPLOAD_ERR_HW_ERROR] = "hw-error" , |
24 | [FW_UPLOAD_ERR_TIMEOUT] = "timeout" , |
25 | [FW_UPLOAD_ERR_CANCELED] = "user-abort" , |
26 | [FW_UPLOAD_ERR_BUSY] = "device-busy" , |
27 | [FW_UPLOAD_ERR_INVALID_SIZE] = "invalid-file-size" , |
28 | [FW_UPLOAD_ERR_RW_ERROR] = "read-write-error" , |
29 | [FW_UPLOAD_ERR_WEAROUT] = "flash-wearout" , |
30 | [FW_UPLOAD_ERR_FW_INVALID] = "firmware-invalid" , |
31 | }; |
32 | |
33 | static const char *fw_upload_progress(struct device *dev, |
34 | enum fw_upload_prog prog) |
35 | { |
36 | const char *status = "unknown-status" ; |
37 | |
38 | if (prog < FW_UPLOAD_PROG_MAX) |
39 | status = fw_upload_prog_str[prog]; |
40 | else |
41 | dev_err(dev, "Invalid status during secure update: %d\n" , prog); |
42 | |
43 | return status; |
44 | } |
45 | |
46 | static const char *fw_upload_error(struct device *dev, |
47 | enum fw_upload_err err_code) |
48 | { |
49 | const char *error = "unknown-error" ; |
50 | |
51 | if (err_code < FW_UPLOAD_ERR_MAX) |
52 | error = fw_upload_err_str[err_code]; |
53 | else |
54 | dev_err(dev, "Invalid error code during secure update: %d\n" , |
55 | err_code); |
56 | |
57 | return error; |
58 | } |
59 | |
60 | static ssize_t |
61 | status_show(struct device *dev, struct device_attribute *attr, char *buf) |
62 | { |
63 | struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv; |
64 | |
65 | return sysfs_emit(buf, fmt: "%s\n" , fw_upload_progress(dev, prog: fwlp->progress)); |
66 | } |
67 | DEVICE_ATTR_RO(status); |
68 | |
69 | static ssize_t |
70 | error_show(struct device *dev, struct device_attribute *attr, char *buf) |
71 | { |
72 | struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv; |
73 | int ret; |
74 | |
75 | mutex_lock(&fwlp->lock); |
76 | |
77 | if (fwlp->progress != FW_UPLOAD_PROG_IDLE) |
78 | ret = -EBUSY; |
79 | else if (!fwlp->err_code) |
80 | ret = 0; |
81 | else |
82 | ret = sysfs_emit(buf, fmt: "%s:%s\n" , |
83 | fw_upload_progress(dev, prog: fwlp->err_progress), |
84 | fw_upload_error(dev, err_code: fwlp->err_code)); |
85 | |
86 | mutex_unlock(lock: &fwlp->lock); |
87 | |
88 | return ret; |
89 | } |
90 | DEVICE_ATTR_RO(error); |
91 | |
92 | static ssize_t cancel_store(struct device *dev, struct device_attribute *attr, |
93 | const char *buf, size_t count) |
94 | { |
95 | struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv; |
96 | int ret = count; |
97 | bool cancel; |
98 | |
99 | if (kstrtobool(s: buf, res: &cancel) || !cancel) |
100 | return -EINVAL; |
101 | |
102 | mutex_lock(&fwlp->lock); |
103 | if (fwlp->progress == FW_UPLOAD_PROG_IDLE) |
104 | ret = -ENODEV; |
105 | |
106 | fwlp->ops->cancel(fwlp->fw_upload); |
107 | mutex_unlock(lock: &fwlp->lock); |
108 | |
109 | return ret; |
110 | } |
111 | DEVICE_ATTR_WO(cancel); |
112 | |
113 | static ssize_t remaining_size_show(struct device *dev, |
114 | struct device_attribute *attr, char *buf) |
115 | { |
116 | struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv; |
117 | |
118 | return sysfs_emit(buf, fmt: "%u\n" , fwlp->remaining_size); |
119 | } |
120 | DEVICE_ATTR_RO(remaining_size); |
121 | |
122 | umode_t |
123 | fw_upload_is_visible(struct kobject *kobj, struct attribute *attr, int n) |
124 | { |
125 | static struct fw_sysfs *fw_sysfs; |
126 | |
127 | fw_sysfs = to_fw_sysfs(kobj_to_dev(kobj)); |
128 | |
129 | if (fw_sysfs->fw_upload_priv || attr == &dev_attr_loading.attr) |
130 | return attr->mode; |
131 | |
132 | return 0; |
133 | } |
134 | |
135 | static void fw_upload_update_progress(struct fw_upload_priv *fwlp, |
136 | enum fw_upload_prog new_progress) |
137 | { |
138 | mutex_lock(&fwlp->lock); |
139 | fwlp->progress = new_progress; |
140 | mutex_unlock(lock: &fwlp->lock); |
141 | } |
142 | |
143 | static void fw_upload_set_error(struct fw_upload_priv *fwlp, |
144 | enum fw_upload_err err_code) |
145 | { |
146 | mutex_lock(&fwlp->lock); |
147 | fwlp->err_progress = fwlp->progress; |
148 | fwlp->err_code = err_code; |
149 | mutex_unlock(lock: &fwlp->lock); |
150 | } |
151 | |
152 | static void fw_upload_prog_complete(struct fw_upload_priv *fwlp) |
153 | { |
154 | mutex_lock(&fwlp->lock); |
155 | fwlp->progress = FW_UPLOAD_PROG_IDLE; |
156 | mutex_unlock(lock: &fwlp->lock); |
157 | } |
158 | |
159 | static void fw_upload_main(struct work_struct *work) |
160 | { |
161 | struct fw_upload_priv *fwlp; |
162 | struct fw_sysfs *fw_sysfs; |
163 | u32 written = 0, offset = 0; |
164 | enum fw_upload_err ret; |
165 | struct device *fw_dev; |
166 | struct fw_upload *fwl; |
167 | |
168 | fwlp = container_of(work, struct fw_upload_priv, work); |
169 | fwl = fwlp->fw_upload; |
170 | fw_sysfs = (struct fw_sysfs *)fwl->priv; |
171 | fw_dev = &fw_sysfs->dev; |
172 | |
173 | fw_upload_update_progress(fwlp, new_progress: FW_UPLOAD_PROG_PREPARING); |
174 | ret = fwlp->ops->prepare(fwl, fwlp->data, fwlp->remaining_size); |
175 | if (ret != FW_UPLOAD_ERR_NONE) { |
176 | fw_upload_set_error(fwlp, err_code: ret); |
177 | goto putdev_exit; |
178 | } |
179 | |
180 | fw_upload_update_progress(fwlp, new_progress: FW_UPLOAD_PROG_TRANSFERRING); |
181 | while (fwlp->remaining_size) { |
182 | ret = fwlp->ops->write(fwl, fwlp->data, offset, |
183 | fwlp->remaining_size, &written); |
184 | if (ret != FW_UPLOAD_ERR_NONE || !written) { |
185 | if (ret == FW_UPLOAD_ERR_NONE) { |
186 | dev_warn(fw_dev, "write-op wrote zero data\n" ); |
187 | ret = FW_UPLOAD_ERR_RW_ERROR; |
188 | } |
189 | fw_upload_set_error(fwlp, err_code: ret); |
190 | goto done; |
191 | } |
192 | |
193 | fwlp->remaining_size -= written; |
194 | offset += written; |
195 | } |
196 | |
197 | fw_upload_update_progress(fwlp, new_progress: FW_UPLOAD_PROG_PROGRAMMING); |
198 | ret = fwlp->ops->poll_complete(fwl); |
199 | if (ret != FW_UPLOAD_ERR_NONE) |
200 | fw_upload_set_error(fwlp, err_code: ret); |
201 | |
202 | done: |
203 | if (fwlp->ops->cleanup) |
204 | fwlp->ops->cleanup(fwl); |
205 | |
206 | putdev_exit: |
207 | put_device(dev: fw_dev->parent); |
208 | |
209 | /* |
210 | * Note: fwlp->remaining_size is left unmodified here to provide |
211 | * additional information on errors. It will be reinitialized when |
212 | * the next firmeware upload begins. |
213 | */ |
214 | mutex_lock(&fw_lock); |
215 | fw_free_paged_buf(fw_priv: fw_sysfs->fw_priv); |
216 | fw_state_init(fw_priv: fw_sysfs->fw_priv); |
217 | mutex_unlock(lock: &fw_lock); |
218 | fwlp->data = NULL; |
219 | fw_upload_prog_complete(fwlp); |
220 | } |
221 | |
222 | /* |
223 | * Start a worker thread to upload data to the parent driver. |
224 | * Must be called with fw_lock held. |
225 | */ |
226 | int fw_upload_start(struct fw_sysfs *fw_sysfs) |
227 | { |
228 | struct fw_priv *fw_priv = fw_sysfs->fw_priv; |
229 | struct device *fw_dev = &fw_sysfs->dev; |
230 | struct fw_upload_priv *fwlp; |
231 | |
232 | if (!fw_sysfs->fw_upload_priv) |
233 | return 0; |
234 | |
235 | if (!fw_priv->size) { |
236 | fw_free_paged_buf(fw_priv); |
237 | fw_state_init(fw_priv: fw_sysfs->fw_priv); |
238 | return 0; |
239 | } |
240 | |
241 | fwlp = fw_sysfs->fw_upload_priv; |
242 | mutex_lock(&fwlp->lock); |
243 | |
244 | /* Do not interfere with an on-going fw_upload */ |
245 | if (fwlp->progress != FW_UPLOAD_PROG_IDLE) { |
246 | mutex_unlock(lock: &fwlp->lock); |
247 | return -EBUSY; |
248 | } |
249 | |
250 | get_device(dev: fw_dev->parent); /* released in fw_upload_main */ |
251 | |
252 | fwlp->progress = FW_UPLOAD_PROG_RECEIVING; |
253 | fwlp->err_code = 0; |
254 | fwlp->remaining_size = fw_priv->size; |
255 | fwlp->data = fw_priv->data; |
256 | |
257 | pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n" , |
258 | __func__, fw_priv->fw_name, |
259 | fw_priv, fw_priv->data, |
260 | (unsigned int)fw_priv->size); |
261 | |
262 | queue_work(wq: system_long_wq, work: &fwlp->work); |
263 | mutex_unlock(lock: &fwlp->lock); |
264 | |
265 | return 0; |
266 | } |
267 | |
268 | void fw_upload_free(struct fw_sysfs *fw_sysfs) |
269 | { |
270 | struct fw_upload_priv *fw_upload_priv = fw_sysfs->fw_upload_priv; |
271 | |
272 | free_fw_priv(fw_priv: fw_sysfs->fw_priv); |
273 | kfree(objp: fw_upload_priv->fw_upload); |
274 | kfree(objp: fw_upload_priv); |
275 | } |
276 | |
277 | /** |
278 | * firmware_upload_register() - register for the firmware upload sysfs API |
279 | * @module: kernel module of this device |
280 | * @parent: parent device instantiating firmware upload |
281 | * @name: firmware name to be associated with this device |
282 | * @ops: pointer to structure of firmware upload ops |
283 | * @dd_handle: pointer to parent driver private data |
284 | * |
285 | * @name must be unique among all users of firmware upload. The firmware |
286 | * sysfs files for this device will be found at /sys/class/firmware/@name. |
287 | * |
288 | * Return: struct fw_upload pointer or ERR_PTR() |
289 | * |
290 | **/ |
291 | struct fw_upload * |
292 | firmware_upload_register(struct module *module, struct device *parent, |
293 | const char *name, const struct fw_upload_ops *ops, |
294 | void *dd_handle) |
295 | { |
296 | u32 opt_flags = FW_OPT_NOCACHE; |
297 | struct fw_upload *fw_upload; |
298 | struct fw_upload_priv *fw_upload_priv; |
299 | struct fw_sysfs *fw_sysfs; |
300 | struct fw_priv *fw_priv; |
301 | struct device *fw_dev; |
302 | int ret; |
303 | |
304 | if (!name || name[0] == '\0') |
305 | return ERR_PTR(error: -EINVAL); |
306 | |
307 | if (!ops || !ops->cancel || !ops->prepare || |
308 | !ops->write || !ops->poll_complete) { |
309 | dev_err(parent, "Attempt to register without all required ops\n" ); |
310 | return ERR_PTR(error: -EINVAL); |
311 | } |
312 | |
313 | if (!try_module_get(module)) |
314 | return ERR_PTR(error: -EFAULT); |
315 | |
316 | fw_upload = kzalloc(size: sizeof(*fw_upload), GFP_KERNEL); |
317 | if (!fw_upload) { |
318 | ret = -ENOMEM; |
319 | goto exit_module_put; |
320 | } |
321 | |
322 | fw_upload_priv = kzalloc(size: sizeof(*fw_upload_priv), GFP_KERNEL); |
323 | if (!fw_upload_priv) { |
324 | ret = -ENOMEM; |
325 | goto free_fw_upload; |
326 | } |
327 | |
328 | fw_upload_priv->fw_upload = fw_upload; |
329 | fw_upload_priv->ops = ops; |
330 | mutex_init(&fw_upload_priv->lock); |
331 | fw_upload_priv->module = module; |
332 | fw_upload_priv->name = name; |
333 | fw_upload_priv->err_code = 0; |
334 | fw_upload_priv->progress = FW_UPLOAD_PROG_IDLE; |
335 | INIT_WORK(&fw_upload_priv->work, fw_upload_main); |
336 | fw_upload->dd_handle = dd_handle; |
337 | |
338 | fw_sysfs = fw_create_instance(NULL, fw_name: name, device: parent, opt_flags); |
339 | if (IS_ERR(ptr: fw_sysfs)) { |
340 | ret = PTR_ERR(ptr: fw_sysfs); |
341 | goto free_fw_upload_priv; |
342 | } |
343 | fw_upload->priv = fw_sysfs; |
344 | fw_sysfs->fw_upload_priv = fw_upload_priv; |
345 | fw_dev = &fw_sysfs->dev; |
346 | |
347 | ret = alloc_lookup_fw_priv(fw_name: name, fwc: &fw_cache, fw_priv: &fw_priv, NULL, size: 0, offset: 0, |
348 | opt_flags: FW_OPT_NOCACHE); |
349 | if (ret != 0) { |
350 | if (ret > 0) |
351 | ret = -EINVAL; |
352 | goto free_fw_sysfs; |
353 | } |
354 | fw_priv->is_paged_buf = true; |
355 | fw_sysfs->fw_priv = fw_priv; |
356 | |
357 | ret = device_add(dev: fw_dev); |
358 | if (ret) { |
359 | dev_err(fw_dev, "%s: device_register failed\n" , __func__); |
360 | put_device(dev: fw_dev); |
361 | goto exit_module_put; |
362 | } |
363 | |
364 | return fw_upload; |
365 | |
366 | free_fw_sysfs: |
367 | kfree(objp: fw_sysfs); |
368 | |
369 | free_fw_upload_priv: |
370 | kfree(objp: fw_upload_priv); |
371 | |
372 | free_fw_upload: |
373 | kfree(objp: fw_upload); |
374 | |
375 | exit_module_put: |
376 | module_put(module); |
377 | |
378 | return ERR_PTR(error: ret); |
379 | } |
380 | EXPORT_SYMBOL_GPL(firmware_upload_register); |
381 | |
382 | /** |
383 | * firmware_upload_unregister() - Unregister firmware upload interface |
384 | * @fw_upload: pointer to struct fw_upload |
385 | **/ |
386 | void firmware_upload_unregister(struct fw_upload *fw_upload) |
387 | { |
388 | struct fw_sysfs *fw_sysfs = fw_upload->priv; |
389 | struct fw_upload_priv *fw_upload_priv = fw_sysfs->fw_upload_priv; |
390 | struct module *module = fw_upload_priv->module; |
391 | |
392 | mutex_lock(&fw_upload_priv->lock); |
393 | if (fw_upload_priv->progress == FW_UPLOAD_PROG_IDLE) { |
394 | mutex_unlock(lock: &fw_upload_priv->lock); |
395 | goto unregister; |
396 | } |
397 | |
398 | fw_upload_priv->ops->cancel(fw_upload); |
399 | mutex_unlock(lock: &fw_upload_priv->lock); |
400 | |
401 | /* Ensure lower-level device-driver is finished */ |
402 | flush_work(work: &fw_upload_priv->work); |
403 | |
404 | unregister: |
405 | device_unregister(dev: &fw_sysfs->dev); |
406 | module_put(module); |
407 | } |
408 | EXPORT_SYMBOL_GPL(firmware_upload_unregister); |
409 | |