xref: /linux-6.15/lib/test_firmware.c (revision baaabecf)
109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20a8adf58SKees Cook /*
30a8adf58SKees Cook  * This module provides an interface to trigger and test firmware loading.
40a8adf58SKees Cook  *
50a8adf58SKees Cook  * It is designed to be used for basic evaluation of the firmware loading
60a8adf58SKees Cook  * subsystem (for example when validating firmware verification). It lacks
70a8adf58SKees Cook  * any extra dependencies, and will not normally be loaded by the system
80a8adf58SKees Cook  * unless explicitly requested by name.
90a8adf58SKees Cook  */
100a8adf58SKees Cook 
110a8adf58SKees Cook #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
120a8adf58SKees Cook 
130a8adf58SKees Cook #include <linux/init.h>
140a8adf58SKees Cook #include <linux/module.h>
150a8adf58SKees Cook #include <linux/printk.h>
16eb910947SBrian Norris #include <linux/completion.h>
170a8adf58SKees Cook #include <linux/firmware.h>
180a8adf58SKees Cook #include <linux/device.h>
190a8adf58SKees Cook #include <linux/fs.h>
200a8adf58SKees Cook #include <linux/miscdevice.h>
217feebfa4SScott Branden #include <linux/sizes.h>
220a8adf58SKees Cook #include <linux/slab.h>
230a8adf58SKees Cook #include <linux/uaccess.h>
24c92316bfSLuis R. Rodriguez #include <linux/delay.h>
25c92316bfSLuis R. Rodriguez #include <linux/kthread.h>
26514c6032SRandy Dunlap #include <linux/vmalloc.h>
27548193cbSHans de Goede #include <linux/efi_embedded_fw.h>
28c92316bfSLuis R. Rodriguez 
29*baaabecfSKees Cook MODULE_IMPORT_NS(TEST_FIRMWARE);
30*baaabecfSKees Cook 
31c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NAME	"test-firmware.bin"
32c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NUM_REQS	4
337feebfa4SScott Branden #define TEST_FIRMWARE_BUF_SIZE	SZ_1K
340a8adf58SKees Cook 
350a8adf58SKees Cook static DEFINE_MUTEX(test_fw_mutex);
360a8adf58SKees Cook static const struct firmware *test_firmware;
370a8adf58SKees Cook 
38c92316bfSLuis R. Rodriguez struct test_batched_req {
39c92316bfSLuis R. Rodriguez 	u8 idx;
40c92316bfSLuis R. Rodriguez 	int rc;
41c92316bfSLuis R. Rodriguez 	bool sent;
42c92316bfSLuis R. Rodriguez 	const struct firmware *fw;
43c92316bfSLuis R. Rodriguez 	const char *name;
44c92316bfSLuis R. Rodriguez 	struct completion completion;
45c92316bfSLuis R. Rodriguez 	struct task_struct *task;
46c92316bfSLuis R. Rodriguez 	struct device *dev;
47c92316bfSLuis R. Rodriguez };
48c92316bfSLuis R. Rodriguez 
49c92316bfSLuis R. Rodriguez /**
50c92316bfSLuis R. Rodriguez  * test_config - represents configuration for the test for different triggers
51c92316bfSLuis R. Rodriguez  *
52c92316bfSLuis R. Rodriguez  * @name: the name of the firmware file to look for
537feebfa4SScott Branden  * @into_buf: when the into_buf is used if this is true
547feebfa4SScott Branden  *	request_firmware_into_buf() will be used instead.
55c92316bfSLuis R. Rodriguez  * @sync_direct: when the sync trigger is used if this is true
56c92316bfSLuis R. Rodriguez  *	request_firmware_direct() will be used instead.
57c92316bfSLuis R. Rodriguez  * @send_uevent: whether or not to send a uevent for async requests
58c92316bfSLuis R. Rodriguez  * @num_requests: number of requests to try per test case. This is trigger
59c92316bfSLuis R. Rodriguez  *	specific.
60c92316bfSLuis R. Rodriguez  * @reqs: stores all requests information
61c92316bfSLuis R. Rodriguez  * @read_fw_idx: index of thread from which we want to read firmware results
62c92316bfSLuis R. Rodriguez  *	from through the read_fw trigger.
63c92316bfSLuis R. Rodriguez  * @test_result: a test may use this to collect the result from the call
64c92316bfSLuis R. Rodriguez  *	of the request_firmware*() calls used in their tests. In order of
65c92316bfSLuis R. Rodriguez  *	priority we always keep first any setup error. If no setup errors were
66c92316bfSLuis R. Rodriguez  *	found then we move on to the first error encountered while running the
67c92316bfSLuis R. Rodriguez  *	API. Note that for async calls this typically will be a successful
68c92316bfSLuis R. Rodriguez  *	result (0) unless of course you've used bogus parameters, or the system
69c92316bfSLuis R. Rodriguez  *	is out of memory.  In the async case the callback is expected to do a
70c92316bfSLuis R. Rodriguez  *	bit more homework to figure out what happened, unfortunately the only
71c92316bfSLuis R. Rodriguez  *	information passed today on error is the fact that no firmware was
72c92316bfSLuis R. Rodriguez  *	found so we can only assume -ENOENT on async calls if the firmware is
73c92316bfSLuis R. Rodriguez  *	NULL.
74c92316bfSLuis R. Rodriguez  *
75c92316bfSLuis R. Rodriguez  *	Errors you can expect:
76c92316bfSLuis R. Rodriguez  *
77c92316bfSLuis R. Rodriguez  *	API specific:
78c92316bfSLuis R. Rodriguez  *
79c92316bfSLuis R. Rodriguez  *	0:		success for sync, for async it means request was sent
80c92316bfSLuis R. Rodriguez  *	-EINVAL:	invalid parameters or request
81c92316bfSLuis R. Rodriguez  *	-ENOENT:	files not found
82c92316bfSLuis R. Rodriguez  *
83c92316bfSLuis R. Rodriguez  *	System environment:
84c92316bfSLuis R. Rodriguez  *
85c92316bfSLuis R. Rodriguez  *	-ENOMEM:	memory pressure on system
86c92316bfSLuis R. Rodriguez  *	-ENODEV:	out of number of devices to test
87c92316bfSLuis R. Rodriguez  *	-EINVAL:	an unexpected error has occurred
88c92316bfSLuis R. Rodriguez  * @req_firmware: if @sync_direct is true this is set to
89c92316bfSLuis R. Rodriguez  *	request_firmware_direct(), otherwise request_firmware()
90c92316bfSLuis R. Rodriguez  */
91c92316bfSLuis R. Rodriguez struct test_config {
92c92316bfSLuis R. Rodriguez 	char *name;
937feebfa4SScott Branden 	bool into_buf;
94c92316bfSLuis R. Rodriguez 	bool sync_direct;
95c92316bfSLuis R. Rodriguez 	bool send_uevent;
96c92316bfSLuis R. Rodriguez 	u8 num_requests;
97c92316bfSLuis R. Rodriguez 	u8 read_fw_idx;
98c92316bfSLuis R. Rodriguez 
99c92316bfSLuis R. Rodriguez 	/*
100c92316bfSLuis R. Rodriguez 	 * These below don't belong her but we'll move them once we create
101c92316bfSLuis R. Rodriguez 	 * a struct fw_test_device and stuff the misc_dev under there later.
102c92316bfSLuis R. Rodriguez 	 */
103c92316bfSLuis R. Rodriguez 	struct test_batched_req *reqs;
104c92316bfSLuis R. Rodriguez 	int test_result;
105c92316bfSLuis R. Rodriguez 	int (*req_firmware)(const struct firmware **fw, const char *name,
106c92316bfSLuis R. Rodriguez 			    struct device *device);
107c92316bfSLuis R. Rodriguez };
108c92316bfSLuis R. Rodriguez 
10976f8ab1bSWei Yongjun static struct test_config *test_fw_config;
110c92316bfSLuis R. Rodriguez 
1110a8adf58SKees Cook static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
1120a8adf58SKees Cook 				 size_t size, loff_t *offset)
1130a8adf58SKees Cook {
1140a8adf58SKees Cook 	ssize_t rc = 0;
1150a8adf58SKees Cook 
1160a8adf58SKees Cook 	mutex_lock(&test_fw_mutex);
1170a8adf58SKees Cook 	if (test_firmware)
1180a8adf58SKees Cook 		rc = simple_read_from_buffer(buf, size, offset,
1190a8adf58SKees Cook 					     test_firmware->data,
1200a8adf58SKees Cook 					     test_firmware->size);
1210a8adf58SKees Cook 	mutex_unlock(&test_fw_mutex);
1220a8adf58SKees Cook 	return rc;
1230a8adf58SKees Cook }
1240a8adf58SKees Cook 
1250a8adf58SKees Cook static const struct file_operations test_fw_fops = {
1260a8adf58SKees Cook 	.owner          = THIS_MODULE,
1270a8adf58SKees Cook 	.read           = test_fw_misc_read,
1280a8adf58SKees Cook };
1290a8adf58SKees Cook 
130c92316bfSLuis R. Rodriguez static void __test_release_all_firmware(void)
131c92316bfSLuis R. Rodriguez {
132c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
133c92316bfSLuis R. Rodriguez 	u8 i;
134c92316bfSLuis R. Rodriguez 
135c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs)
136c92316bfSLuis R. Rodriguez 		return;
137c92316bfSLuis R. Rodriguez 
138c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
139c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
140c92316bfSLuis R. Rodriguez 		if (req->fw)
141c92316bfSLuis R. Rodriguez 			release_firmware(req->fw);
142c92316bfSLuis R. Rodriguez 	}
143c92316bfSLuis R. Rodriguez 
144c92316bfSLuis R. Rodriguez 	vfree(test_fw_config->reqs);
145c92316bfSLuis R. Rodriguez 	test_fw_config->reqs = NULL;
146c92316bfSLuis R. Rodriguez }
147c92316bfSLuis R. Rodriguez 
148c92316bfSLuis R. Rodriguez static void test_release_all_firmware(void)
149c92316bfSLuis R. Rodriguez {
150c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
151c92316bfSLuis R. Rodriguez 	__test_release_all_firmware();
152c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
153c92316bfSLuis R. Rodriguez }
154c92316bfSLuis R. Rodriguez 
155c92316bfSLuis R. Rodriguez 
156c92316bfSLuis R. Rodriguez static void __test_firmware_config_free(void)
157c92316bfSLuis R. Rodriguez {
158c92316bfSLuis R. Rodriguez 	__test_release_all_firmware();
159c92316bfSLuis R. Rodriguez 	kfree_const(test_fw_config->name);
160c92316bfSLuis R. Rodriguez 	test_fw_config->name = NULL;
161c92316bfSLuis R. Rodriguez }
162c92316bfSLuis R. Rodriguez 
163c92316bfSLuis R. Rodriguez /*
164c92316bfSLuis R. Rodriguez  * XXX: move to kstrncpy() once merged.
165c92316bfSLuis R. Rodriguez  *
166c92316bfSLuis R. Rodriguez  * Users should use kfree_const() when freeing these.
167c92316bfSLuis R. Rodriguez  */
168c92316bfSLuis R. Rodriguez static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
169c92316bfSLuis R. Rodriguez {
170c92316bfSLuis R. Rodriguez 	*dst = kstrndup(name, count, gfp);
171c92316bfSLuis R. Rodriguez 	if (!*dst)
172c92316bfSLuis R. Rodriguez 		return -ENOSPC;
173c92316bfSLuis R. Rodriguez 	return count;
174c92316bfSLuis R. Rodriguez }
175c92316bfSLuis R. Rodriguez 
176c92316bfSLuis R. Rodriguez static int __test_firmware_config_init(void)
177c92316bfSLuis R. Rodriguez {
178c92316bfSLuis R. Rodriguez 	int ret;
179c92316bfSLuis R. Rodriguez 
180c92316bfSLuis R. Rodriguez 	ret = __kstrncpy(&test_fw_config->name, TEST_FIRMWARE_NAME,
181c92316bfSLuis R. Rodriguez 			 strlen(TEST_FIRMWARE_NAME), GFP_KERNEL);
182c92316bfSLuis R. Rodriguez 	if (ret < 0)
183c92316bfSLuis R. Rodriguez 		goto out;
184c92316bfSLuis R. Rodriguez 
185c92316bfSLuis R. Rodriguez 	test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS;
186c92316bfSLuis R. Rodriguez 	test_fw_config->send_uevent = true;
1877feebfa4SScott Branden 	test_fw_config->into_buf = false;
188c92316bfSLuis R. Rodriguez 	test_fw_config->sync_direct = false;
189c92316bfSLuis R. Rodriguez 	test_fw_config->req_firmware = request_firmware;
190c92316bfSLuis R. Rodriguez 	test_fw_config->test_result = 0;
191c92316bfSLuis R. Rodriguez 	test_fw_config->reqs = NULL;
192c92316bfSLuis R. Rodriguez 
193c92316bfSLuis R. Rodriguez 	return 0;
194c92316bfSLuis R. Rodriguez 
195c92316bfSLuis R. Rodriguez out:
196c92316bfSLuis R. Rodriguez 	__test_firmware_config_free();
197c92316bfSLuis R. Rodriguez 	return ret;
198c92316bfSLuis R. Rodriguez }
199c92316bfSLuis R. Rodriguez 
200c92316bfSLuis R. Rodriguez static ssize_t reset_store(struct device *dev,
201c92316bfSLuis R. Rodriguez 			   struct device_attribute *attr,
202c92316bfSLuis R. Rodriguez 			   const char *buf, size_t count)
203c92316bfSLuis R. Rodriguez {
204c92316bfSLuis R. Rodriguez 	int ret;
205c92316bfSLuis R. Rodriguez 
206c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
207c92316bfSLuis R. Rodriguez 
208c92316bfSLuis R. Rodriguez 	__test_firmware_config_free();
209c92316bfSLuis R. Rodriguez 
210c92316bfSLuis R. Rodriguez 	ret = __test_firmware_config_init();
211c92316bfSLuis R. Rodriguez 	if (ret < 0) {
212c92316bfSLuis R. Rodriguez 		ret = -ENOMEM;
213c92316bfSLuis R. Rodriguez 		pr_err("could not alloc settings for config trigger: %d\n",
214c92316bfSLuis R. Rodriguez 		       ret);
215c92316bfSLuis R. Rodriguez 		goto out;
216c92316bfSLuis R. Rodriguez 	}
217c92316bfSLuis R. Rodriguez 
218c92316bfSLuis R. Rodriguez 	pr_info("reset\n");
219c92316bfSLuis R. Rodriguez 	ret = count;
220c92316bfSLuis R. Rodriguez 
221c92316bfSLuis R. Rodriguez out:
222c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
223c92316bfSLuis R. Rodriguez 
224c92316bfSLuis R. Rodriguez 	return ret;
225c92316bfSLuis R. Rodriguez }
226c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(reset);
227c92316bfSLuis R. Rodriguez 
228c92316bfSLuis R. Rodriguez static ssize_t config_show(struct device *dev,
229c92316bfSLuis R. Rodriguez 			   struct device_attribute *attr,
230c92316bfSLuis R. Rodriguez 			   char *buf)
231c92316bfSLuis R. Rodriguez {
232c92316bfSLuis R. Rodriguez 	int len = 0;
233c92316bfSLuis R. Rodriguez 
234c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
235c92316bfSLuis R. Rodriguez 
236bd17cc5aSDan Carpenter 	len += scnprintf(buf, PAGE_SIZE - len,
237c92316bfSLuis R. Rodriguez 			"Custom trigger configuration for: %s\n",
238c92316bfSLuis R. Rodriguez 			dev_name(dev));
239c92316bfSLuis R. Rodriguez 
240c92316bfSLuis R. Rodriguez 	if (test_fw_config->name)
241bd17cc5aSDan Carpenter 		len += scnprintf(buf+len, PAGE_SIZE - len,
242c92316bfSLuis R. Rodriguez 				"name:\t%s\n",
243c92316bfSLuis R. Rodriguez 				test_fw_config->name);
244c92316bfSLuis R. Rodriguez 	else
245bd17cc5aSDan Carpenter 		len += scnprintf(buf+len, PAGE_SIZE - len,
246c92316bfSLuis R. Rodriguez 				"name:\tEMTPY\n");
247c92316bfSLuis R. Rodriguez 
248bd17cc5aSDan Carpenter 	len += scnprintf(buf+len, PAGE_SIZE - len,
249c92316bfSLuis R. Rodriguez 			"num_requests:\t%u\n", test_fw_config->num_requests);
250c92316bfSLuis R. Rodriguez 
251bd17cc5aSDan Carpenter 	len += scnprintf(buf+len, PAGE_SIZE - len,
252c92316bfSLuis R. Rodriguez 			"send_uevent:\t\t%s\n",
253c92316bfSLuis R. Rodriguez 			test_fw_config->send_uevent ?
254c92316bfSLuis R. Rodriguez 			"FW_ACTION_HOTPLUG" :
255c92316bfSLuis R. Rodriguez 			"FW_ACTION_NOHOTPLUG");
256bd17cc5aSDan Carpenter 	len += scnprintf(buf+len, PAGE_SIZE - len,
2577feebfa4SScott Branden 			"into_buf:\t\t%s\n",
2587feebfa4SScott Branden 			test_fw_config->into_buf ? "true" : "false");
2597feebfa4SScott Branden 	len += scnprintf(buf+len, PAGE_SIZE - len,
260c92316bfSLuis R. Rodriguez 			"sync_direct:\t\t%s\n",
261c92316bfSLuis R. Rodriguez 			test_fw_config->sync_direct ? "true" : "false");
262bd17cc5aSDan Carpenter 	len += scnprintf(buf+len, PAGE_SIZE - len,
263c92316bfSLuis R. Rodriguez 			"read_fw_idx:\t%u\n", test_fw_config->read_fw_idx);
264c92316bfSLuis R. Rodriguez 
265c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
266c92316bfSLuis R. Rodriguez 
267c92316bfSLuis R. Rodriguez 	return len;
268c92316bfSLuis R. Rodriguez }
269c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(config);
270c92316bfSLuis R. Rodriguez 
271c92316bfSLuis R. Rodriguez static ssize_t config_name_store(struct device *dev,
272c92316bfSLuis R. Rodriguez 				 struct device_attribute *attr,
273c92316bfSLuis R. Rodriguez 				 const char *buf, size_t count)
274c92316bfSLuis R. Rodriguez {
275c92316bfSLuis R. Rodriguez 	int ret;
276c92316bfSLuis R. Rodriguez 
277c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
278c92316bfSLuis R. Rodriguez 	kfree_const(test_fw_config->name);
279c92316bfSLuis R. Rodriguez 	ret = __kstrncpy(&test_fw_config->name, buf, count, GFP_KERNEL);
280c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
281c92316bfSLuis R. Rodriguez 
282c92316bfSLuis R. Rodriguez 	return ret;
283c92316bfSLuis R. Rodriguez }
284c92316bfSLuis R. Rodriguez 
285c92316bfSLuis R. Rodriguez /*
286c92316bfSLuis R. Rodriguez  * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
287c92316bfSLuis R. Rodriguez  */
288c92316bfSLuis R. Rodriguez static ssize_t config_test_show_str(char *dst,
289c92316bfSLuis R. Rodriguez 				    char *src)
290c92316bfSLuis R. Rodriguez {
291c92316bfSLuis R. Rodriguez 	int len;
292c92316bfSLuis R. Rodriguez 
293c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
294c92316bfSLuis R. Rodriguez 	len = snprintf(dst, PAGE_SIZE, "%s\n", src);
295c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
296c92316bfSLuis R. Rodriguez 
297c92316bfSLuis R. Rodriguez 	return len;
298c92316bfSLuis R. Rodriguez }
299c92316bfSLuis R. Rodriguez 
300c92316bfSLuis R. Rodriguez static int test_dev_config_update_bool(const char *buf, size_t size,
301c92316bfSLuis R. Rodriguez 				       bool *cfg)
302c92316bfSLuis R. Rodriguez {
303c92316bfSLuis R. Rodriguez 	int ret;
304c92316bfSLuis R. Rodriguez 
305c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
306c92316bfSLuis R. Rodriguez 	if (strtobool(buf, cfg) < 0)
307c92316bfSLuis R. Rodriguez 		ret = -EINVAL;
308c92316bfSLuis R. Rodriguez 	else
309c92316bfSLuis R. Rodriguez 		ret = size;
310c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
311c92316bfSLuis R. Rodriguez 
312c92316bfSLuis R. Rodriguez 	return ret;
313c92316bfSLuis R. Rodriguez }
314c92316bfSLuis R. Rodriguez 
31555623260SScott Branden static ssize_t test_dev_config_show_bool(char *buf, bool val)
316c92316bfSLuis R. Rodriguez {
317c92316bfSLuis R. Rodriguez 	return snprintf(buf, PAGE_SIZE, "%d\n", val);
318c92316bfSLuis R. Rodriguez }
319c92316bfSLuis R. Rodriguez 
32055623260SScott Branden static ssize_t test_dev_config_show_int(char *buf, int val)
321c92316bfSLuis R. Rodriguez {
322c92316bfSLuis R. Rodriguez 	return snprintf(buf, PAGE_SIZE, "%d\n", val);
323c92316bfSLuis R. Rodriguez }
324c92316bfSLuis R. Rodriguez 
325c92316bfSLuis R. Rodriguez static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg)
326c92316bfSLuis R. Rodriguez {
327c92316bfSLuis R. Rodriguez 	int ret;
328c92316bfSLuis R. Rodriguez 	long new;
329c92316bfSLuis R. Rodriguez 
330c92316bfSLuis R. Rodriguez 	ret = kstrtol(buf, 10, &new);
331c92316bfSLuis R. Rodriguez 	if (ret)
332c92316bfSLuis R. Rodriguez 		return ret;
333c92316bfSLuis R. Rodriguez 
334c92316bfSLuis R. Rodriguez 	if (new > U8_MAX)
335c92316bfSLuis R. Rodriguez 		return -EINVAL;
336c92316bfSLuis R. Rodriguez 
337c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
338c92316bfSLuis R. Rodriguez 	*(u8 *)cfg = new;
339c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
340c92316bfSLuis R. Rodriguez 
341c92316bfSLuis R. Rodriguez 	/* Always return full write size even if we didn't consume all */
342c92316bfSLuis R. Rodriguez 	return size;
343c92316bfSLuis R. Rodriguez }
344c92316bfSLuis R. Rodriguez 
34555623260SScott Branden static ssize_t test_dev_config_show_u8(char *buf, u8 val)
346c92316bfSLuis R. Rodriguez {
347c92316bfSLuis R. Rodriguez 	return snprintf(buf, PAGE_SIZE, "%u\n", val);
348c92316bfSLuis R. Rodriguez }
349c92316bfSLuis R. Rodriguez 
350c92316bfSLuis R. Rodriguez static ssize_t config_name_show(struct device *dev,
351c92316bfSLuis R. Rodriguez 				struct device_attribute *attr,
352c92316bfSLuis R. Rodriguez 				char *buf)
353c92316bfSLuis R. Rodriguez {
354c92316bfSLuis R. Rodriguez 	return config_test_show_str(buf, test_fw_config->name);
355c92316bfSLuis R. Rodriguez }
356b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_name);
357c92316bfSLuis R. Rodriguez 
358c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_store(struct device *dev,
359c92316bfSLuis R. Rodriguez 					 struct device_attribute *attr,
360c92316bfSLuis R. Rodriguez 					 const char *buf, size_t count)
361c92316bfSLuis R. Rodriguez {
362c92316bfSLuis R. Rodriguez 	int rc;
363c92316bfSLuis R. Rodriguez 
364c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
365c92316bfSLuis R. Rodriguez 	if (test_fw_config->reqs) {
366c92316bfSLuis R. Rodriguez 		pr_err("Must call release_all_firmware prior to changing config\n");
367c92316bfSLuis R. Rodriguez 		rc = -EINVAL;
368a5e19233SWei Yongjun 		mutex_unlock(&test_fw_mutex);
369c92316bfSLuis R. Rodriguez 		goto out;
370c92316bfSLuis R. Rodriguez 	}
371c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
372c92316bfSLuis R. Rodriguez 
373c92316bfSLuis R. Rodriguez 	rc = test_dev_config_update_u8(buf, count,
374c92316bfSLuis R. Rodriguez 				       &test_fw_config->num_requests);
375c92316bfSLuis R. Rodriguez 
376c92316bfSLuis R. Rodriguez out:
377c92316bfSLuis R. Rodriguez 	return rc;
378c92316bfSLuis R. Rodriguez }
379c92316bfSLuis R. Rodriguez 
380c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_show(struct device *dev,
381c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
382c92316bfSLuis R. Rodriguez 					char *buf)
383c92316bfSLuis R. Rodriguez {
384c92316bfSLuis R. Rodriguez 	return test_dev_config_show_u8(buf, test_fw_config->num_requests);
385c92316bfSLuis R. Rodriguez }
386b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_num_requests);
387c92316bfSLuis R. Rodriguez 
3887feebfa4SScott Branden static ssize_t config_into_buf_store(struct device *dev,
3897feebfa4SScott Branden 				     struct device_attribute *attr,
3907feebfa4SScott Branden 				     const char *buf, size_t count)
3917feebfa4SScott Branden {
3927feebfa4SScott Branden 	return test_dev_config_update_bool(buf,
3937feebfa4SScott Branden 					   count,
3947feebfa4SScott Branden 					   &test_fw_config->into_buf);
3957feebfa4SScott Branden }
3967feebfa4SScott Branden 
3977feebfa4SScott Branden static ssize_t config_into_buf_show(struct device *dev,
3987feebfa4SScott Branden 				    struct device_attribute *attr,
3997feebfa4SScott Branden 				    char *buf)
4007feebfa4SScott Branden {
4017feebfa4SScott Branden 	return test_dev_config_show_bool(buf, test_fw_config->into_buf);
4027feebfa4SScott Branden }
4037feebfa4SScott Branden static DEVICE_ATTR_RW(config_into_buf);
4047feebfa4SScott Branden 
405c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_store(struct device *dev,
406c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
407c92316bfSLuis R. Rodriguez 					const char *buf, size_t count)
408c92316bfSLuis R. Rodriguez {
409c92316bfSLuis R. Rodriguez 	int rc = test_dev_config_update_bool(buf, count,
410c92316bfSLuis R. Rodriguez 					     &test_fw_config->sync_direct);
411c92316bfSLuis R. Rodriguez 
412c92316bfSLuis R. Rodriguez 	if (rc == count)
413c92316bfSLuis R. Rodriguez 		test_fw_config->req_firmware = test_fw_config->sync_direct ?
414c92316bfSLuis R. Rodriguez 				       request_firmware_direct :
415c92316bfSLuis R. Rodriguez 				       request_firmware;
416c92316bfSLuis R. Rodriguez 	return rc;
417c92316bfSLuis R. Rodriguez }
418c92316bfSLuis R. Rodriguez 
419c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_show(struct device *dev,
420c92316bfSLuis R. Rodriguez 				       struct device_attribute *attr,
421c92316bfSLuis R. Rodriguez 				       char *buf)
422c92316bfSLuis R. Rodriguez {
423c92316bfSLuis R. Rodriguez 	return test_dev_config_show_bool(buf, test_fw_config->sync_direct);
424c92316bfSLuis R. Rodriguez }
425b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_sync_direct);
426c92316bfSLuis R. Rodriguez 
427c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_store(struct device *dev,
428c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
429c92316bfSLuis R. Rodriguez 					const char *buf, size_t count)
430c92316bfSLuis R. Rodriguez {
431c92316bfSLuis R. Rodriguez 	return test_dev_config_update_bool(buf, count,
432c92316bfSLuis R. Rodriguez 					   &test_fw_config->send_uevent);
433c92316bfSLuis R. Rodriguez }
434c92316bfSLuis R. Rodriguez 
435c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_show(struct device *dev,
436c92316bfSLuis R. Rodriguez 				       struct device_attribute *attr,
437c92316bfSLuis R. Rodriguez 				       char *buf)
438c92316bfSLuis R. Rodriguez {
439c92316bfSLuis R. Rodriguez 	return test_dev_config_show_bool(buf, test_fw_config->send_uevent);
440c92316bfSLuis R. Rodriguez }
441b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_send_uevent);
442c92316bfSLuis R. Rodriguez 
443c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_store(struct device *dev,
444c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
445c92316bfSLuis R. Rodriguez 					const char *buf, size_t count)
446c92316bfSLuis R. Rodriguez {
447c92316bfSLuis R. Rodriguez 	return test_dev_config_update_u8(buf, count,
448c92316bfSLuis R. Rodriguez 					 &test_fw_config->read_fw_idx);
449c92316bfSLuis R. Rodriguez }
450c92316bfSLuis R. Rodriguez 
451c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_show(struct device *dev,
452c92316bfSLuis R. Rodriguez 				       struct device_attribute *attr,
453c92316bfSLuis R. Rodriguez 				       char *buf)
454c92316bfSLuis R. Rodriguez {
455c92316bfSLuis R. Rodriguez 	return test_dev_config_show_u8(buf, test_fw_config->read_fw_idx);
456c92316bfSLuis R. Rodriguez }
457b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_read_fw_idx);
458c92316bfSLuis R. Rodriguez 
459c92316bfSLuis R. Rodriguez 
4600a8adf58SKees Cook static ssize_t trigger_request_store(struct device *dev,
4610a8adf58SKees Cook 				     struct device_attribute *attr,
4620a8adf58SKees Cook 				     const char *buf, size_t count)
4630a8adf58SKees Cook {
4640a8adf58SKees Cook 	int rc;
4650a8adf58SKees Cook 	char *name;
4660a8adf58SKees Cook 
467be4a1326SBrian Norris 	name = kstrndup(buf, count, GFP_KERNEL);
4680a8adf58SKees Cook 	if (!name)
4690a8adf58SKees Cook 		return -ENOSPC;
4700a8adf58SKees Cook 
4710a8adf58SKees Cook 	pr_info("loading '%s'\n", name);
4720a8adf58SKees Cook 
4730a8adf58SKees Cook 	mutex_lock(&test_fw_mutex);
4740a8adf58SKees Cook 	release_firmware(test_firmware);
4750a8adf58SKees Cook 	test_firmware = NULL;
4760a8adf58SKees Cook 	rc = request_firmware(&test_firmware, name, dev);
47747e0bbb7SBrian Norris 	if (rc) {
4780a8adf58SKees Cook 		pr_info("load of '%s' failed: %d\n", name, rc);
47947e0bbb7SBrian Norris 		goto out;
48047e0bbb7SBrian Norris 	}
48147e0bbb7SBrian Norris 	pr_info("loaded: %zu\n", test_firmware->size);
48247e0bbb7SBrian Norris 	rc = count;
48347e0bbb7SBrian Norris 
48447e0bbb7SBrian Norris out:
4850a8adf58SKees Cook 	mutex_unlock(&test_fw_mutex);
4860a8adf58SKees Cook 
4870a8adf58SKees Cook 	kfree(name);
4880a8adf58SKees Cook 
48947e0bbb7SBrian Norris 	return rc;
4900a8adf58SKees Cook }
4910a8adf58SKees Cook static DEVICE_ATTR_WO(trigger_request);
4920a8adf58SKees Cook 
493548193cbSHans de Goede #ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
494*baaabecfSKees Cook extern struct list_head efi_embedded_fw_list;
495*baaabecfSKees Cook extern bool efi_embedded_fw_checked;
496*baaabecfSKees Cook 
497548193cbSHans de Goede static ssize_t trigger_request_platform_store(struct device *dev,
498548193cbSHans de Goede 					      struct device_attribute *attr,
499548193cbSHans de Goede 					      const char *buf, size_t count)
500548193cbSHans de Goede {
501548193cbSHans de Goede 	static const u8 test_data[] = {
502548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x01, 0x02, 0x03, 0x04,
503548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x05, 0x06, 0x07, 0x08,
504548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x10, 0x20, 0x30, 0x40,
505548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x50, 0x60, 0x70, 0x80
506548193cbSHans de Goede 	};
507548193cbSHans de Goede 	struct efi_embedded_fw efi_embedded_fw;
508548193cbSHans de Goede 	const struct firmware *firmware = NULL;
509*baaabecfSKees Cook 	bool saved_efi_embedded_fw_checked;
510548193cbSHans de Goede 	char *name;
511548193cbSHans de Goede 	int rc;
512548193cbSHans de Goede 
513548193cbSHans de Goede 	name = kstrndup(buf, count, GFP_KERNEL);
514548193cbSHans de Goede 	if (!name)
515548193cbSHans de Goede 		return -ENOSPC;
516548193cbSHans de Goede 
517548193cbSHans de Goede 	pr_info("inserting test platform fw '%s'\n", name);
518548193cbSHans de Goede 	efi_embedded_fw.name = name;
519548193cbSHans de Goede 	efi_embedded_fw.data = (void *)test_data;
520548193cbSHans de Goede 	efi_embedded_fw.length = sizeof(test_data);
521548193cbSHans de Goede 	list_add(&efi_embedded_fw.list, &efi_embedded_fw_list);
522*baaabecfSKees Cook 	saved_efi_embedded_fw_checked = efi_embedded_fw_checked;
523*baaabecfSKees Cook 	efi_embedded_fw_checked = true;
524548193cbSHans de Goede 
525548193cbSHans de Goede 	pr_info("loading '%s'\n", name);
526548193cbSHans de Goede 	rc = firmware_request_platform(&firmware, name, dev);
527548193cbSHans de Goede 	if (rc) {
528548193cbSHans de Goede 		pr_info("load of '%s' failed: %d\n", name, rc);
529548193cbSHans de Goede 		goto out;
530548193cbSHans de Goede 	}
531548193cbSHans de Goede 	if (firmware->size != sizeof(test_data) ||
532548193cbSHans de Goede 	    memcmp(firmware->data, test_data, sizeof(test_data)) != 0) {
533548193cbSHans de Goede 		pr_info("firmware contents mismatch for '%s'\n", name);
534548193cbSHans de Goede 		rc = -EINVAL;
535548193cbSHans de Goede 		goto out;
536548193cbSHans de Goede 	}
537548193cbSHans de Goede 	pr_info("loaded: %zu\n", firmware->size);
538548193cbSHans de Goede 	rc = count;
539548193cbSHans de Goede 
540548193cbSHans de Goede out:
541*baaabecfSKees Cook 	efi_embedded_fw_checked = saved_efi_embedded_fw_checked;
542548193cbSHans de Goede 	release_firmware(firmware);
543548193cbSHans de Goede 	list_del(&efi_embedded_fw.list);
544548193cbSHans de Goede 	kfree(name);
545548193cbSHans de Goede 
546548193cbSHans de Goede 	return rc;
547548193cbSHans de Goede }
548548193cbSHans de Goede static DEVICE_ATTR_WO(trigger_request_platform);
549548193cbSHans de Goede #endif
550548193cbSHans de Goede 
551eb910947SBrian Norris static DECLARE_COMPLETION(async_fw_done);
552eb910947SBrian Norris 
553eb910947SBrian Norris static void trigger_async_request_cb(const struct firmware *fw, void *context)
554eb910947SBrian Norris {
555eb910947SBrian Norris 	test_firmware = fw;
556eb910947SBrian Norris 	complete(&async_fw_done);
557eb910947SBrian Norris }
558eb910947SBrian Norris 
559eb910947SBrian Norris static ssize_t trigger_async_request_store(struct device *dev,
560eb910947SBrian Norris 					   struct device_attribute *attr,
561eb910947SBrian Norris 					   const char *buf, size_t count)
562eb910947SBrian Norris {
563eb910947SBrian Norris 	int rc;
564eb910947SBrian Norris 	char *name;
565eb910947SBrian Norris 
566eb910947SBrian Norris 	name = kstrndup(buf, count, GFP_KERNEL);
567eb910947SBrian Norris 	if (!name)
568eb910947SBrian Norris 		return -ENOSPC;
569eb910947SBrian Norris 
570eb910947SBrian Norris 	pr_info("loading '%s'\n", name);
571eb910947SBrian Norris 
572eb910947SBrian Norris 	mutex_lock(&test_fw_mutex);
573eb910947SBrian Norris 	release_firmware(test_firmware);
574eb910947SBrian Norris 	test_firmware = NULL;
575eb910947SBrian Norris 	rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL,
576eb910947SBrian Norris 				     NULL, trigger_async_request_cb);
577eb910947SBrian Norris 	if (rc) {
578eb910947SBrian Norris 		pr_info("async load of '%s' failed: %d\n", name, rc);
579eb910947SBrian Norris 		kfree(name);
580eb910947SBrian Norris 		goto out;
581eb910947SBrian Norris 	}
582eb910947SBrian Norris 	/* Free 'name' ASAP, to test for race conditions */
583eb910947SBrian Norris 	kfree(name);
584eb910947SBrian Norris 
585eb910947SBrian Norris 	wait_for_completion(&async_fw_done);
586eb910947SBrian Norris 
587eb910947SBrian Norris 	if (test_firmware) {
588eb910947SBrian Norris 		pr_info("loaded: %zu\n", test_firmware->size);
589eb910947SBrian Norris 		rc = count;
590eb910947SBrian Norris 	} else {
591eb910947SBrian Norris 		pr_err("failed to async load firmware\n");
5927feebfa4SScott Branden 		rc = -ENOMEM;
593eb910947SBrian Norris 	}
594eb910947SBrian Norris 
595eb910947SBrian Norris out:
596eb910947SBrian Norris 	mutex_unlock(&test_fw_mutex);
597eb910947SBrian Norris 
598eb910947SBrian Norris 	return rc;
599eb910947SBrian Norris }
600eb910947SBrian Norris static DEVICE_ATTR_WO(trigger_async_request);
601eb910947SBrian Norris 
602061132d2SLuis R. Rodriguez static ssize_t trigger_custom_fallback_store(struct device *dev,
603061132d2SLuis R. Rodriguez 					     struct device_attribute *attr,
604061132d2SLuis R. Rodriguez 					     const char *buf, size_t count)
605061132d2SLuis R. Rodriguez {
606061132d2SLuis R. Rodriguez 	int rc;
607061132d2SLuis R. Rodriguez 	char *name;
608061132d2SLuis R. Rodriguez 
609061132d2SLuis R. Rodriguez 	name = kstrndup(buf, count, GFP_KERNEL);
610061132d2SLuis R. Rodriguez 	if (!name)
611061132d2SLuis R. Rodriguez 		return -ENOSPC;
612061132d2SLuis R. Rodriguez 
613061132d2SLuis R. Rodriguez 	pr_info("loading '%s' using custom fallback mechanism\n", name);
614061132d2SLuis R. Rodriguez 
615061132d2SLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
616061132d2SLuis R. Rodriguez 	release_firmware(test_firmware);
617061132d2SLuis R. Rodriguez 	test_firmware = NULL;
618061132d2SLuis R. Rodriguez 	rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, name,
619061132d2SLuis R. Rodriguez 				     dev, GFP_KERNEL, NULL,
620061132d2SLuis R. Rodriguez 				     trigger_async_request_cb);
621061132d2SLuis R. Rodriguez 	if (rc) {
622061132d2SLuis R. Rodriguez 		pr_info("async load of '%s' failed: %d\n", name, rc);
623061132d2SLuis R. Rodriguez 		kfree(name);
624061132d2SLuis R. Rodriguez 		goto out;
625061132d2SLuis R. Rodriguez 	}
626061132d2SLuis R. Rodriguez 	/* Free 'name' ASAP, to test for race conditions */
627061132d2SLuis R. Rodriguez 	kfree(name);
628061132d2SLuis R. Rodriguez 
629061132d2SLuis R. Rodriguez 	wait_for_completion(&async_fw_done);
630061132d2SLuis R. Rodriguez 
631061132d2SLuis R. Rodriguez 	if (test_firmware) {
632061132d2SLuis R. Rodriguez 		pr_info("loaded: %zu\n", test_firmware->size);
633061132d2SLuis R. Rodriguez 		rc = count;
634061132d2SLuis R. Rodriguez 	} else {
635061132d2SLuis R. Rodriguez 		pr_err("failed to async load firmware\n");
636061132d2SLuis R. Rodriguez 		rc = -ENODEV;
637061132d2SLuis R. Rodriguez 	}
638061132d2SLuis R. Rodriguez 
639061132d2SLuis R. Rodriguez out:
640061132d2SLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
641061132d2SLuis R. Rodriguez 
642061132d2SLuis R. Rodriguez 	return rc;
643061132d2SLuis R. Rodriguez }
644061132d2SLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_custom_fallback);
645061132d2SLuis R. Rodriguez 
646c92316bfSLuis R. Rodriguez static int test_fw_run_batch_request(void *data)
647c92316bfSLuis R. Rodriguez {
648c92316bfSLuis R. Rodriguez 	struct test_batched_req *req = data;
649c92316bfSLuis R. Rodriguez 
650c92316bfSLuis R. Rodriguez 	if (!req) {
651c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = -EINVAL;
652c92316bfSLuis R. Rodriguez 		return -EINVAL;
653c92316bfSLuis R. Rodriguez 	}
654c92316bfSLuis R. Rodriguez 
6557feebfa4SScott Branden 	if (test_fw_config->into_buf) {
6567feebfa4SScott Branden 		void *test_buf;
6577feebfa4SScott Branden 
6587feebfa4SScott Branden 		test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL);
6597feebfa4SScott Branden 		if (!test_buf)
6607feebfa4SScott Branden 			return -ENOSPC;
6617feebfa4SScott Branden 
6627feebfa4SScott Branden 		req->rc = request_firmware_into_buf(&req->fw,
6637feebfa4SScott Branden 						    req->name,
6647feebfa4SScott Branden 						    req->dev,
6657feebfa4SScott Branden 						    test_buf,
6667feebfa4SScott Branden 						    TEST_FIRMWARE_BUF_SIZE);
6677feebfa4SScott Branden 		if (!req->fw)
6687feebfa4SScott Branden 			kfree(test_buf);
6697feebfa4SScott Branden 	} else {
6707feebfa4SScott Branden 		req->rc = test_fw_config->req_firmware(&req->fw,
6717feebfa4SScott Branden 						       req->name,
6727feebfa4SScott Branden 						       req->dev);
6737feebfa4SScott Branden 	}
6747feebfa4SScott Branden 
675c92316bfSLuis R. Rodriguez 	if (req->rc) {
676c92316bfSLuis R. Rodriguez 		pr_info("#%u: batched sync load failed: %d\n",
677c92316bfSLuis R. Rodriguez 			req->idx, req->rc);
678c92316bfSLuis R. Rodriguez 		if (!test_fw_config->test_result)
679c92316bfSLuis R. Rodriguez 			test_fw_config->test_result = req->rc;
680c92316bfSLuis R. Rodriguez 	} else if (req->fw) {
681c92316bfSLuis R. Rodriguez 		req->sent = true;
682c92316bfSLuis R. Rodriguez 		pr_info("#%u: batched sync loaded %zu\n",
683c92316bfSLuis R. Rodriguez 			req->idx, req->fw->size);
684c92316bfSLuis R. Rodriguez 	}
685c92316bfSLuis R. Rodriguez 	complete(&req->completion);
686c92316bfSLuis R. Rodriguez 
687c92316bfSLuis R. Rodriguez 	req->task = NULL;
688c92316bfSLuis R. Rodriguez 
689c92316bfSLuis R. Rodriguez 	return 0;
690c92316bfSLuis R. Rodriguez }
691c92316bfSLuis R. Rodriguez 
692c92316bfSLuis R. Rodriguez /*
693c92316bfSLuis R. Rodriguez  * We use a kthread as otherwise the kernel serializes all our sync requests
694c92316bfSLuis R. Rodriguez  * and we would not be able to mimic batched requests on a sync call. Batched
695c92316bfSLuis R. Rodriguez  * requests on a sync call can for instance happen on a device driver when
696c92316bfSLuis R. Rodriguez  * multiple cards are used and firmware loading happens outside of probe.
697c92316bfSLuis R. Rodriguez  */
698c92316bfSLuis R. Rodriguez static ssize_t trigger_batched_requests_store(struct device *dev,
699c92316bfSLuis R. Rodriguez 					      struct device_attribute *attr,
700c92316bfSLuis R. Rodriguez 					      const char *buf, size_t count)
701c92316bfSLuis R. Rodriguez {
702c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
703c92316bfSLuis R. Rodriguez 	int rc;
704c92316bfSLuis R. Rodriguez 	u8 i;
705c92316bfSLuis R. Rodriguez 
706c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
707c92316bfSLuis R. Rodriguez 
708fad953ceSKees Cook 	test_fw_config->reqs =
709fad953ceSKees Cook 		vzalloc(array3_size(sizeof(struct test_batched_req),
710fad953ceSKees Cook 				    test_fw_config->num_requests, 2));
711c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs) {
712c92316bfSLuis R. Rodriguez 		rc = -ENOMEM;
713c92316bfSLuis R. Rodriguez 		goto out_unlock;
714c92316bfSLuis R. Rodriguez 	}
715c92316bfSLuis R. Rodriguez 
716c92316bfSLuis R. Rodriguez 	pr_info("batched sync firmware loading '%s' %u times\n",
717c92316bfSLuis R. Rodriguez 		test_fw_config->name, test_fw_config->num_requests);
718c92316bfSLuis R. Rodriguez 
719c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
720c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
721c92316bfSLuis R. Rodriguez 		req->fw = NULL;
722c92316bfSLuis R. Rodriguez 		req->idx = i;
723c92316bfSLuis R. Rodriguez 		req->name = test_fw_config->name;
724c92316bfSLuis R. Rodriguez 		req->dev = dev;
725c92316bfSLuis R. Rodriguez 		init_completion(&req->completion);
726c92316bfSLuis R. Rodriguez 		req->task = kthread_run(test_fw_run_batch_request, req,
727c92316bfSLuis R. Rodriguez 					     "%s-%u", KBUILD_MODNAME, req->idx);
728c92316bfSLuis R. Rodriguez 		if (!req->task || IS_ERR(req->task)) {
729c92316bfSLuis R. Rodriguez 			pr_err("Setting up thread %u failed\n", req->idx);
730c92316bfSLuis R. Rodriguez 			req->task = NULL;
731c92316bfSLuis R. Rodriguez 			rc = -ENOMEM;
732c92316bfSLuis R. Rodriguez 			goto out_bail;
733c92316bfSLuis R. Rodriguez 		}
734c92316bfSLuis R. Rodriguez 	}
735c92316bfSLuis R. Rodriguez 
736c92316bfSLuis R. Rodriguez 	rc = count;
737c92316bfSLuis R. Rodriguez 
738c92316bfSLuis R. Rodriguez 	/*
739c92316bfSLuis R. Rodriguez 	 * We require an explicit release to enable more time and delay of
740c92316bfSLuis R. Rodriguez 	 * calling release_firmware() to improve our chances of forcing a
741c92316bfSLuis R. Rodriguez 	 * batched request. If we instead called release_firmware() right away
742c92316bfSLuis R. Rodriguez 	 * then we might miss on an opportunity of having a successful firmware
743c92316bfSLuis R. Rodriguez 	 * request pass on the opportunity to be come a batched request.
744c92316bfSLuis R. Rodriguez 	 */
745c92316bfSLuis R. Rodriguez 
746c92316bfSLuis R. Rodriguez out_bail:
747c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
748c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
749c92316bfSLuis R. Rodriguez 		if (req->task || req->sent)
750c92316bfSLuis R. Rodriguez 			wait_for_completion(&req->completion);
751c92316bfSLuis R. Rodriguez 	}
752c92316bfSLuis R. Rodriguez 
753c92316bfSLuis R. Rodriguez 	/* Override any worker error if we had a general setup error */
754c92316bfSLuis R. Rodriguez 	if (rc < 0)
755c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = rc;
756c92316bfSLuis R. Rodriguez 
757c92316bfSLuis R. Rodriguez out_unlock:
758c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
759c92316bfSLuis R. Rodriguez 
760c92316bfSLuis R. Rodriguez 	return rc;
761c92316bfSLuis R. Rodriguez }
762c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests);
763c92316bfSLuis R. Rodriguez 
764c92316bfSLuis R. Rodriguez /*
765c92316bfSLuis R. Rodriguez  * We wait for each callback to return with the lock held, no need to lock here
766c92316bfSLuis R. Rodriguez  */
767c92316bfSLuis R. Rodriguez static void trigger_batched_cb(const struct firmware *fw, void *context)
768c92316bfSLuis R. Rodriguez {
769c92316bfSLuis R. Rodriguez 	struct test_batched_req *req = context;
770c92316bfSLuis R. Rodriguez 
771c92316bfSLuis R. Rodriguez 	if (!req) {
772c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = -EINVAL;
773c92316bfSLuis R. Rodriguez 		return;
774c92316bfSLuis R. Rodriguez 	}
775c92316bfSLuis R. Rodriguez 
776c92316bfSLuis R. Rodriguez 	/* forces *some* batched requests to queue up */
777c92316bfSLuis R. Rodriguez 	if (!req->idx)
778c92316bfSLuis R. Rodriguez 		ssleep(2);
779c92316bfSLuis R. Rodriguez 
780c92316bfSLuis R. Rodriguez 	req->fw = fw;
781c92316bfSLuis R. Rodriguez 
782c92316bfSLuis R. Rodriguez 	/*
783c92316bfSLuis R. Rodriguez 	 * Unfortunately the firmware API gives us nothing other than a null FW
784c92316bfSLuis R. Rodriguez 	 * if the firmware was not found on async requests.  Best we can do is
785c92316bfSLuis R. Rodriguez 	 * just assume -ENOENT. A better API would pass the actual return
786c92316bfSLuis R. Rodriguez 	 * value to the callback.
787c92316bfSLuis R. Rodriguez 	 */
788c92316bfSLuis R. Rodriguez 	if (!fw && !test_fw_config->test_result)
789c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = -ENOENT;
790c92316bfSLuis R. Rodriguez 
791c92316bfSLuis R. Rodriguez 	complete(&req->completion);
792c92316bfSLuis R. Rodriguez }
793c92316bfSLuis R. Rodriguez 
794c92316bfSLuis R. Rodriguez static
795c92316bfSLuis R. Rodriguez ssize_t trigger_batched_requests_async_store(struct device *dev,
796c92316bfSLuis R. Rodriguez 					     struct device_attribute *attr,
797c92316bfSLuis R. Rodriguez 					     const char *buf, size_t count)
798c92316bfSLuis R. Rodriguez {
799c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
800c92316bfSLuis R. Rodriguez 	bool send_uevent;
801c92316bfSLuis R. Rodriguez 	int rc;
802c92316bfSLuis R. Rodriguez 	u8 i;
803c92316bfSLuis R. Rodriguez 
804c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
805c92316bfSLuis R. Rodriguez 
806fad953ceSKees Cook 	test_fw_config->reqs =
807fad953ceSKees Cook 		vzalloc(array3_size(sizeof(struct test_batched_req),
808fad953ceSKees Cook 				    test_fw_config->num_requests, 2));
809c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs) {
810c92316bfSLuis R. Rodriguez 		rc = -ENOMEM;
811c92316bfSLuis R. Rodriguez 		goto out;
812c92316bfSLuis R. Rodriguez 	}
813c92316bfSLuis R. Rodriguez 
814c92316bfSLuis R. Rodriguez 	pr_info("batched loading '%s' custom fallback mechanism %u times\n",
815c92316bfSLuis R. Rodriguez 		test_fw_config->name, test_fw_config->num_requests);
816c92316bfSLuis R. Rodriguez 
817c92316bfSLuis R. Rodriguez 	send_uevent = test_fw_config->send_uevent ? FW_ACTION_HOTPLUG :
818c92316bfSLuis R. Rodriguez 		FW_ACTION_NOHOTPLUG;
819c92316bfSLuis R. Rodriguez 
820c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
821c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
822c92316bfSLuis R. Rodriguez 		req->name = test_fw_config->name;
823c92316bfSLuis R. Rodriguez 		req->fw = NULL;
824c92316bfSLuis R. Rodriguez 		req->idx = i;
825c92316bfSLuis R. Rodriguez 		init_completion(&req->completion);
826c92316bfSLuis R. Rodriguez 		rc = request_firmware_nowait(THIS_MODULE, send_uevent,
827c92316bfSLuis R. Rodriguez 					     req->name,
828c92316bfSLuis R. Rodriguez 					     dev, GFP_KERNEL, req,
829c92316bfSLuis R. Rodriguez 					     trigger_batched_cb);
830c92316bfSLuis R. Rodriguez 		if (rc) {
831c92316bfSLuis R. Rodriguez 			pr_info("#%u: batched async load failed setup: %d\n",
832c92316bfSLuis R. Rodriguez 				i, rc);
833c92316bfSLuis R. Rodriguez 			req->rc = rc;
834c92316bfSLuis R. Rodriguez 			goto out_bail;
835c92316bfSLuis R. Rodriguez 		} else
836c92316bfSLuis R. Rodriguez 			req->sent = true;
837c92316bfSLuis R. Rodriguez 	}
838c92316bfSLuis R. Rodriguez 
839c92316bfSLuis R. Rodriguez 	rc = count;
840c92316bfSLuis R. Rodriguez 
841c92316bfSLuis R. Rodriguez out_bail:
842c92316bfSLuis R. Rodriguez 
843c92316bfSLuis R. Rodriguez 	/*
844c92316bfSLuis R. Rodriguez 	 * We require an explicit release to enable more time and delay of
845c92316bfSLuis R. Rodriguez 	 * calling release_firmware() to improve our chances of forcing a
846c92316bfSLuis R. Rodriguez 	 * batched request. If we instead called release_firmware() right away
847c92316bfSLuis R. Rodriguez 	 * then we might miss on an opportunity of having a successful firmware
848c92316bfSLuis R. Rodriguez 	 * request pass on the opportunity to be come a batched request.
849c92316bfSLuis R. Rodriguez 	 */
850c92316bfSLuis R. Rodriguez 
851c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
852c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
853c92316bfSLuis R. Rodriguez 		if (req->sent)
854c92316bfSLuis R. Rodriguez 			wait_for_completion(&req->completion);
855c92316bfSLuis R. Rodriguez 	}
856c92316bfSLuis R. Rodriguez 
857c92316bfSLuis R. Rodriguez 	/* Override any worker error if we had a general setup error */
858c92316bfSLuis R. Rodriguez 	if (rc < 0)
859c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = rc;
860c92316bfSLuis R. Rodriguez 
861c92316bfSLuis R. Rodriguez out:
862c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
863c92316bfSLuis R. Rodriguez 
864c92316bfSLuis R. Rodriguez 	return rc;
865c92316bfSLuis R. Rodriguez }
866c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests_async);
867c92316bfSLuis R. Rodriguez 
868c92316bfSLuis R. Rodriguez static ssize_t test_result_show(struct device *dev,
869c92316bfSLuis R. Rodriguez 				struct device_attribute *attr,
870c92316bfSLuis R. Rodriguez 				char *buf)
871c92316bfSLuis R. Rodriguez {
872c92316bfSLuis R. Rodriguez 	return test_dev_config_show_int(buf, test_fw_config->test_result);
873c92316bfSLuis R. Rodriguez }
874c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(test_result);
875c92316bfSLuis R. Rodriguez 
876c92316bfSLuis R. Rodriguez static ssize_t release_all_firmware_store(struct device *dev,
877c92316bfSLuis R. Rodriguez 					  struct device_attribute *attr,
878c92316bfSLuis R. Rodriguez 					  const char *buf, size_t count)
879c92316bfSLuis R. Rodriguez {
880c92316bfSLuis R. Rodriguez 	test_release_all_firmware();
881c92316bfSLuis R. Rodriguez 	return count;
882c92316bfSLuis R. Rodriguez }
883c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(release_all_firmware);
884c92316bfSLuis R. Rodriguez 
885c92316bfSLuis R. Rodriguez static ssize_t read_firmware_show(struct device *dev,
886c92316bfSLuis R. Rodriguez 				  struct device_attribute *attr,
887c92316bfSLuis R. Rodriguez 				  char *buf)
888c92316bfSLuis R. Rodriguez {
889c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
890c92316bfSLuis R. Rodriguez 	u8 idx;
891c92316bfSLuis R. Rodriguez 	ssize_t rc = 0;
892c92316bfSLuis R. Rodriguez 
893c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
894c92316bfSLuis R. Rodriguez 
895c92316bfSLuis R. Rodriguez 	idx = test_fw_config->read_fw_idx;
896c92316bfSLuis R. Rodriguez 	if (idx >= test_fw_config->num_requests) {
897c92316bfSLuis R. Rodriguez 		rc = -ERANGE;
898c92316bfSLuis R. Rodriguez 		goto out;
899c92316bfSLuis R. Rodriguez 	}
900c92316bfSLuis R. Rodriguez 
901c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs) {
902c92316bfSLuis R. Rodriguez 		rc = -EINVAL;
903c92316bfSLuis R. Rodriguez 		goto out;
904c92316bfSLuis R. Rodriguez 	}
905c92316bfSLuis R. Rodriguez 
906c92316bfSLuis R. Rodriguez 	req = &test_fw_config->reqs[idx];
907c92316bfSLuis R. Rodriguez 	if (!req->fw) {
908c92316bfSLuis R. Rodriguez 		pr_err("#%u: failed to async load firmware\n", idx);
909c92316bfSLuis R. Rodriguez 		rc = -ENOENT;
910c92316bfSLuis R. Rodriguez 		goto out;
911c92316bfSLuis R. Rodriguez 	}
912c92316bfSLuis R. Rodriguez 
913c92316bfSLuis R. Rodriguez 	pr_info("#%u: loaded %zu\n", idx, req->fw->size);
914c92316bfSLuis R. Rodriguez 
915c92316bfSLuis R. Rodriguez 	if (req->fw->size > PAGE_SIZE) {
916c92316bfSLuis R. Rodriguez 		pr_err("Testing interface must use PAGE_SIZE firmware for now\n");
917c92316bfSLuis R. Rodriguez 		rc = -EINVAL;
9188bb0a886SColin Ian King 		goto out;
919c92316bfSLuis R. Rodriguez 	}
920c92316bfSLuis R. Rodriguez 	memcpy(buf, req->fw->data, req->fw->size);
921c92316bfSLuis R. Rodriguez 
922c92316bfSLuis R. Rodriguez 	rc = req->fw->size;
923c92316bfSLuis R. Rodriguez out:
924c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
925c92316bfSLuis R. Rodriguez 
926c92316bfSLuis R. Rodriguez 	return rc;
927c92316bfSLuis R. Rodriguez }
928c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(read_firmware);
929c92316bfSLuis R. Rodriguez 
930083a93b0SLuis R. Rodriguez #define TEST_FW_DEV_ATTR(name)          &dev_attr_##name.attr
931083a93b0SLuis R. Rodriguez 
932083a93b0SLuis R. Rodriguez static struct attribute *test_dev_attrs[] = {
933c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(reset),
934c92316bfSLuis R. Rodriguez 
935c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config),
936c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_name),
937c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_num_requests),
9387feebfa4SScott Branden 	TEST_FW_DEV_ATTR(config_into_buf),
939c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_sync_direct),
940c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_send_uevent),
941c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_read_fw_idx),
942c92316bfSLuis R. Rodriguez 
943c92316bfSLuis R. Rodriguez 	/* These don't use the config at all - they could be ported! */
944083a93b0SLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_request),
945083a93b0SLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_async_request),
946061132d2SLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_custom_fallback),
947548193cbSHans de Goede #ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
948548193cbSHans de Goede 	TEST_FW_DEV_ATTR(trigger_request_platform),
949548193cbSHans de Goede #endif
950c92316bfSLuis R. Rodriguez 
951c92316bfSLuis R. Rodriguez 	/* These use the config and can use the test_result */
952c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_batched_requests),
953c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_batched_requests_async),
954c92316bfSLuis R. Rodriguez 
955c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(release_all_firmware),
956c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(test_result),
957c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(read_firmware),
958083a93b0SLuis R. Rodriguez 	NULL,
959083a93b0SLuis R. Rodriguez };
960083a93b0SLuis R. Rodriguez 
961083a93b0SLuis R. Rodriguez ATTRIBUTE_GROUPS(test_dev);
962083a93b0SLuis R. Rodriguez 
96367fd553cSLuis R. Rodriguez static struct miscdevice test_fw_misc_device = {
96467fd553cSLuis R. Rodriguez 	.minor          = MISC_DYNAMIC_MINOR,
96567fd553cSLuis R. Rodriguez 	.name           = "test_firmware",
96667fd553cSLuis R. Rodriguez 	.fops           = &test_fw_fops,
967083a93b0SLuis R. Rodriguez 	.groups 	= test_dev_groups,
96867fd553cSLuis R. Rodriguez };
96967fd553cSLuis R. Rodriguez 
9700a8adf58SKees Cook static int __init test_firmware_init(void)
9710a8adf58SKees Cook {
9720a8adf58SKees Cook 	int rc;
9730a8adf58SKees Cook 
974c92316bfSLuis R. Rodriguez 	test_fw_config = kzalloc(sizeof(struct test_config), GFP_KERNEL);
975c92316bfSLuis R. Rodriguez 	if (!test_fw_config)
976c92316bfSLuis R. Rodriguez 		return -ENOMEM;
977c92316bfSLuis R. Rodriguez 
978c92316bfSLuis R. Rodriguez 	rc = __test_firmware_config_init();
979d4fddac5SWenwen Wang 	if (rc) {
980d4fddac5SWenwen Wang 		kfree(test_fw_config);
981d4fddac5SWenwen Wang 		pr_err("could not init firmware test config: %d\n", rc);
982c92316bfSLuis R. Rodriguez 		return rc;
983d4fddac5SWenwen Wang 	}
984c92316bfSLuis R. Rodriguez 
9850a8adf58SKees Cook 	rc = misc_register(&test_fw_misc_device);
9860a8adf58SKees Cook 	if (rc) {
987c92316bfSLuis R. Rodriguez 		kfree(test_fw_config);
9880a8adf58SKees Cook 		pr_err("could not register misc device: %d\n", rc);
9890a8adf58SKees Cook 		return rc;
9900a8adf58SKees Cook 	}
991eb910947SBrian Norris 
9920a8adf58SKees Cook 	pr_warn("interface ready\n");
9930a8adf58SKees Cook 
9940a8adf58SKees Cook 	return 0;
9950a8adf58SKees Cook }
9960a8adf58SKees Cook 
9970a8adf58SKees Cook module_init(test_firmware_init);
9980a8adf58SKees Cook 
9990a8adf58SKees Cook static void __exit test_firmware_exit(void)
10000a8adf58SKees Cook {
1001c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
10020a8adf58SKees Cook 	release_firmware(test_firmware);
10030a8adf58SKees Cook 	misc_deregister(&test_fw_misc_device);
1004c92316bfSLuis R. Rodriguez 	__test_firmware_config_free();
1005c92316bfSLuis R. Rodriguez 	kfree(test_fw_config);
1006c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
1007c92316bfSLuis R. Rodriguez 
10080a8adf58SKees Cook 	pr_warn("removed interface\n");
10090a8adf58SKees Cook }
10100a8adf58SKees Cook 
10110a8adf58SKees Cook module_exit(test_firmware_exit);
10120a8adf58SKees Cook 
10130a8adf58SKees Cook MODULE_AUTHOR("Kees Cook <[email protected]>");
10140a8adf58SKees Cook MODULE_LICENSE("GPL");
1015