diff options
-rw-r--r-- | lib/test_firmware.c | 710 | ||||
-rwxr-xr-x | tools/testing/selftests/firmware/fw_filesystem.sh | 241 |
2 files changed, 949 insertions, 2 deletions
diff --git a/lib/test_firmware.c b/lib/test_firmware.c index 09371b0a9baf..64a4c76cba2b 100644 --- a/lib/test_firmware.c +++ b/lib/test_firmware.c | |||
@@ -19,10 +19,85 @@ | |||
19 | #include <linux/miscdevice.h> | 19 | #include <linux/miscdevice.h> |
20 | #include <linux/slab.h> | 20 | #include <linux/slab.h> |
21 | #include <linux/uaccess.h> | 21 | #include <linux/uaccess.h> |
22 | #include <linux/delay.h> | ||
23 | #include <linux/kthread.h> | ||
24 | |||
25 | #define TEST_FIRMWARE_NAME "test-firmware.bin" | ||
26 | #define TEST_FIRMWARE_NUM_REQS 4 | ||
22 | 27 | ||
23 | static DEFINE_MUTEX(test_fw_mutex); | 28 | static DEFINE_MUTEX(test_fw_mutex); |
24 | static const struct firmware *test_firmware; | 29 | static const struct firmware *test_firmware; |
25 | 30 | ||
31 | struct test_batched_req { | ||
32 | u8 idx; | ||
33 | int rc; | ||
34 | bool sent; | ||
35 | const struct firmware *fw; | ||
36 | const char *name; | ||
37 | struct completion completion; | ||
38 | struct task_struct *task; | ||
39 | struct device *dev; | ||
40 | }; | ||
41 | |||
42 | /** | ||
43 | * test_config - represents configuration for the test for different triggers | ||
44 | * | ||
45 | * @name: the name of the firmware file to look for | ||
46 | * @sync_direct: when the sync trigger is used if this is true | ||
47 | * request_firmware_direct() will be used instead. | ||
48 | * @send_uevent: whether or not to send a uevent for async requests | ||
49 | * @num_requests: number of requests to try per test case. This is trigger | ||
50 | * specific. | ||
51 | * @reqs: stores all requests information | ||
52 | * @read_fw_idx: index of thread from which we want to read firmware results | ||
53 | * from through the read_fw trigger. | ||
54 | * @test_result: a test may use this to collect the result from the call | ||
55 | * of the request_firmware*() calls used in their tests. In order of | ||
56 | * priority we always keep first any setup error. If no setup errors were | ||
57 | * found then we move on to the first error encountered while running the | ||
58 | * API. Note that for async calls this typically will be a successful | ||
59 | * result (0) unless of course you've used bogus parameters, or the system | ||
60 | * is out of memory. In the async case the callback is expected to do a | ||
61 | * bit more homework to figure out what happened, unfortunately the only | ||
62 | * information passed today on error is the fact that no firmware was | ||
63 | * found so we can only assume -ENOENT on async calls if the firmware is | ||
64 | * NULL. | ||
65 | * | ||
66 | * Errors you can expect: | ||
67 | * | ||
68 | * API specific: | ||
69 | * | ||
70 | * 0: success for sync, for async it means request was sent | ||
71 | * -EINVAL: invalid parameters or request | ||
72 | * -ENOENT: files not found | ||
73 | * | ||
74 | * System environment: | ||
75 | * | ||
76 | * -ENOMEM: memory pressure on system | ||
77 | * -ENODEV: out of number of devices to test | ||
78 | * -EINVAL: an unexpected error has occurred | ||
79 | * @req_firmware: if @sync_direct is true this is set to | ||
80 | * request_firmware_direct(), otherwise request_firmware() | ||
81 | */ | ||
82 | struct test_config { | ||
83 | char *name; | ||
84 | bool sync_direct; | ||
85 | bool send_uevent; | ||
86 | u8 num_requests; | ||
87 | u8 read_fw_idx; | ||
88 | |||
89 | /* | ||
90 | * These below don't belong her but we'll move them once we create | ||
91 | * a struct fw_test_device and stuff the misc_dev under there later. | ||
92 | */ | ||
93 | struct test_batched_req *reqs; | ||
94 | int test_result; | ||
95 | int (*req_firmware)(const struct firmware **fw, const char *name, | ||
96 | struct device *device); | ||
97 | }; | ||
98 | |||
99 | struct test_config *test_fw_config; | ||
100 | |||
26 | static ssize_t test_fw_misc_read(struct file *f, char __user *buf, | 101 | static ssize_t test_fw_misc_read(struct file *f, char __user *buf, |
27 | size_t size, loff_t *offset) | 102 | size_t size, loff_t *offset) |
28 | { | 103 | { |
@@ -42,6 +117,338 @@ static const struct file_operations test_fw_fops = { | |||
42 | .read = test_fw_misc_read, | 117 | .read = test_fw_misc_read, |
43 | }; | 118 | }; |
44 | 119 | ||
120 | static void __test_release_all_firmware(void) | ||
121 | { | ||
122 | struct test_batched_req *req; | ||
123 | u8 i; | ||
124 | |||
125 | if (!test_fw_config->reqs) | ||
126 | return; | ||
127 | |||
128 | for (i = 0; i < test_fw_config->num_requests; i++) { | ||
129 | req = &test_fw_config->reqs[i]; | ||
130 | if (req->fw) | ||
131 | release_firmware(req->fw); | ||
132 | } | ||
133 | |||
134 | vfree(test_fw_config->reqs); | ||
135 | test_fw_config->reqs = NULL; | ||
136 | } | ||
137 | |||
138 | static void test_release_all_firmware(void) | ||
139 | { | ||
140 | mutex_lock(&test_fw_mutex); | ||
141 | __test_release_all_firmware(); | ||
142 | mutex_unlock(&test_fw_mutex); | ||
143 | } | ||
144 | |||
145 | |||
146 | static void __test_firmware_config_free(void) | ||
147 | { | ||
148 | __test_release_all_firmware(); | ||
149 | kfree_const(test_fw_config->name); | ||
150 | test_fw_config->name = NULL; | ||
151 | } | ||
152 | |||
153 | /* | ||
154 | * XXX: move to kstrncpy() once merged. | ||
155 | * | ||
156 | * Users should use kfree_const() when freeing these. | ||
157 | */ | ||
158 | static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp) | ||
159 | { | ||
160 | *dst = kstrndup(name, count, gfp); | ||
161 | if (!*dst) | ||
162 | return -ENOSPC; | ||
163 | return count; | ||
164 | } | ||
165 | |||
166 | static int __test_firmware_config_init(void) | ||
167 | { | ||
168 | int ret; | ||
169 | |||
170 | ret = __kstrncpy(&test_fw_config->name, TEST_FIRMWARE_NAME, | ||
171 | strlen(TEST_FIRMWARE_NAME), GFP_KERNEL); | ||
172 | if (ret < 0) | ||
173 | goto out; | ||
174 | |||
175 | test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS; | ||
176 | test_fw_config->send_uevent = true; | ||
177 | test_fw_config->sync_direct = false; | ||
178 | test_fw_config->req_firmware = request_firmware; | ||
179 | test_fw_config->test_result = 0; | ||
180 | test_fw_config->reqs = NULL; | ||
181 | |||
182 | return 0; | ||
183 | |||
184 | out: | ||
185 | __test_firmware_config_free(); | ||
186 | return ret; | ||
187 | } | ||
188 | |||
189 | static ssize_t reset_store(struct device *dev, | ||
190 | struct device_attribute *attr, | ||
191 | const char *buf, size_t count) | ||
192 | { | ||
193 | int ret; | ||
194 | |||
195 | mutex_lock(&test_fw_mutex); | ||
196 | |||
197 | __test_firmware_config_free(); | ||
198 | |||
199 | ret = __test_firmware_config_init(); | ||
200 | if (ret < 0) { | ||
201 | ret = -ENOMEM; | ||
202 | pr_err("could not alloc settings for config trigger: %d\n", | ||
203 | ret); | ||
204 | goto out; | ||
205 | } | ||
206 | |||
207 | pr_info("reset\n"); | ||
208 | ret = count; | ||
209 | |||
210 | out: | ||
211 | mutex_unlock(&test_fw_mutex); | ||
212 | |||
213 | return ret; | ||
214 | } | ||
215 | static DEVICE_ATTR_WO(reset); | ||
216 | |||
217 | static ssize_t config_show(struct device *dev, | ||
218 | struct device_attribute *attr, | ||
219 | char *buf) | ||
220 | { | ||
221 | int len = 0; | ||
222 | |||
223 | mutex_lock(&test_fw_mutex); | ||
224 | |||
225 | len += snprintf(buf, PAGE_SIZE, | ||
226 | "Custom trigger configuration for: %s\n", | ||
227 | dev_name(dev)); | ||
228 | |||
229 | if (test_fw_config->name) | ||
230 | len += snprintf(buf+len, PAGE_SIZE, | ||
231 | "name:\t%s\n", | ||
232 | test_fw_config->name); | ||
233 | else | ||
234 | len += snprintf(buf+len, PAGE_SIZE, | ||
235 | "name:\tEMTPY\n"); | ||
236 | |||
237 | len += snprintf(buf+len, PAGE_SIZE, | ||
238 | "num_requests:\t%u\n", test_fw_config->num_requests); | ||
239 | |||
240 | len += snprintf(buf+len, PAGE_SIZE, | ||
241 | "send_uevent:\t\t%s\n", | ||
242 | test_fw_config->send_uevent ? | ||
243 | "FW_ACTION_HOTPLUG" : | ||
244 | "FW_ACTION_NOHOTPLUG"); | ||
245 | len += snprintf(buf+len, PAGE_SIZE, | ||
246 | "sync_direct:\t\t%s\n", | ||
247 | test_fw_config->sync_direct ? "true" : "false"); | ||
248 | len += snprintf(buf+len, PAGE_SIZE, | ||
249 | "read_fw_idx:\t%u\n", test_fw_config->read_fw_idx); | ||
250 | |||
251 | mutex_unlock(&test_fw_mutex); | ||
252 | |||
253 | return len; | ||
254 | } | ||
255 | static DEVICE_ATTR_RO(config); | ||
256 | |||
257 | static ssize_t config_name_store(struct device *dev, | ||
258 | struct device_attribute *attr, | ||
259 | const char *buf, size_t count) | ||
260 | { | ||
261 | int ret; | ||
262 | |||
263 | mutex_lock(&test_fw_mutex); | ||
264 | kfree_const(test_fw_config->name); | ||
265 | ret = __kstrncpy(&test_fw_config->name, buf, count, GFP_KERNEL); | ||
266 | mutex_unlock(&test_fw_mutex); | ||
267 | |||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | /* | ||
272 | * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE. | ||
273 | */ | ||
274 | static ssize_t config_test_show_str(char *dst, | ||
275 | char *src) | ||
276 | { | ||
277 | int len; | ||
278 | |||
279 | mutex_lock(&test_fw_mutex); | ||
280 | len = snprintf(dst, PAGE_SIZE, "%s\n", src); | ||
281 | mutex_unlock(&test_fw_mutex); | ||
282 | |||
283 | return len; | ||
284 | } | ||
285 | |||
286 | static int test_dev_config_update_bool(const char *buf, size_t size, | ||
287 | bool *cfg) | ||
288 | { | ||
289 | int ret; | ||
290 | |||
291 | mutex_lock(&test_fw_mutex); | ||
292 | if (strtobool(buf, cfg) < 0) | ||
293 | ret = -EINVAL; | ||
294 | else | ||
295 | ret = size; | ||
296 | mutex_unlock(&test_fw_mutex); | ||
297 | |||
298 | return ret; | ||
299 | } | ||
300 | |||
301 | static ssize_t | ||
302 | test_dev_config_show_bool(char *buf, | ||
303 | bool config) | ||
304 | { | ||
305 | bool val; | ||
306 | |||
307 | mutex_lock(&test_fw_mutex); | ||
308 | val = config; | ||
309 | mutex_unlock(&test_fw_mutex); | ||
310 | |||
311 | return snprintf(buf, PAGE_SIZE, "%d\n", val); | ||
312 | } | ||
313 | |||
314 | static ssize_t test_dev_config_show_int(char *buf, int cfg) | ||
315 | { | ||
316 | int val; | ||
317 | |||
318 | mutex_lock(&test_fw_mutex); | ||
319 | val = cfg; | ||
320 | mutex_unlock(&test_fw_mutex); | ||
321 | |||
322 | return snprintf(buf, PAGE_SIZE, "%d\n", val); | ||
323 | } | ||
324 | |||
325 | static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) | ||
326 | { | ||
327 | int ret; | ||
328 | long new; | ||
329 | |||
330 | ret = kstrtol(buf, 10, &new); | ||
331 | if (ret) | ||
332 | return ret; | ||
333 | |||
334 | if (new > U8_MAX) | ||
335 | return -EINVAL; | ||
336 | |||
337 | mutex_lock(&test_fw_mutex); | ||
338 | *(u8 *)cfg = new; | ||
339 | mutex_unlock(&test_fw_mutex); | ||
340 | |||
341 | /* Always return full write size even if we didn't consume all */ | ||
342 | return size; | ||
343 | } | ||
344 | |||
345 | static ssize_t test_dev_config_show_u8(char *buf, u8 cfg) | ||
346 | { | ||
347 | u8 val; | ||
348 | |||
349 | mutex_lock(&test_fw_mutex); | ||
350 | val = cfg; | ||
351 | mutex_unlock(&test_fw_mutex); | ||
352 | |||
353 | return snprintf(buf, PAGE_SIZE, "%u\n", val); | ||
354 | } | ||
355 | |||
356 | static ssize_t config_name_show(struct device *dev, | ||
357 | struct device_attribute *attr, | ||
358 | char *buf) | ||
359 | { | ||
360 | return config_test_show_str(buf, test_fw_config->name); | ||
361 | } | ||
362 | static DEVICE_ATTR(config_name, 0644, config_name_show, config_name_store); | ||
363 | |||
364 | static ssize_t config_num_requests_store(struct device *dev, | ||
365 | struct device_attribute *attr, | ||
366 | const char *buf, size_t count) | ||
367 | { | ||
368 | int rc; | ||
369 | |||
370 | mutex_lock(&test_fw_mutex); | ||
371 | if (test_fw_config->reqs) { | ||
372 | pr_err("Must call release_all_firmware prior to changing config\n"); | ||
373 | rc = -EINVAL; | ||
374 | goto out; | ||
375 | } | ||
376 | mutex_unlock(&test_fw_mutex); | ||
377 | |||
378 | rc = test_dev_config_update_u8(buf, count, | ||
379 | &test_fw_config->num_requests); | ||
380 | |||
381 | out: | ||
382 | return rc; | ||
383 | } | ||
384 | |||
385 | static ssize_t config_num_requests_show(struct device *dev, | ||
386 | struct device_attribute *attr, | ||
387 | char *buf) | ||
388 | { | ||
389 | return test_dev_config_show_u8(buf, test_fw_config->num_requests); | ||
390 | } | ||
391 | static DEVICE_ATTR(config_num_requests, 0644, config_num_requests_show, | ||
392 | config_num_requests_store); | ||
393 | |||
394 | static ssize_t config_sync_direct_store(struct device *dev, | ||
395 | struct device_attribute *attr, | ||
396 | const char *buf, size_t count) | ||
397 | { | ||
398 | int rc = test_dev_config_update_bool(buf, count, | ||
399 | &test_fw_config->sync_direct); | ||
400 | |||
401 | if (rc == count) | ||
402 | test_fw_config->req_firmware = test_fw_config->sync_direct ? | ||
403 | request_firmware_direct : | ||
404 | request_firmware; | ||
405 | return rc; | ||
406 | } | ||
407 | |||
408 | static ssize_t config_sync_direct_show(struct device *dev, | ||
409 | struct device_attribute *attr, | ||
410 | char *buf) | ||
411 | { | ||
412 | return test_dev_config_show_bool(buf, test_fw_config->sync_direct); | ||
413 | } | ||
414 | static DEVICE_ATTR(config_sync_direct, 0644, config_sync_direct_show, | ||
415 | config_sync_direct_store); | ||
416 | |||
417 | static ssize_t config_send_uevent_store(struct device *dev, | ||
418 | struct device_attribute *attr, | ||
419 | const char *buf, size_t count) | ||
420 | { | ||
421 | return test_dev_config_update_bool(buf, count, | ||
422 | &test_fw_config->send_uevent); | ||
423 | } | ||
424 | |||
425 | static ssize_t config_send_uevent_show(struct device *dev, | ||
426 | struct device_attribute *attr, | ||
427 | char *buf) | ||
428 | { | ||
429 | return test_dev_config_show_bool(buf, test_fw_config->send_uevent); | ||
430 | } | ||
431 | static DEVICE_ATTR(config_send_uevent, 0644, config_send_uevent_show, | ||
432 | config_send_uevent_store); | ||
433 | |||
434 | static ssize_t config_read_fw_idx_store(struct device *dev, | ||
435 | struct device_attribute *attr, | ||
436 | const char *buf, size_t count) | ||
437 | { | ||
438 | return test_dev_config_update_u8(buf, count, | ||
439 | &test_fw_config->read_fw_idx); | ||
440 | } | ||
441 | |||
442 | static ssize_t config_read_fw_idx_show(struct device *dev, | ||
443 | struct device_attribute *attr, | ||
444 | char *buf) | ||
445 | { | ||
446 | return test_dev_config_show_u8(buf, test_fw_config->read_fw_idx); | ||
447 | } | ||
448 | static DEVICE_ATTR(config_read_fw_idx, 0644, config_read_fw_idx_show, | ||
449 | config_read_fw_idx_store); | ||
450 | |||
451 | |||
45 | static ssize_t trigger_request_store(struct device *dev, | 452 | static ssize_t trigger_request_store(struct device *dev, |
46 | struct device_attribute *attr, | 453 | struct device_attribute *attr, |
47 | const char *buf, size_t count) | 454 | const char *buf, size_t count) |
@@ -170,12 +577,301 @@ out: | |||
170 | } | 577 | } |
171 | static DEVICE_ATTR_WO(trigger_custom_fallback); | 578 | static DEVICE_ATTR_WO(trigger_custom_fallback); |
172 | 579 | ||
580 | static int test_fw_run_batch_request(void *data) | ||
581 | { | ||
582 | struct test_batched_req *req = data; | ||
583 | |||
584 | if (!req) { | ||
585 | test_fw_config->test_result = -EINVAL; | ||
586 | return -EINVAL; | ||
587 | } | ||
588 | |||
589 | req->rc = test_fw_config->req_firmware(&req->fw, req->name, req->dev); | ||
590 | if (req->rc) { | ||
591 | pr_info("#%u: batched sync load failed: %d\n", | ||
592 | req->idx, req->rc); | ||
593 | if (!test_fw_config->test_result) | ||
594 | test_fw_config->test_result = req->rc; | ||
595 | } else if (req->fw) { | ||
596 | req->sent = true; | ||
597 | pr_info("#%u: batched sync loaded %zu\n", | ||
598 | req->idx, req->fw->size); | ||
599 | } | ||
600 | complete(&req->completion); | ||
601 | |||
602 | req->task = NULL; | ||
603 | |||
604 | return 0; | ||
605 | } | ||
606 | |||
607 | /* | ||
608 | * We use a kthread as otherwise the kernel serializes all our sync requests | ||
609 | * and we would not be able to mimic batched requests on a sync call. Batched | ||
610 | * requests on a sync call can for instance happen on a device driver when | ||
611 | * multiple cards are used and firmware loading happens outside of probe. | ||
612 | */ | ||
613 | static ssize_t trigger_batched_requests_store(struct device *dev, | ||
614 | struct device_attribute *attr, | ||
615 | const char *buf, size_t count) | ||
616 | { | ||
617 | struct test_batched_req *req; | ||
618 | int rc; | ||
619 | u8 i; | ||
620 | |||
621 | mutex_lock(&test_fw_mutex); | ||
622 | |||
623 | test_fw_config->reqs = vzalloc(sizeof(struct test_batched_req) * | ||
624 | test_fw_config->num_requests * 2); | ||
625 | if (!test_fw_config->reqs) { | ||
626 | rc = -ENOMEM; | ||
627 | goto out_unlock; | ||
628 | } | ||
629 | |||
630 | pr_info("batched sync firmware loading '%s' %u times\n", | ||
631 | test_fw_config->name, test_fw_config->num_requests); | ||
632 | |||
633 | for (i = 0; i < test_fw_config->num_requests; i++) { | ||
634 | req = &test_fw_config->reqs[i]; | ||
635 | if (!req) { | ||
636 | WARN_ON(1); | ||
637 | rc = -ENOMEM; | ||
638 | goto out_bail; | ||
639 | } | ||
640 | req->fw = NULL; | ||
641 | req->idx = i; | ||
642 | req->name = test_fw_config->name; | ||
643 | req->dev = dev; | ||
644 | init_completion(&req->completion); | ||
645 | req->task = kthread_run(test_fw_run_batch_request, req, | ||
646 | "%s-%u", KBUILD_MODNAME, req->idx); | ||
647 | if (!req->task || IS_ERR(req->task)) { | ||
648 | pr_err("Setting up thread %u failed\n", req->idx); | ||
649 | req->task = NULL; | ||
650 | rc = -ENOMEM; | ||
651 | goto out_bail; | ||
652 | } | ||
653 | } | ||
654 | |||
655 | rc = count; | ||
656 | |||
657 | /* | ||
658 | * We require an explicit release to enable more time and delay of | ||
659 | * calling release_firmware() to improve our chances of forcing a | ||
660 | * batched request. If we instead called release_firmware() right away | ||
661 | * then we might miss on an opportunity of having a successful firmware | ||
662 | * request pass on the opportunity to be come a batched request. | ||
663 | */ | ||
664 | |||
665 | out_bail: | ||
666 | for (i = 0; i < test_fw_config->num_requests; i++) { | ||
667 | req = &test_fw_config->reqs[i]; | ||
668 | if (req->task || req->sent) | ||
669 | wait_for_completion(&req->completion); | ||
670 | } | ||
671 | |||
672 | /* Override any worker error if we had a general setup error */ | ||
673 | if (rc < 0) | ||
674 | test_fw_config->test_result = rc; | ||
675 | |||
676 | out_unlock: | ||
677 | mutex_unlock(&test_fw_mutex); | ||
678 | |||
679 | return rc; | ||
680 | } | ||
681 | static DEVICE_ATTR_WO(trigger_batched_requests); | ||
682 | |||
683 | /* | ||
684 | * We wait for each callback to return with the lock held, no need to lock here | ||
685 | */ | ||
686 | static void trigger_batched_cb(const struct firmware *fw, void *context) | ||
687 | { | ||
688 | struct test_batched_req *req = context; | ||
689 | |||
690 | if (!req) { | ||
691 | test_fw_config->test_result = -EINVAL; | ||
692 | return; | ||
693 | } | ||
694 | |||
695 | /* forces *some* batched requests to queue up */ | ||
696 | if (!req->idx) | ||
697 | ssleep(2); | ||
698 | |||
699 | req->fw = fw; | ||
700 | |||
701 | /* | ||
702 | * Unfortunately the firmware API gives us nothing other than a null FW | ||
703 | * if the firmware was not found on async requests. Best we can do is | ||
704 | * just assume -ENOENT. A better API would pass the actual return | ||
705 | * value to the callback. | ||
706 | */ | ||
707 | if (!fw && !test_fw_config->test_result) | ||
708 | test_fw_config->test_result = -ENOENT; | ||
709 | |||
710 | complete(&req->completion); | ||
711 | } | ||
712 | |||
713 | static | ||
714 | ssize_t trigger_batched_requests_async_store(struct device *dev, | ||
715 | struct device_attribute *attr, | ||
716 | const char *buf, size_t count) | ||
717 | { | ||
718 | struct test_batched_req *req; | ||
719 | bool send_uevent; | ||
720 | int rc; | ||
721 | u8 i; | ||
722 | |||
723 | mutex_lock(&test_fw_mutex); | ||
724 | |||
725 | test_fw_config->reqs = vzalloc(sizeof(struct test_batched_req) * | ||
726 | test_fw_config->num_requests * 2); | ||
727 | if (!test_fw_config->reqs) { | ||
728 | rc = -ENOMEM; | ||
729 | goto out; | ||
730 | } | ||
731 | |||
732 | pr_info("batched loading '%s' custom fallback mechanism %u times\n", | ||
733 | test_fw_config->name, test_fw_config->num_requests); | ||
734 | |||
735 | send_uevent = test_fw_config->send_uevent ? FW_ACTION_HOTPLUG : | ||
736 | FW_ACTION_NOHOTPLUG; | ||
737 | |||
738 | for (i = 0; i < test_fw_config->num_requests; i++) { | ||
739 | req = &test_fw_config->reqs[i]; | ||
740 | if (!req) { | ||
741 | WARN_ON(1); | ||
742 | goto out_bail; | ||
743 | } | ||
744 | req->name = test_fw_config->name; | ||
745 | req->fw = NULL; | ||
746 | req->idx = i; | ||
747 | init_completion(&req->completion); | ||
748 | rc = request_firmware_nowait(THIS_MODULE, send_uevent, | ||
749 | req->name, | ||
750 | dev, GFP_KERNEL, req, | ||
751 | trigger_batched_cb); | ||
752 | if (rc) { | ||
753 | pr_info("#%u: batched async load failed setup: %d\n", | ||
754 | i, rc); | ||
755 | req->rc = rc; | ||
756 | goto out_bail; | ||
757 | } else | ||
758 | req->sent = true; | ||
759 | } | ||
760 | |||
761 | rc = count; | ||
762 | |||
763 | out_bail: | ||
764 | |||
765 | /* | ||
766 | * We require an explicit release to enable more time and delay of | ||
767 | * calling release_firmware() to improve our chances of forcing a | ||
768 | * batched request. If we instead called release_firmware() right away | ||
769 | * then we might miss on an opportunity of having a successful firmware | ||
770 | * request pass on the opportunity to be come a batched request. | ||
771 | */ | ||
772 | |||
773 | for (i = 0; i < test_fw_config->num_requests; i++) { | ||
774 | req = &test_fw_config->reqs[i]; | ||
775 | if (req->sent) | ||
776 | wait_for_completion(&req->completion); | ||
777 | } | ||
778 | |||
779 | /* Override any worker error if we had a general setup error */ | ||
780 | if (rc < 0) | ||
781 | test_fw_config->test_result = rc; | ||
782 | |||
783 | out: | ||
784 | mutex_unlock(&test_fw_mutex); | ||
785 | |||
786 | return rc; | ||
787 | } | ||
788 | static DEVICE_ATTR_WO(trigger_batched_requests_async); | ||
789 | |||
790 | static ssize_t test_result_show(struct device *dev, | ||
791 | struct device_attribute *attr, | ||
792 | char *buf) | ||
793 | { | ||
794 | return test_dev_config_show_int(buf, test_fw_config->test_result); | ||
795 | } | ||
796 | static DEVICE_ATTR_RO(test_result); | ||
797 | |||
798 | static ssize_t release_all_firmware_store(struct device *dev, | ||
799 | struct device_attribute *attr, | ||
800 | const char *buf, size_t count) | ||
801 | { | ||
802 | test_release_all_firmware(); | ||
803 | return count; | ||
804 | } | ||
805 | static DEVICE_ATTR_WO(release_all_firmware); | ||
806 | |||
807 | static ssize_t read_firmware_show(struct device *dev, | ||
808 | struct device_attribute *attr, | ||
809 | char *buf) | ||
810 | { | ||
811 | struct test_batched_req *req; | ||
812 | u8 idx; | ||
813 | ssize_t rc = 0; | ||
814 | |||
815 | mutex_lock(&test_fw_mutex); | ||
816 | |||
817 | idx = test_fw_config->read_fw_idx; | ||
818 | if (idx >= test_fw_config->num_requests) { | ||
819 | rc = -ERANGE; | ||
820 | goto out; | ||
821 | } | ||
822 | |||
823 | if (!test_fw_config->reqs) { | ||
824 | rc = -EINVAL; | ||
825 | goto out; | ||
826 | } | ||
827 | |||
828 | req = &test_fw_config->reqs[idx]; | ||
829 | if (!req->fw) { | ||
830 | pr_err("#%u: failed to async load firmware\n", idx); | ||
831 | rc = -ENOENT; | ||
832 | goto out; | ||
833 | } | ||
834 | |||
835 | pr_info("#%u: loaded %zu\n", idx, req->fw->size); | ||
836 | |||
837 | if (req->fw->size > PAGE_SIZE) { | ||
838 | pr_err("Testing interface must use PAGE_SIZE firmware for now\n"); | ||
839 | rc = -EINVAL; | ||
840 | } | ||
841 | memcpy(buf, req->fw->data, req->fw->size); | ||
842 | |||
843 | rc = req->fw->size; | ||
844 | out: | ||
845 | mutex_unlock(&test_fw_mutex); | ||
846 | |||
847 | return rc; | ||
848 | } | ||
849 | static DEVICE_ATTR_RO(read_firmware); | ||
850 | |||
173 | #define TEST_FW_DEV_ATTR(name) &dev_attr_##name.attr | 851 | #define TEST_FW_DEV_ATTR(name) &dev_attr_##name.attr |
174 | 852 | ||
175 | static struct attribute *test_dev_attrs[] = { | 853 | static struct attribute *test_dev_attrs[] = { |
854 | TEST_FW_DEV_ATTR(reset), | ||
855 | |||
856 | TEST_FW_DEV_ATTR(config), | ||
857 | TEST_FW_DEV_ATTR(config_name), | ||
858 | TEST_FW_DEV_ATTR(config_num_requests), | ||
859 | TEST_FW_DEV_ATTR(config_sync_direct), | ||
860 | TEST_FW_DEV_ATTR(config_send_uevent), | ||
861 | TEST_FW_DEV_ATTR(config_read_fw_idx), | ||
862 | |||
863 | /* These don't use the config at all - they could be ported! */ | ||
176 | TEST_FW_DEV_ATTR(trigger_request), | 864 | TEST_FW_DEV_ATTR(trigger_request), |
177 | TEST_FW_DEV_ATTR(trigger_async_request), | 865 | TEST_FW_DEV_ATTR(trigger_async_request), |
178 | TEST_FW_DEV_ATTR(trigger_custom_fallback), | 866 | TEST_FW_DEV_ATTR(trigger_custom_fallback), |
867 | |||
868 | /* These use the config and can use the test_result */ | ||
869 | TEST_FW_DEV_ATTR(trigger_batched_requests), | ||
870 | TEST_FW_DEV_ATTR(trigger_batched_requests_async), | ||
871 | |||
872 | TEST_FW_DEV_ATTR(release_all_firmware), | ||
873 | TEST_FW_DEV_ATTR(test_result), | ||
874 | TEST_FW_DEV_ATTR(read_firmware), | ||
179 | NULL, | 875 | NULL, |
180 | }; | 876 | }; |
181 | 877 | ||
@@ -192,8 +888,17 @@ static int __init test_firmware_init(void) | |||
192 | { | 888 | { |
193 | int rc; | 889 | int rc; |
194 | 890 | ||
891 | test_fw_config = kzalloc(sizeof(struct test_config), GFP_KERNEL); | ||
892 | if (!test_fw_config) | ||
893 | return -ENOMEM; | ||
894 | |||
895 | rc = __test_firmware_config_init(); | ||
896 | if (rc) | ||
897 | return rc; | ||
898 | |||
195 | rc = misc_register(&test_fw_misc_device); | 899 | rc = misc_register(&test_fw_misc_device); |
196 | if (rc) { | 900 | if (rc) { |
901 | kfree(test_fw_config); | ||
197 | pr_err("could not register misc device: %d\n", rc); | 902 | pr_err("could not register misc device: %d\n", rc); |
198 | return rc; | 903 | return rc; |
199 | } | 904 | } |
@@ -207,8 +912,13 @@ module_init(test_firmware_init); | |||
207 | 912 | ||
208 | static void __exit test_firmware_exit(void) | 913 | static void __exit test_firmware_exit(void) |
209 | { | 914 | { |
915 | mutex_lock(&test_fw_mutex); | ||
210 | release_firmware(test_firmware); | 916 | release_firmware(test_firmware); |
211 | misc_deregister(&test_fw_misc_device); | 917 | misc_deregister(&test_fw_misc_device); |
918 | __test_firmware_config_free(); | ||
919 | kfree(test_fw_config); | ||
920 | mutex_unlock(&test_fw_mutex); | ||
921 | |||
212 | pr_warn("removed interface\n"); | 922 | pr_warn("removed interface\n"); |
213 | } | 923 | } |
214 | 924 | ||
diff --git a/tools/testing/selftests/firmware/fw_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh index e35691239350..7d8fd2e3695a 100755 --- a/tools/testing/selftests/firmware/fw_filesystem.sh +++ b/tools/testing/selftests/firmware/fw_filesystem.sh | |||
@@ -25,8 +25,9 @@ if [ ! -d $DIR ]; then | |||
25 | fi | 25 | fi |
26 | 26 | ||
27 | # CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/ | 27 | # CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/ |
28 | # These days no one enables CONFIG_FW_LOADER_USER_HELPER so check for that | 28 | # These days most distros enable CONFIG_FW_LOADER_USER_HELPER but disable |
29 | # as an indicator for CONFIG_FW_LOADER_USER_HELPER. | 29 | # CONFIG_FW_LOADER_USER_HELPER_FALLBACK. We use /sys/class/firmware/ as an |
30 | # indicator for CONFIG_FW_LOADER_USER_HELPER. | ||
30 | HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi) | 31 | HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi) |
31 | 32 | ||
32 | if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then | 33 | if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then |
@@ -116,4 +117,240 @@ else | |||
116 | echo "$0: async filesystem loading works" | 117 | echo "$0: async filesystem loading works" |
117 | fi | 118 | fi |
118 | 119 | ||
120 | ### Batched requests tests | ||
121 | test_config_present() | ||
122 | { | ||
123 | if [ ! -f $DIR/reset ]; then | ||
124 | echo "Configuration triggers not present, ignoring test" | ||
125 | exit 0 | ||
126 | fi | ||
127 | } | ||
128 | |||
129 | # Defaults : | ||
130 | # | ||
131 | # send_uevent: 1 | ||
132 | # sync_direct: 0 | ||
133 | # name: test-firmware.bin | ||
134 | # num_requests: 4 | ||
135 | config_reset() | ||
136 | { | ||
137 | echo 1 > $DIR/reset | ||
138 | } | ||
139 | |||
140 | release_all_firmware() | ||
141 | { | ||
142 | echo 1 > $DIR/release_all_firmware | ||
143 | } | ||
144 | |||
145 | config_set_name() | ||
146 | { | ||
147 | echo -n $1 > $DIR/config_name | ||
148 | } | ||
149 | |||
150 | config_set_sync_direct() | ||
151 | { | ||
152 | echo 1 > $DIR/config_sync_direct | ||
153 | } | ||
154 | |||
155 | config_unset_sync_direct() | ||
156 | { | ||
157 | echo 0 > $DIR/config_sync_direct | ||
158 | } | ||
159 | |||
160 | config_set_uevent() | ||
161 | { | ||
162 | echo 1 > $DIR/config_send_uevent | ||
163 | } | ||
164 | |||
165 | config_unset_uevent() | ||
166 | { | ||
167 | echo 0 > $DIR/config_send_uevent | ||
168 | } | ||
169 | |||
170 | config_trigger_sync() | ||
171 | { | ||
172 | echo -n 1 > $DIR/trigger_batched_requests 2>/dev/null | ||
173 | } | ||
174 | |||
175 | config_trigger_async() | ||
176 | { | ||
177 | echo -n 1 > $DIR/trigger_batched_requests_async 2> /dev/null | ||
178 | } | ||
179 | |||
180 | config_set_read_fw_idx() | ||
181 | { | ||
182 | echo -n $1 > $DIR/config_read_fw_idx 2> /dev/null | ||
183 | } | ||
184 | |||
185 | read_firmwares() | ||
186 | { | ||
187 | for i in $(seq 0 3); do | ||
188 | config_set_read_fw_idx $i | ||
189 | # Verify the contents are what we expect. | ||
190 | # -Z required for now -- check for yourself, md5sum | ||
191 | # on $FW and DIR/read_firmware will yield the same. Even | ||
192 | # cmp agrees, so something is off. | ||
193 | if ! diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then | ||
194 | echo "request #$i: firmware was not loaded" >&2 | ||
195 | exit 1 | ||
196 | fi | ||
197 | done | ||
198 | } | ||
199 | |||
200 | read_firmwares_expect_nofile() | ||
201 | { | ||
202 | for i in $(seq 0 3); do | ||
203 | config_set_read_fw_idx $i | ||
204 | # Ensures contents differ | ||
205 | if diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then | ||
206 | echo "request $i: file was not expected to match" >&2 | ||
207 | exit 1 | ||
208 | fi | ||
209 | done | ||
210 | } | ||
211 | |||
212 | test_batched_request_firmware_nofile() | ||
213 | { | ||
214 | echo -n "Batched request_firmware() nofile try #$1: " | ||
215 | config_reset | ||
216 | config_set_name nope-test-firmware.bin | ||
217 | config_trigger_sync | ||
218 | read_firmwares_expect_nofile | ||
219 | release_all_firmware | ||
220 | echo "OK" | ||
221 | } | ||
222 | |||
223 | test_batched_request_firmware_direct_nofile() | ||
224 | { | ||
225 | echo -n "Batched request_firmware_direct() nofile try #$1: " | ||
226 | config_reset | ||
227 | config_set_name nope-test-firmware.bin | ||
228 | config_set_sync_direct | ||
229 | config_trigger_sync | ||
230 | release_all_firmware | ||
231 | echo "OK" | ||
232 | } | ||
233 | |||
234 | test_request_firmware_nowait_uevent_nofile() | ||
235 | { | ||
236 | echo -n "Batched request_firmware_nowait(uevent=true) nofile try #$1: " | ||
237 | config_reset | ||
238 | config_set_name nope-test-firmware.bin | ||
239 | config_trigger_async | ||
240 | release_all_firmware | ||
241 | echo "OK" | ||
242 | } | ||
243 | |||
244 | test_wait_and_cancel_custom_load() | ||
245 | { | ||
246 | if [ "$HAS_FW_LOADER_USER_HELPER" != "yes" ]; then | ||
247 | return | ||
248 | fi | ||
249 | local timeout=10 | ||
250 | name=$1 | ||
251 | while [ ! -e "$DIR"/"$name"/loading ]; do | ||
252 | sleep 0.1 | ||
253 | timeout=$(( $timeout - 1 )) | ||
254 | if [ "$timeout" -eq 0 ]; then | ||
255 | echo "firmware interface never appeared:" >&2 | ||
256 | echo "$DIR/$name/loading" >&2 | ||
257 | exit 1 | ||
258 | fi | ||
259 | done | ||
260 | echo -1 >"$DIR"/"$name"/loading | ||
261 | } | ||
262 | |||
263 | test_request_firmware_nowait_custom_nofile() | ||
264 | { | ||
265 | echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: " | ||
266 | config_unset_uevent | ||
267 | config_set_name nope-test-firmware.bin | ||
268 | config_trigger_async & | ||
269 | test_wait_and_cancel_custom_load nope-test-firmware.bin | ||
270 | wait | ||
271 | release_all_firmware | ||
272 | echo "OK" | ||
273 | } | ||
274 | |||
275 | test_batched_request_firmware() | ||
276 | { | ||
277 | echo -n "Batched request_firmware() try #$1: " | ||
278 | config_reset | ||
279 | config_trigger_sync | ||
280 | read_firmwares | ||
281 | release_all_firmware | ||
282 | echo "OK" | ||
283 | } | ||
284 | |||
285 | test_batched_request_firmware_direct() | ||
286 | { | ||
287 | echo -n "Batched request_firmware_direct() try #$1: " | ||
288 | config_reset | ||
289 | config_set_sync_direct | ||
290 | config_trigger_sync | ||
291 | release_all_firmware | ||
292 | echo "OK" | ||
293 | } | ||
294 | |||
295 | test_request_firmware_nowait_uevent() | ||
296 | { | ||
297 | echo -n "Batched request_firmware_nowait(uevent=true) try #$1: " | ||
298 | config_reset | ||
299 | config_trigger_async | ||
300 | release_all_firmware | ||
301 | echo "OK" | ||
302 | } | ||
303 | |||
304 | test_request_firmware_nowait_custom() | ||
305 | { | ||
306 | echo -n "Batched request_firmware_nowait(uevent=false) try #$1: " | ||
307 | config_unset_uevent | ||
308 | config_trigger_async | ||
309 | release_all_firmware | ||
310 | echo "OK" | ||
311 | } | ||
312 | |||
313 | # Only continue if batched request triggers are present on the | ||
314 | # test-firmware driver | ||
315 | test_config_present | ||
316 | |||
317 | # test with the file present | ||
318 | echo | ||
319 | echo "Testing with the file present..." | ||
320 | for i in $(seq 1 5); do | ||
321 | test_batched_request_firmware $i | ||
322 | done | ||
323 | |||
324 | for i in $(seq 1 5); do | ||
325 | test_batched_request_firmware_direct $i | ||
326 | done | ||
327 | |||
328 | for i in $(seq 1 5); do | ||
329 | test_request_firmware_nowait_uevent $i | ||
330 | done | ||
331 | |||
332 | for i in $(seq 1 5); do | ||
333 | test_request_firmware_nowait_custom $i | ||
334 | done | ||
335 | |||
336 | # Test for file not found, errors are expected, the failure would be | ||
337 | # a hung task, which would require a hard reset. | ||
338 | echo | ||
339 | echo "Testing with the file missing..." | ||
340 | for i in $(seq 1 5); do | ||
341 | test_batched_request_firmware_nofile $i | ||
342 | done | ||
343 | |||
344 | for i in $(seq 1 5); do | ||
345 | test_batched_request_firmware_direct_nofile $i | ||
346 | done | ||
347 | |||
348 | for i in $(seq 1 5); do | ||
349 | test_request_firmware_nowait_uevent_nofile $i | ||
350 | done | ||
351 | |||
352 | for i in $(seq 1 5); do | ||
353 | test_request_firmware_nowait_custom_nofile $i | ||
354 | done | ||
355 | |||
119 | exit 0 | 356 | exit 0 |