xref: /openbmc/linux/lib/test_firmware.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
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>
25f7d85515SChristophe JAILLET #include <linux/kstrtox.h>
26c92316bfSLuis R. Rodriguez #include <linux/kthread.h>
27514c6032SRandy Dunlap #include <linux/vmalloc.h>
28548193cbSHans de Goede #include <linux/efi_embedded_fw.h>
29c92316bfSLuis R. Rodriguez 
30baaabecfSKees Cook MODULE_IMPORT_NS(TEST_FIRMWARE);
31baaabecfSKees Cook 
32c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NAME	"test-firmware.bin"
33c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NUM_REQS	4
347feebfa4SScott Branden #define TEST_FIRMWARE_BUF_SIZE	SZ_1K
35a31ad463SRuss Weight #define TEST_UPLOAD_MAX_SIZE	SZ_2K
36a31ad463SRuss Weight #define TEST_UPLOAD_BLK_SIZE	37	/* Avoid powers of two in testing */
370a8adf58SKees Cook 
380a8adf58SKees Cook static DEFINE_MUTEX(test_fw_mutex);
390a8adf58SKees Cook static const struct firmware *test_firmware;
40a31ad463SRuss Weight static LIST_HEAD(test_upload_list);
410a8adf58SKees Cook 
42c92316bfSLuis R. Rodriguez struct test_batched_req {
43c92316bfSLuis R. Rodriguez 	u8 idx;
44c92316bfSLuis R. Rodriguez 	int rc;
45c92316bfSLuis R. Rodriguez 	bool sent;
46c92316bfSLuis R. Rodriguez 	const struct firmware *fw;
47c92316bfSLuis R. Rodriguez 	const char *name;
48*48e15602SMirsad Goran Todorovac 	const char *fw_buf;
49c92316bfSLuis R. Rodriguez 	struct completion completion;
50c92316bfSLuis R. Rodriguez 	struct task_struct *task;
51c92316bfSLuis R. Rodriguez 	struct device *dev;
52c92316bfSLuis R. Rodriguez };
53c92316bfSLuis R. Rodriguez 
54c92316bfSLuis R. Rodriguez /**
551ad5288fSRandy Dunlap  * struct test_config - represents configuration for the test for different triggers
56c92316bfSLuis R. Rodriguez  *
57c92316bfSLuis R. Rodriguez  * @name: the name of the firmware file to look for
587feebfa4SScott Branden  * @into_buf: when the into_buf is used if this is true
597feebfa4SScott Branden  *	request_firmware_into_buf() will be used instead.
605d90e05cSScott Branden  * @buf_size: size of buf to allocate when into_buf is true
615d90e05cSScott Branden  * @file_offset: file offset to request when calling request_firmware_into_buf
625d90e05cSScott Branden  * @partial: partial read opt when calling request_firmware_into_buf
63c92316bfSLuis R. Rodriguez  * @sync_direct: when the sync trigger is used if this is true
64c92316bfSLuis R. Rodriguez  *	request_firmware_direct() will be used instead.
65c92316bfSLuis R. Rodriguez  * @send_uevent: whether or not to send a uevent for async requests
66c92316bfSLuis R. Rodriguez  * @num_requests: number of requests to try per test case. This is trigger
67c92316bfSLuis R. Rodriguez  *	specific.
68c92316bfSLuis R. Rodriguez  * @reqs: stores all requests information
69c92316bfSLuis R. Rodriguez  * @read_fw_idx: index of thread from which we want to read firmware results
70c92316bfSLuis R. Rodriguez  *	from through the read_fw trigger.
71a31ad463SRuss Weight  * @upload_name: firmware name to be used with upload_read sysfs node
72c92316bfSLuis R. Rodriguez  * @test_result: a test may use this to collect the result from the call
73c92316bfSLuis R. Rodriguez  *	of the request_firmware*() calls used in their tests. In order of
74c92316bfSLuis R. Rodriguez  *	priority we always keep first any setup error. If no setup errors were
75c92316bfSLuis R. Rodriguez  *	found then we move on to the first error encountered while running the
76c92316bfSLuis R. Rodriguez  *	API. Note that for async calls this typically will be a successful
77c92316bfSLuis R. Rodriguez  *	result (0) unless of course you've used bogus parameters, or the system
78c92316bfSLuis R. Rodriguez  *	is out of memory.  In the async case the callback is expected to do a
79c92316bfSLuis R. Rodriguez  *	bit more homework to figure out what happened, unfortunately the only
80c92316bfSLuis R. Rodriguez  *	information passed today on error is the fact that no firmware was
81c92316bfSLuis R. Rodriguez  *	found so we can only assume -ENOENT on async calls if the firmware is
82c92316bfSLuis R. Rodriguez  *	NULL.
83c92316bfSLuis R. Rodriguez  *
84c92316bfSLuis R. Rodriguez  *	Errors you can expect:
85c92316bfSLuis R. Rodriguez  *
86c92316bfSLuis R. Rodriguez  *	API specific:
87c92316bfSLuis R. Rodriguez  *
88c92316bfSLuis R. Rodriguez  *	0:		success for sync, for async it means request was sent
89c92316bfSLuis R. Rodriguez  *	-EINVAL:	invalid parameters or request
90c92316bfSLuis R. Rodriguez  *	-ENOENT:	files not found
91c92316bfSLuis R. Rodriguez  *
92c92316bfSLuis R. Rodriguez  *	System environment:
93c92316bfSLuis R. Rodriguez  *
94c92316bfSLuis R. Rodriguez  *	-ENOMEM:	memory pressure on system
95c92316bfSLuis R. Rodriguez  *	-ENODEV:	out of number of devices to test
96c92316bfSLuis R. Rodriguez  *	-EINVAL:	an unexpected error has occurred
97c92316bfSLuis R. Rodriguez  * @req_firmware: if @sync_direct is true this is set to
98c92316bfSLuis R. Rodriguez  *	request_firmware_direct(), otherwise request_firmware()
99c92316bfSLuis R. Rodriguez  */
100c92316bfSLuis R. Rodriguez struct test_config {
101c92316bfSLuis R. Rodriguez 	char *name;
1027feebfa4SScott Branden 	bool into_buf;
1035d90e05cSScott Branden 	size_t buf_size;
1045d90e05cSScott Branden 	size_t file_offset;
1055d90e05cSScott Branden 	bool partial;
106c92316bfSLuis R. Rodriguez 	bool sync_direct;
107c92316bfSLuis R. Rodriguez 	bool send_uevent;
108c92316bfSLuis R. Rodriguez 	u8 num_requests;
109c92316bfSLuis R. Rodriguez 	u8 read_fw_idx;
110a31ad463SRuss Weight 	char *upload_name;
111c92316bfSLuis R. Rodriguez 
112c92316bfSLuis R. Rodriguez 	/*
113c92316bfSLuis R. Rodriguez 	 * These below don't belong her but we'll move them once we create
114c92316bfSLuis R. Rodriguez 	 * a struct fw_test_device and stuff the misc_dev under there later.
115c92316bfSLuis R. Rodriguez 	 */
116c92316bfSLuis R. Rodriguez 	struct test_batched_req *reqs;
117c92316bfSLuis R. Rodriguez 	int test_result;
118c92316bfSLuis R. Rodriguez 	int (*req_firmware)(const struct firmware **fw, const char *name,
119c92316bfSLuis R. Rodriguez 			    struct device *device);
120c92316bfSLuis R. Rodriguez };
121c92316bfSLuis R. Rodriguez 
1224a4e975bSRuss Weight struct upload_inject_err {
1234a4e975bSRuss Weight 	const char *prog;
1244a4e975bSRuss Weight 	enum fw_upload_err err_code;
1254a4e975bSRuss Weight };
1264a4e975bSRuss Weight 
127a31ad463SRuss Weight struct test_firmware_upload {
128a31ad463SRuss Weight 	char *name;
129a31ad463SRuss Weight 	struct list_head node;
130a31ad463SRuss Weight 	char *buf;
131a31ad463SRuss Weight 	size_t size;
132a31ad463SRuss Weight 	bool cancel_request;
1334a4e975bSRuss Weight 	struct upload_inject_err inject;
134a31ad463SRuss Weight 	struct fw_upload *fwl;
135a31ad463SRuss Weight };
136a31ad463SRuss Weight 
13776f8ab1bSWei Yongjun static struct test_config *test_fw_config;
138c92316bfSLuis R. Rodriguez 
upload_lookup_name(const char * name)139a31ad463SRuss Weight static struct test_firmware_upload *upload_lookup_name(const char *name)
140a31ad463SRuss Weight {
141a31ad463SRuss Weight 	struct test_firmware_upload *tst;
142a31ad463SRuss Weight 
143a31ad463SRuss Weight 	list_for_each_entry(tst, &test_upload_list, node)
144a31ad463SRuss Weight 		if (strncmp(name, tst->name, strlen(tst->name)) == 0)
145a31ad463SRuss Weight 			return tst;
146a31ad463SRuss Weight 
147a31ad463SRuss Weight 	return NULL;
148a31ad463SRuss Weight }
149a31ad463SRuss Weight 
test_fw_misc_read(struct file * f,char __user * buf,size_t size,loff_t * offset)1500a8adf58SKees Cook static ssize_t test_fw_misc_read(struct file *f, char __user *buf,
1510a8adf58SKees Cook 				 size_t size, loff_t *offset)
1520a8adf58SKees Cook {
1530a8adf58SKees Cook 	ssize_t rc = 0;
1540a8adf58SKees Cook 
1550a8adf58SKees Cook 	mutex_lock(&test_fw_mutex);
1560a8adf58SKees Cook 	if (test_firmware)
1570a8adf58SKees Cook 		rc = simple_read_from_buffer(buf, size, offset,
1580a8adf58SKees Cook 					     test_firmware->data,
1590a8adf58SKees Cook 					     test_firmware->size);
1600a8adf58SKees Cook 	mutex_unlock(&test_fw_mutex);
1610a8adf58SKees Cook 	return rc;
1620a8adf58SKees Cook }
1630a8adf58SKees Cook 
1640a8adf58SKees Cook static const struct file_operations test_fw_fops = {
1650a8adf58SKees Cook 	.owner          = THIS_MODULE,
1660a8adf58SKees Cook 	.read           = test_fw_misc_read,
1670a8adf58SKees Cook };
1680a8adf58SKees Cook 
__test_release_all_firmware(void)169c92316bfSLuis R. Rodriguez static void __test_release_all_firmware(void)
170c92316bfSLuis R. Rodriguez {
171c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
172c92316bfSLuis R. Rodriguez 	u8 i;
173c92316bfSLuis R. Rodriguez 
174c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs)
175c92316bfSLuis R. Rodriguez 		return;
176c92316bfSLuis R. Rodriguez 
177c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
178c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
179*48e15602SMirsad Goran Todorovac 		if (req->fw) {
180*48e15602SMirsad Goran Todorovac 			if (req->fw_buf) {
181*48e15602SMirsad Goran Todorovac 				kfree_const(req->fw_buf);
182*48e15602SMirsad Goran Todorovac 				req->fw_buf = NULL;
183*48e15602SMirsad Goran Todorovac 			}
184c92316bfSLuis R. Rodriguez 			release_firmware(req->fw);
185*48e15602SMirsad Goran Todorovac 			req->fw = NULL;
186*48e15602SMirsad Goran Todorovac 		}
187c92316bfSLuis R. Rodriguez 	}
188c92316bfSLuis R. Rodriguez 
189c92316bfSLuis R. Rodriguez 	vfree(test_fw_config->reqs);
190c92316bfSLuis R. Rodriguez 	test_fw_config->reqs = NULL;
191c92316bfSLuis R. Rodriguez }
192c92316bfSLuis R. Rodriguez 
test_release_all_firmware(void)193c92316bfSLuis R. Rodriguez static void test_release_all_firmware(void)
194c92316bfSLuis R. Rodriguez {
195c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
196c92316bfSLuis R. Rodriguez 	__test_release_all_firmware();
197c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
198c92316bfSLuis R. Rodriguez }
199c92316bfSLuis R. Rodriguez 
200c92316bfSLuis R. Rodriguez 
__test_firmware_config_free(void)201c92316bfSLuis R. Rodriguez static void __test_firmware_config_free(void)
202c92316bfSLuis R. Rodriguez {
203c92316bfSLuis R. Rodriguez 	__test_release_all_firmware();
204c92316bfSLuis R. Rodriguez 	kfree_const(test_fw_config->name);
205c92316bfSLuis R. Rodriguez 	test_fw_config->name = NULL;
206c92316bfSLuis R. Rodriguez }
207c92316bfSLuis R. Rodriguez 
208c92316bfSLuis R. Rodriguez /*
209c92316bfSLuis R. Rodriguez  * XXX: move to kstrncpy() once merged.
210c92316bfSLuis R. Rodriguez  *
211c92316bfSLuis R. Rodriguez  * Users should use kfree_const() when freeing these.
212c92316bfSLuis R. Rodriguez  */
__kstrncpy(char ** dst,const char * name,size_t count,gfp_t gfp)213c92316bfSLuis R. Rodriguez static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
214c92316bfSLuis R. Rodriguez {
215c92316bfSLuis R. Rodriguez 	*dst = kstrndup(name, count, gfp);
216c92316bfSLuis R. Rodriguez 	if (!*dst)
217c92316bfSLuis R. Rodriguez 		return -ENOMEM;
218c92316bfSLuis R. Rodriguez 	return count;
219c92316bfSLuis R. Rodriguez }
220c92316bfSLuis R. Rodriguez 
__test_firmware_config_init(void)221c92316bfSLuis R. Rodriguez static int __test_firmware_config_init(void)
222c92316bfSLuis R. Rodriguez {
223c92316bfSLuis R. Rodriguez 	int ret;
224c92316bfSLuis R. Rodriguez 
225c92316bfSLuis R. Rodriguez 	ret = __kstrncpy(&test_fw_config->name, TEST_FIRMWARE_NAME,
226c92316bfSLuis R. Rodriguez 			 strlen(TEST_FIRMWARE_NAME), GFP_KERNEL);
227c92316bfSLuis R. Rodriguez 	if (ret < 0)
228c92316bfSLuis R. Rodriguez 		goto out;
229c92316bfSLuis R. Rodriguez 
230c92316bfSLuis R. Rodriguez 	test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS;
231c92316bfSLuis R. Rodriguez 	test_fw_config->send_uevent = true;
2327feebfa4SScott Branden 	test_fw_config->into_buf = false;
2335d90e05cSScott Branden 	test_fw_config->buf_size = TEST_FIRMWARE_BUF_SIZE;
2345d90e05cSScott Branden 	test_fw_config->file_offset = 0;
2355d90e05cSScott Branden 	test_fw_config->partial = false;
236c92316bfSLuis R. Rodriguez 	test_fw_config->sync_direct = false;
237c92316bfSLuis R. Rodriguez 	test_fw_config->req_firmware = request_firmware;
238c92316bfSLuis R. Rodriguez 	test_fw_config->test_result = 0;
239c92316bfSLuis R. Rodriguez 	test_fw_config->reqs = NULL;
240a31ad463SRuss Weight 	test_fw_config->upload_name = NULL;
241c92316bfSLuis R. Rodriguez 
242c92316bfSLuis R. Rodriguez 	return 0;
243c92316bfSLuis R. Rodriguez 
244c92316bfSLuis R. Rodriguez out:
245c92316bfSLuis R. Rodriguez 	__test_firmware_config_free();
246c92316bfSLuis R. Rodriguez 	return ret;
247c92316bfSLuis R. Rodriguez }
248c92316bfSLuis R. Rodriguez 
reset_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)249c92316bfSLuis R. Rodriguez static ssize_t reset_store(struct device *dev,
250c92316bfSLuis R. Rodriguez 			   struct device_attribute *attr,
251c92316bfSLuis R. Rodriguez 			   const char *buf, size_t count)
252c92316bfSLuis R. Rodriguez {
253c92316bfSLuis R. Rodriguez 	int ret;
254c92316bfSLuis R. Rodriguez 
255c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
256c92316bfSLuis R. Rodriguez 
257c92316bfSLuis R. Rodriguez 	__test_firmware_config_free();
258c92316bfSLuis R. Rodriguez 
259c92316bfSLuis R. Rodriguez 	ret = __test_firmware_config_init();
260c92316bfSLuis R. Rodriguez 	if (ret < 0) {
261c92316bfSLuis R. Rodriguez 		ret = -ENOMEM;
262c92316bfSLuis R. Rodriguez 		pr_err("could not alloc settings for config trigger: %d\n",
263c92316bfSLuis R. Rodriguez 		       ret);
264c92316bfSLuis R. Rodriguez 		goto out;
265c92316bfSLuis R. Rodriguez 	}
266c92316bfSLuis R. Rodriguez 
267c92316bfSLuis R. Rodriguez 	pr_info("reset\n");
268c92316bfSLuis R. Rodriguez 	ret = count;
269c92316bfSLuis R. Rodriguez 
270c92316bfSLuis R. Rodriguez out:
271c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
272c92316bfSLuis R. Rodriguez 
273c92316bfSLuis R. Rodriguez 	return ret;
274c92316bfSLuis R. Rodriguez }
275c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(reset);
276c92316bfSLuis R. Rodriguez 
config_show(struct device * dev,struct device_attribute * attr,char * buf)277c92316bfSLuis R. Rodriguez static ssize_t config_show(struct device *dev,
278c92316bfSLuis R. Rodriguez 			   struct device_attribute *attr,
279c92316bfSLuis R. Rodriguez 			   char *buf)
280c92316bfSLuis R. Rodriguez {
281c92316bfSLuis R. Rodriguez 	int len = 0;
282c92316bfSLuis R. Rodriguez 
283c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
284c92316bfSLuis R. Rodriguez 
285bd17cc5aSDan Carpenter 	len += scnprintf(buf, PAGE_SIZE - len,
286c92316bfSLuis R. Rodriguez 			"Custom trigger configuration for: %s\n",
287c92316bfSLuis R. Rodriguez 			dev_name(dev));
288c92316bfSLuis R. Rodriguez 
289c92316bfSLuis R. Rodriguez 	if (test_fw_config->name)
290bd17cc5aSDan Carpenter 		len += scnprintf(buf + len, PAGE_SIZE - len,
291c92316bfSLuis R. Rodriguez 				"name:\t%s\n",
292c92316bfSLuis R. Rodriguez 				test_fw_config->name);
293c92316bfSLuis R. Rodriguez 	else
294bd17cc5aSDan Carpenter 		len += scnprintf(buf + len, PAGE_SIZE - len,
295d88bd098SColin Ian King 				"name:\tEMPTY\n");
296c92316bfSLuis R. Rodriguez 
297bd17cc5aSDan Carpenter 	len += scnprintf(buf + len, PAGE_SIZE - len,
298c92316bfSLuis R. Rodriguez 			"num_requests:\t%u\n", test_fw_config->num_requests);
299c92316bfSLuis R. Rodriguez 
300bd17cc5aSDan Carpenter 	len += scnprintf(buf + len, PAGE_SIZE - len,
301c92316bfSLuis R. Rodriguez 			"send_uevent:\t\t%s\n",
302c92316bfSLuis R. Rodriguez 			test_fw_config->send_uevent ?
3030733d839SShawn Guo 			"FW_ACTION_UEVENT" :
3040733d839SShawn Guo 			"FW_ACTION_NOUEVENT");
305bd17cc5aSDan Carpenter 	len += scnprintf(buf + len, PAGE_SIZE - len,
3067feebfa4SScott Branden 			"into_buf:\t\t%s\n",
3077feebfa4SScott Branden 			test_fw_config->into_buf ? "true" : "false");
3087feebfa4SScott Branden 	len += scnprintf(buf + len, PAGE_SIZE - len,
3095d90e05cSScott Branden 			"buf_size:\t%zu\n", test_fw_config->buf_size);
3105d90e05cSScott Branden 	len += scnprintf(buf + len, PAGE_SIZE - len,
3115d90e05cSScott Branden 			"file_offset:\t%zu\n", test_fw_config->file_offset);
3125d90e05cSScott Branden 	len += scnprintf(buf + len, PAGE_SIZE - len,
3135d90e05cSScott Branden 			"partial:\t\t%s\n",
3145d90e05cSScott Branden 			test_fw_config->partial ? "true" : "false");
3155d90e05cSScott Branden 	len += scnprintf(buf + len, PAGE_SIZE - len,
316c92316bfSLuis R. Rodriguez 			"sync_direct:\t\t%s\n",
317c92316bfSLuis R. Rodriguez 			test_fw_config->sync_direct ? "true" : "false");
318bd17cc5aSDan Carpenter 	len += scnprintf(buf + len, PAGE_SIZE - len,
319c92316bfSLuis R. Rodriguez 			"read_fw_idx:\t%u\n", test_fw_config->read_fw_idx);
320a31ad463SRuss Weight 	if (test_fw_config->upload_name)
321a31ad463SRuss Weight 		len += scnprintf(buf + len, PAGE_SIZE - len,
322a31ad463SRuss Weight 				"upload_name:\t%s\n",
323a31ad463SRuss Weight 				test_fw_config->upload_name);
324a31ad463SRuss Weight 	else
325a31ad463SRuss Weight 		len += scnprintf(buf + len, PAGE_SIZE - len,
326d88bd098SColin Ian King 				"upload_name:\tEMPTY\n");
327c92316bfSLuis R. Rodriguez 
328c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
329c92316bfSLuis R. Rodriguez 
330c92316bfSLuis R. Rodriguez 	return len;
331c92316bfSLuis R. Rodriguez }
332c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(config);
333c92316bfSLuis R. Rodriguez 
config_name_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)334c92316bfSLuis R. Rodriguez static ssize_t config_name_store(struct device *dev,
335c92316bfSLuis R. Rodriguez 				 struct device_attribute *attr,
336c92316bfSLuis R. Rodriguez 				 const char *buf, size_t count)
337c92316bfSLuis R. Rodriguez {
338c92316bfSLuis R. Rodriguez 	int ret;
339c92316bfSLuis R. Rodriguez 
340c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
341c92316bfSLuis R. Rodriguez 	kfree_const(test_fw_config->name);
342c92316bfSLuis R. Rodriguez 	ret = __kstrncpy(&test_fw_config->name, buf, count, GFP_KERNEL);
343c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
344c92316bfSLuis R. Rodriguez 
345c92316bfSLuis R. Rodriguez 	return ret;
346c92316bfSLuis R. Rodriguez }
347c92316bfSLuis R. Rodriguez 
348c92316bfSLuis R. Rodriguez /*
349c92316bfSLuis R. Rodriguez  * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
350c92316bfSLuis R. Rodriguez  */
config_test_show_str(char * dst,char * src)351c92316bfSLuis R. Rodriguez static ssize_t config_test_show_str(char *dst,
352c92316bfSLuis R. Rodriguez 				    char *src)
353c92316bfSLuis R. Rodriguez {
354c92316bfSLuis R. Rodriguez 	int len;
355c92316bfSLuis R. Rodriguez 
356c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
357c92316bfSLuis R. Rodriguez 	len = snprintf(dst, PAGE_SIZE, "%s\n", src);
358c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
359c92316bfSLuis R. Rodriguez 
360c92316bfSLuis R. Rodriguez 	return len;
361c92316bfSLuis R. Rodriguez }
362c92316bfSLuis R. Rodriguez 
__test_dev_config_update_bool(const char * buf,size_t size,bool * cfg)3634acfe3dfSMirsad Goran Todorovac static inline int __test_dev_config_update_bool(const char *buf, size_t size,
3644acfe3dfSMirsad Goran Todorovac 				       bool *cfg)
3654acfe3dfSMirsad Goran Todorovac {
3664acfe3dfSMirsad Goran Todorovac 	int ret;
3674acfe3dfSMirsad Goran Todorovac 
3684acfe3dfSMirsad Goran Todorovac 	if (kstrtobool(buf, cfg) < 0)
3694acfe3dfSMirsad Goran Todorovac 		ret = -EINVAL;
3704acfe3dfSMirsad Goran Todorovac 	else
3714acfe3dfSMirsad Goran Todorovac 		ret = size;
3724acfe3dfSMirsad Goran Todorovac 
3734acfe3dfSMirsad Goran Todorovac 	return ret;
3744acfe3dfSMirsad Goran Todorovac }
3754acfe3dfSMirsad Goran Todorovac 
test_dev_config_update_bool(const char * buf,size_t size,bool * cfg)376c92316bfSLuis R. Rodriguez static int test_dev_config_update_bool(const char *buf, size_t size,
377c92316bfSLuis R. Rodriguez 				       bool *cfg)
378c92316bfSLuis R. Rodriguez {
379c92316bfSLuis R. Rodriguez 	int ret;
380c92316bfSLuis R. Rodriguez 
381c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
3824acfe3dfSMirsad Goran Todorovac 	ret = __test_dev_config_update_bool(buf, size, cfg);
383c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
384c92316bfSLuis R. Rodriguez 
385c92316bfSLuis R. Rodriguez 	return ret;
386c92316bfSLuis R. Rodriguez }
387c92316bfSLuis R. Rodriguez 
test_dev_config_show_bool(char * buf,bool val)38855623260SScott Branden static ssize_t test_dev_config_show_bool(char *buf, bool val)
389c92316bfSLuis R. Rodriguez {
390c92316bfSLuis R. Rodriguez 	return snprintf(buf, PAGE_SIZE, "%d\n", val);
391c92316bfSLuis R. Rodriguez }
392c92316bfSLuis R. Rodriguez 
__test_dev_config_update_size_t(const char * buf,size_t size,size_t * cfg)3934acfe3dfSMirsad Goran Todorovac static int __test_dev_config_update_size_t(
3944acfe3dfSMirsad Goran Todorovac 					 const char *buf,
3955d90e05cSScott Branden 					 size_t size,
3965d90e05cSScott Branden 					 size_t *cfg)
3975d90e05cSScott Branden {
3985d90e05cSScott Branden 	int ret;
3995d90e05cSScott Branden 	long new;
4005d90e05cSScott Branden 
4015d90e05cSScott Branden 	ret = kstrtol(buf, 10, &new);
4025d90e05cSScott Branden 	if (ret)
4035d90e05cSScott Branden 		return ret;
4045d90e05cSScott Branden 
4055d90e05cSScott Branden 	*(size_t *)cfg = new;
4065d90e05cSScott Branden 
4075d90e05cSScott Branden 	/* Always return full write size even if we didn't consume all */
4085d90e05cSScott Branden 	return size;
4095d90e05cSScott Branden }
4105d90e05cSScott Branden 
test_dev_config_show_size_t(char * buf,size_t val)4115d90e05cSScott Branden static ssize_t test_dev_config_show_size_t(char *buf, size_t val)
4125d90e05cSScott Branden {
4135d90e05cSScott Branden 	return snprintf(buf, PAGE_SIZE, "%zu\n", val);
4145d90e05cSScott Branden }
4155d90e05cSScott Branden 
test_dev_config_show_int(char * buf,int val)41655623260SScott Branden static ssize_t test_dev_config_show_int(char *buf, int val)
417c92316bfSLuis R. Rodriguez {
418c92316bfSLuis R. Rodriguez 	return snprintf(buf, PAGE_SIZE, "%d\n", val);
419c92316bfSLuis R. Rodriguez }
420c92316bfSLuis R. Rodriguez 
__test_dev_config_update_u8(const char * buf,size_t size,u8 * cfg)4214acfe3dfSMirsad Goran Todorovac static int __test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg)
422c92316bfSLuis R. Rodriguez {
423506dfc99SAlexey Dobriyan 	u8 val;
424c92316bfSLuis R. Rodriguez 	int ret;
425c92316bfSLuis R. Rodriguez 
426506dfc99SAlexey Dobriyan 	ret = kstrtou8(buf, 10, &val);
427c92316bfSLuis R. Rodriguez 	if (ret)
428c92316bfSLuis R. Rodriguez 		return ret;
429c92316bfSLuis R. Rodriguez 
430506dfc99SAlexey Dobriyan 	*(u8 *)cfg = val;
431c92316bfSLuis R. Rodriguez 
432c92316bfSLuis R. Rodriguez 	/* Always return full write size even if we didn't consume all */
433c92316bfSLuis R. Rodriguez 	return size;
434c92316bfSLuis R. Rodriguez }
435c92316bfSLuis R. Rodriguez 
test_dev_config_update_u8(const char * buf,size_t size,u8 * cfg)4364acfe3dfSMirsad Goran Todorovac static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg)
4374acfe3dfSMirsad Goran Todorovac {
4384acfe3dfSMirsad Goran Todorovac 	int ret;
4394acfe3dfSMirsad Goran Todorovac 
4404acfe3dfSMirsad Goran Todorovac 	mutex_lock(&test_fw_mutex);
4414acfe3dfSMirsad Goran Todorovac 	ret = __test_dev_config_update_u8(buf, size, cfg);
4424acfe3dfSMirsad Goran Todorovac 	mutex_unlock(&test_fw_mutex);
4434acfe3dfSMirsad Goran Todorovac 
4444acfe3dfSMirsad Goran Todorovac 	return ret;
4454acfe3dfSMirsad Goran Todorovac }
4464acfe3dfSMirsad Goran Todorovac 
test_dev_config_show_u8(char * buf,u8 val)44755623260SScott Branden static ssize_t test_dev_config_show_u8(char *buf, u8 val)
448c92316bfSLuis R. Rodriguez {
449c92316bfSLuis R. Rodriguez 	return snprintf(buf, PAGE_SIZE, "%u\n", val);
450c92316bfSLuis R. Rodriguez }
451c92316bfSLuis R. Rodriguez 
config_name_show(struct device * dev,struct device_attribute * attr,char * buf)452c92316bfSLuis R. Rodriguez static ssize_t config_name_show(struct device *dev,
453c92316bfSLuis R. Rodriguez 				struct device_attribute *attr,
454c92316bfSLuis R. Rodriguez 				char *buf)
455c92316bfSLuis R. Rodriguez {
456c92316bfSLuis R. Rodriguez 	return config_test_show_str(buf, test_fw_config->name);
457c92316bfSLuis R. Rodriguez }
458b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_name);
459c92316bfSLuis R. Rodriguez 
config_upload_name_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)460a31ad463SRuss Weight static ssize_t config_upload_name_store(struct device *dev,
461a31ad463SRuss Weight 					struct device_attribute *attr,
462a31ad463SRuss Weight 					const char *buf, size_t count)
463a31ad463SRuss Weight {
464a31ad463SRuss Weight 	struct test_firmware_upload *tst;
465a31ad463SRuss Weight 	int ret = count;
466a31ad463SRuss Weight 
467a31ad463SRuss Weight 	mutex_lock(&test_fw_mutex);
468a31ad463SRuss Weight 	tst = upload_lookup_name(buf);
469a31ad463SRuss Weight 	if (tst)
470a31ad463SRuss Weight 		test_fw_config->upload_name = tst->name;
471a31ad463SRuss Weight 	else
472a31ad463SRuss Weight 		ret = -EINVAL;
473a31ad463SRuss Weight 	mutex_unlock(&test_fw_mutex);
474a31ad463SRuss Weight 
475a31ad463SRuss Weight 	return ret;
476a31ad463SRuss Weight }
477a31ad463SRuss Weight 
config_upload_name_show(struct device * dev,struct device_attribute * attr,char * buf)478a31ad463SRuss Weight static ssize_t config_upload_name_show(struct device *dev,
479a31ad463SRuss Weight 				       struct device_attribute *attr,
480a31ad463SRuss Weight 				       char *buf)
481a31ad463SRuss Weight {
482a31ad463SRuss Weight 	return config_test_show_str(buf, test_fw_config->upload_name);
483a31ad463SRuss Weight }
484a31ad463SRuss Weight static DEVICE_ATTR_RW(config_upload_name);
485a31ad463SRuss Weight 
config_num_requests_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)486c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_store(struct device *dev,
487c92316bfSLuis R. Rodriguez 					 struct device_attribute *attr,
488c92316bfSLuis R. Rodriguez 					 const char *buf, size_t count)
489c92316bfSLuis R. Rodriguez {
490c92316bfSLuis R. Rodriguez 	int rc;
491c92316bfSLuis R. Rodriguez 
492c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
493c92316bfSLuis R. Rodriguez 	if (test_fw_config->reqs) {
494c92316bfSLuis R. Rodriguez 		pr_err("Must call release_all_firmware prior to changing config\n");
495c92316bfSLuis R. Rodriguez 		rc = -EINVAL;
496a5e19233SWei Yongjun 		mutex_unlock(&test_fw_mutex);
497c92316bfSLuis R. Rodriguez 		goto out;
498c92316bfSLuis R. Rodriguez 	}
499c92316bfSLuis R. Rodriguez 
5004acfe3dfSMirsad Goran Todorovac 	rc = __test_dev_config_update_u8(buf, count,
501c92316bfSLuis R. Rodriguez 					 &test_fw_config->num_requests);
5024acfe3dfSMirsad Goran Todorovac 	mutex_unlock(&test_fw_mutex);
503c92316bfSLuis R. Rodriguez 
504c92316bfSLuis R. Rodriguez out:
505c92316bfSLuis R. Rodriguez 	return rc;
506c92316bfSLuis R. Rodriguez }
507c92316bfSLuis R. Rodriguez 
config_num_requests_show(struct device * dev,struct device_attribute * attr,char * buf)508c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_show(struct device *dev,
509c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
510c92316bfSLuis R. Rodriguez 					char *buf)
511c92316bfSLuis R. Rodriguez {
512c92316bfSLuis R. Rodriguez 	return test_dev_config_show_u8(buf, test_fw_config->num_requests);
513c92316bfSLuis R. Rodriguez }
514b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_num_requests);
515c92316bfSLuis R. Rodriguez 
config_into_buf_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)5167feebfa4SScott Branden static ssize_t config_into_buf_store(struct device *dev,
5177feebfa4SScott Branden 				     struct device_attribute *attr,
5187feebfa4SScott Branden 				     const char *buf, size_t count)
5197feebfa4SScott Branden {
5207feebfa4SScott Branden 	return test_dev_config_update_bool(buf,
5217feebfa4SScott Branden 					   count,
5227feebfa4SScott Branden 					   &test_fw_config->into_buf);
5237feebfa4SScott Branden }
5247feebfa4SScott Branden 
config_into_buf_show(struct device * dev,struct device_attribute * attr,char * buf)5257feebfa4SScott Branden static ssize_t config_into_buf_show(struct device *dev,
5267feebfa4SScott Branden 				    struct device_attribute *attr,
5277feebfa4SScott Branden 				    char *buf)
5287feebfa4SScott Branden {
5297feebfa4SScott Branden 	return test_dev_config_show_bool(buf, test_fw_config->into_buf);
5307feebfa4SScott Branden }
5317feebfa4SScott Branden static DEVICE_ATTR_RW(config_into_buf);
5327feebfa4SScott Branden 
config_buf_size_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)5335d90e05cSScott Branden static ssize_t config_buf_size_store(struct device *dev,
5345d90e05cSScott Branden 				     struct device_attribute *attr,
5355d90e05cSScott Branden 				     const char *buf, size_t count)
5365d90e05cSScott Branden {
5375d90e05cSScott Branden 	int rc;
5385d90e05cSScott Branden 
5395d90e05cSScott Branden 	mutex_lock(&test_fw_mutex);
5405d90e05cSScott Branden 	if (test_fw_config->reqs) {
5415d90e05cSScott Branden 		pr_err("Must call release_all_firmware prior to changing config\n");
5425d90e05cSScott Branden 		rc = -EINVAL;
5435d90e05cSScott Branden 		mutex_unlock(&test_fw_mutex);
5445d90e05cSScott Branden 		goto out;
5455d90e05cSScott Branden 	}
5465d90e05cSScott Branden 
5474acfe3dfSMirsad Goran Todorovac 	rc = __test_dev_config_update_size_t(buf, count,
5485d90e05cSScott Branden 					     &test_fw_config->buf_size);
5494acfe3dfSMirsad Goran Todorovac 	mutex_unlock(&test_fw_mutex);
5505d90e05cSScott Branden 
5515d90e05cSScott Branden out:
5525d90e05cSScott Branden 	return rc;
5535d90e05cSScott Branden }
5545d90e05cSScott Branden 
config_buf_size_show(struct device * dev,struct device_attribute * attr,char * buf)5555d90e05cSScott Branden static ssize_t config_buf_size_show(struct device *dev,
5565d90e05cSScott Branden 				    struct device_attribute *attr,
5575d90e05cSScott Branden 				    char *buf)
5585d90e05cSScott Branden {
5595d90e05cSScott Branden 	return test_dev_config_show_size_t(buf, test_fw_config->buf_size);
5605d90e05cSScott Branden }
5615d90e05cSScott Branden static DEVICE_ATTR_RW(config_buf_size);
5625d90e05cSScott Branden 
config_file_offset_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)5635d90e05cSScott Branden static ssize_t config_file_offset_store(struct device *dev,
5645d90e05cSScott Branden 					struct device_attribute *attr,
5655d90e05cSScott Branden 					const char *buf, size_t count)
5665d90e05cSScott Branden {
5675d90e05cSScott Branden 	int rc;
5685d90e05cSScott Branden 
5695d90e05cSScott Branden 	mutex_lock(&test_fw_mutex);
5705d90e05cSScott Branden 	if (test_fw_config->reqs) {
5715d90e05cSScott Branden 		pr_err("Must call release_all_firmware prior to changing config\n");
5725d90e05cSScott Branden 		rc = -EINVAL;
5735d90e05cSScott Branden 		mutex_unlock(&test_fw_mutex);
5745d90e05cSScott Branden 		goto out;
5755d90e05cSScott Branden 	}
5765d90e05cSScott Branden 
5774acfe3dfSMirsad Goran Todorovac 	rc = __test_dev_config_update_size_t(buf, count,
5785d90e05cSScott Branden 					     &test_fw_config->file_offset);
5794acfe3dfSMirsad Goran Todorovac 	mutex_unlock(&test_fw_mutex);
5805d90e05cSScott Branden 
5815d90e05cSScott Branden out:
5825d90e05cSScott Branden 	return rc;
5835d90e05cSScott Branden }
5845d90e05cSScott Branden 
config_file_offset_show(struct device * dev,struct device_attribute * attr,char * buf)5855d90e05cSScott Branden static ssize_t config_file_offset_show(struct device *dev,
5865d90e05cSScott Branden 				       struct device_attribute *attr,
5875d90e05cSScott Branden 				       char *buf)
5885d90e05cSScott Branden {
5895d90e05cSScott Branden 	return test_dev_config_show_size_t(buf, test_fw_config->file_offset);
5905d90e05cSScott Branden }
5915d90e05cSScott Branden static DEVICE_ATTR_RW(config_file_offset);
5925d90e05cSScott Branden 
config_partial_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)5935d90e05cSScott Branden static ssize_t config_partial_store(struct device *dev,
5945d90e05cSScott Branden 				    struct device_attribute *attr,
5955d90e05cSScott Branden 				    const char *buf, size_t count)
5965d90e05cSScott Branden {
5975d90e05cSScott Branden 	return test_dev_config_update_bool(buf,
5985d90e05cSScott Branden 					   count,
5995d90e05cSScott Branden 					   &test_fw_config->partial);
6005d90e05cSScott Branden }
6015d90e05cSScott Branden 
config_partial_show(struct device * dev,struct device_attribute * attr,char * buf)6025d90e05cSScott Branden static ssize_t config_partial_show(struct device *dev,
6035d90e05cSScott Branden 				   struct device_attribute *attr,
6045d90e05cSScott Branden 				   char *buf)
6055d90e05cSScott Branden {
6065d90e05cSScott Branden 	return test_dev_config_show_bool(buf, test_fw_config->partial);
6075d90e05cSScott Branden }
6085d90e05cSScott Branden static DEVICE_ATTR_RW(config_partial);
6095d90e05cSScott Branden 
config_sync_direct_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)610c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_store(struct device *dev,
611c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
612c92316bfSLuis R. Rodriguez 					const char *buf, size_t count)
613c92316bfSLuis R. Rodriguez {
614c92316bfSLuis R. Rodriguez 	int rc = test_dev_config_update_bool(buf, count,
615c92316bfSLuis R. Rodriguez 					     &test_fw_config->sync_direct);
616c92316bfSLuis R. Rodriguez 
617c92316bfSLuis R. Rodriguez 	if (rc == count)
618c92316bfSLuis R. Rodriguez 		test_fw_config->req_firmware = test_fw_config->sync_direct ?
619c92316bfSLuis R. Rodriguez 				       request_firmware_direct :
620c92316bfSLuis R. Rodriguez 				       request_firmware;
621c92316bfSLuis R. Rodriguez 	return rc;
622c92316bfSLuis R. Rodriguez }
623c92316bfSLuis R. Rodriguez 
config_sync_direct_show(struct device * dev,struct device_attribute * attr,char * buf)624c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_show(struct device *dev,
625c92316bfSLuis R. Rodriguez 				       struct device_attribute *attr,
626c92316bfSLuis R. Rodriguez 				       char *buf)
627c92316bfSLuis R. Rodriguez {
628c92316bfSLuis R. Rodriguez 	return test_dev_config_show_bool(buf, test_fw_config->sync_direct);
629c92316bfSLuis R. Rodriguez }
630b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_sync_direct);
631c92316bfSLuis R. Rodriguez 
config_send_uevent_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)632c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_store(struct device *dev,
633c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
634c92316bfSLuis R. Rodriguez 					const char *buf, size_t count)
635c92316bfSLuis R. Rodriguez {
636c92316bfSLuis R. Rodriguez 	return test_dev_config_update_bool(buf, count,
637c92316bfSLuis R. Rodriguez 					   &test_fw_config->send_uevent);
638c92316bfSLuis R. Rodriguez }
639c92316bfSLuis R. Rodriguez 
config_send_uevent_show(struct device * dev,struct device_attribute * attr,char * buf)640c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_show(struct device *dev,
641c92316bfSLuis R. Rodriguez 				       struct device_attribute *attr,
642c92316bfSLuis R. Rodriguez 				       char *buf)
643c92316bfSLuis R. Rodriguez {
644c92316bfSLuis R. Rodriguez 	return test_dev_config_show_bool(buf, test_fw_config->send_uevent);
645c92316bfSLuis R. Rodriguez }
646b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_send_uevent);
647c92316bfSLuis R. Rodriguez 
config_read_fw_idx_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)648c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_store(struct device *dev,
649c92316bfSLuis R. Rodriguez 					struct device_attribute *attr,
650c92316bfSLuis R. Rodriguez 					const char *buf, size_t count)
651c92316bfSLuis R. Rodriguez {
652c92316bfSLuis R. Rodriguez 	return test_dev_config_update_u8(buf, count,
653c92316bfSLuis R. Rodriguez 					 &test_fw_config->read_fw_idx);
654c92316bfSLuis R. Rodriguez }
655c92316bfSLuis R. Rodriguez 
config_read_fw_idx_show(struct device * dev,struct device_attribute * attr,char * buf)656c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_show(struct device *dev,
657c92316bfSLuis R. Rodriguez 				       struct device_attribute *attr,
658c92316bfSLuis R. Rodriguez 				       char *buf)
659c92316bfSLuis R. Rodriguez {
660c92316bfSLuis R. Rodriguez 	return test_dev_config_show_u8(buf, test_fw_config->read_fw_idx);
661c92316bfSLuis R. Rodriguez }
662b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_read_fw_idx);
663c92316bfSLuis R. Rodriguez 
664c92316bfSLuis R. Rodriguez 
trigger_request_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)6650a8adf58SKees Cook static ssize_t trigger_request_store(struct device *dev,
6660a8adf58SKees Cook 				     struct device_attribute *attr,
6670a8adf58SKees Cook 				     const char *buf, size_t count)
6680a8adf58SKees Cook {
6690a8adf58SKees Cook 	int rc;
6700a8adf58SKees Cook 	char *name;
6710a8adf58SKees Cook 
672be4a1326SBrian Norris 	name = kstrndup(buf, count, GFP_KERNEL);
6730a8adf58SKees Cook 	if (!name)
6740a8adf58SKees Cook 		return -ENOMEM;
6750a8adf58SKees Cook 
6760a8adf58SKees Cook 	pr_info("loading '%s'\n", name);
6770a8adf58SKees Cook 
6780a8adf58SKees Cook 	mutex_lock(&test_fw_mutex);
6790a8adf58SKees Cook 	release_firmware(test_firmware);
680*48e15602SMirsad Goran Todorovac 	if (test_fw_config->reqs)
681*48e15602SMirsad Goran Todorovac 		__test_release_all_firmware();
6820a8adf58SKees Cook 	test_firmware = NULL;
6830a8adf58SKees Cook 	rc = request_firmware(&test_firmware, name, dev);
68447e0bbb7SBrian Norris 	if (rc) {
6850a8adf58SKees Cook 		pr_info("load of '%s' failed: %d\n", name, rc);
68647e0bbb7SBrian Norris 		goto out;
68747e0bbb7SBrian Norris 	}
68847e0bbb7SBrian Norris 	pr_info("loaded: %zu\n", test_firmware->size);
68947e0bbb7SBrian Norris 	rc = count;
69047e0bbb7SBrian Norris 
69147e0bbb7SBrian Norris out:
6920a8adf58SKees Cook 	mutex_unlock(&test_fw_mutex);
6930a8adf58SKees Cook 
6940a8adf58SKees Cook 	kfree(name);
6950a8adf58SKees Cook 
69647e0bbb7SBrian Norris 	return rc;
6970a8adf58SKees Cook }
6980a8adf58SKees Cook static DEVICE_ATTR_WO(trigger_request);
6990a8adf58SKees Cook 
700548193cbSHans de Goede #ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
701baaabecfSKees Cook extern struct list_head efi_embedded_fw_list;
702baaabecfSKees Cook extern bool efi_embedded_fw_checked;
703baaabecfSKees Cook 
trigger_request_platform_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)704548193cbSHans de Goede static ssize_t trigger_request_platform_store(struct device *dev,
705548193cbSHans de Goede 					      struct device_attribute *attr,
706548193cbSHans de Goede 					      const char *buf, size_t count)
707548193cbSHans de Goede {
708548193cbSHans de Goede 	static const u8 test_data[] = {
709548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x01, 0x02, 0x03, 0x04,
710548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x05, 0x06, 0x07, 0x08,
711548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x10, 0x20, 0x30, 0x40,
712548193cbSHans de Goede 		0x55, 0xaa, 0x55, 0xaa, 0x50, 0x60, 0x70, 0x80
713548193cbSHans de Goede 	};
714548193cbSHans de Goede 	struct efi_embedded_fw efi_embedded_fw;
715548193cbSHans de Goede 	const struct firmware *firmware = NULL;
716baaabecfSKees Cook 	bool saved_efi_embedded_fw_checked;
717548193cbSHans de Goede 	char *name;
718548193cbSHans de Goede 	int rc;
719548193cbSHans de Goede 
720548193cbSHans de Goede 	name = kstrndup(buf, count, GFP_KERNEL);
721548193cbSHans de Goede 	if (!name)
722548193cbSHans de Goede 		return -ENOMEM;
723548193cbSHans de Goede 
724548193cbSHans de Goede 	pr_info("inserting test platform fw '%s'\n", name);
725548193cbSHans de Goede 	efi_embedded_fw.name = name;
726548193cbSHans de Goede 	efi_embedded_fw.data = (void *)test_data;
727548193cbSHans de Goede 	efi_embedded_fw.length = sizeof(test_data);
728548193cbSHans de Goede 	list_add(&efi_embedded_fw.list, &efi_embedded_fw_list);
729baaabecfSKees Cook 	saved_efi_embedded_fw_checked = efi_embedded_fw_checked;
730baaabecfSKees Cook 	efi_embedded_fw_checked = true;
731548193cbSHans de Goede 
732548193cbSHans de Goede 	pr_info("loading '%s'\n", name);
733548193cbSHans de Goede 	rc = firmware_request_platform(&firmware, name, dev);
734548193cbSHans de Goede 	if (rc) {
735548193cbSHans de Goede 		pr_info("load of '%s' failed: %d\n", name, rc);
736548193cbSHans de Goede 		goto out;
737548193cbSHans de Goede 	}
738548193cbSHans de Goede 	if (firmware->size != sizeof(test_data) ||
739548193cbSHans de Goede 	    memcmp(firmware->data, test_data, sizeof(test_data)) != 0) {
740548193cbSHans de Goede 		pr_info("firmware contents mismatch for '%s'\n", name);
741548193cbSHans de Goede 		rc = -EINVAL;
742548193cbSHans de Goede 		goto out;
743548193cbSHans de Goede 	}
744548193cbSHans de Goede 	pr_info("loaded: %zu\n", firmware->size);
745548193cbSHans de Goede 	rc = count;
746548193cbSHans de Goede 
747548193cbSHans de Goede out:
748baaabecfSKees Cook 	efi_embedded_fw_checked = saved_efi_embedded_fw_checked;
749548193cbSHans de Goede 	release_firmware(firmware);
750548193cbSHans de Goede 	list_del(&efi_embedded_fw.list);
751548193cbSHans de Goede 	kfree(name);
752548193cbSHans de Goede 
753548193cbSHans de Goede 	return rc;
754548193cbSHans de Goede }
755548193cbSHans de Goede static DEVICE_ATTR_WO(trigger_request_platform);
756548193cbSHans de Goede #endif
757548193cbSHans de Goede 
758eb910947SBrian Norris static DECLARE_COMPLETION(async_fw_done);
759eb910947SBrian Norris 
trigger_async_request_cb(const struct firmware * fw,void * context)760eb910947SBrian Norris static void trigger_async_request_cb(const struct firmware *fw, void *context)
761eb910947SBrian Norris {
762eb910947SBrian Norris 	test_firmware = fw;
763eb910947SBrian Norris 	complete(&async_fw_done);
764eb910947SBrian Norris }
765eb910947SBrian Norris 
trigger_async_request_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)766eb910947SBrian Norris static ssize_t trigger_async_request_store(struct device *dev,
767eb910947SBrian Norris 					   struct device_attribute *attr,
768eb910947SBrian Norris 					   const char *buf, size_t count)
769eb910947SBrian Norris {
770eb910947SBrian Norris 	int rc;
771eb910947SBrian Norris 	char *name;
772eb910947SBrian Norris 
773eb910947SBrian Norris 	name = kstrndup(buf, count, GFP_KERNEL);
774eb910947SBrian Norris 	if (!name)
775eb910947SBrian Norris 		return -ENOMEM;
776eb910947SBrian Norris 
777eb910947SBrian Norris 	pr_info("loading '%s'\n", name);
778eb910947SBrian Norris 
779eb910947SBrian Norris 	mutex_lock(&test_fw_mutex);
780eb910947SBrian Norris 	release_firmware(test_firmware);
781eb910947SBrian Norris 	test_firmware = NULL;
782*48e15602SMirsad Goran Todorovac 	if (test_fw_config->reqs)
783*48e15602SMirsad Goran Todorovac 		__test_release_all_firmware();
784eb910947SBrian Norris 	rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL,
785eb910947SBrian Norris 				     NULL, trigger_async_request_cb);
786eb910947SBrian Norris 	if (rc) {
787eb910947SBrian Norris 		pr_info("async load of '%s' failed: %d\n", name, rc);
788eb910947SBrian Norris 		kfree(name);
789eb910947SBrian Norris 		goto out;
790eb910947SBrian Norris 	}
791eb910947SBrian Norris 	/* Free 'name' ASAP, to test for race conditions */
792eb910947SBrian Norris 	kfree(name);
793eb910947SBrian Norris 
794eb910947SBrian Norris 	wait_for_completion(&async_fw_done);
795eb910947SBrian Norris 
796eb910947SBrian Norris 	if (test_firmware) {
797eb910947SBrian Norris 		pr_info("loaded: %zu\n", test_firmware->size);
798eb910947SBrian Norris 		rc = count;
799eb910947SBrian Norris 	} else {
800eb910947SBrian Norris 		pr_err("failed to async load firmware\n");
8017feebfa4SScott Branden 		rc = -ENOMEM;
802eb910947SBrian Norris 	}
803eb910947SBrian Norris 
804eb910947SBrian Norris out:
805eb910947SBrian Norris 	mutex_unlock(&test_fw_mutex);
806eb910947SBrian Norris 
807eb910947SBrian Norris 	return rc;
808eb910947SBrian Norris }
809eb910947SBrian Norris static DEVICE_ATTR_WO(trigger_async_request);
810eb910947SBrian Norris 
trigger_custom_fallback_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)811061132d2SLuis R. Rodriguez static ssize_t trigger_custom_fallback_store(struct device *dev,
812061132d2SLuis R. Rodriguez 					     struct device_attribute *attr,
813061132d2SLuis R. Rodriguez 					     const char *buf, size_t count)
814061132d2SLuis R. Rodriguez {
815061132d2SLuis R. Rodriguez 	int rc;
816061132d2SLuis R. Rodriguez 	char *name;
817061132d2SLuis R. Rodriguez 
818061132d2SLuis R. Rodriguez 	name = kstrndup(buf, count, GFP_KERNEL);
819061132d2SLuis R. Rodriguez 	if (!name)
820061132d2SLuis R. Rodriguez 		return -ENOMEM;
821061132d2SLuis R. Rodriguez 
822061132d2SLuis R. Rodriguez 	pr_info("loading '%s' using custom fallback mechanism\n", name);
823061132d2SLuis R. Rodriguez 
824061132d2SLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
825061132d2SLuis R. Rodriguez 	release_firmware(test_firmware);
826*48e15602SMirsad Goran Todorovac 	if (test_fw_config->reqs)
827*48e15602SMirsad Goran Todorovac 		__test_release_all_firmware();
828061132d2SLuis R. Rodriguez 	test_firmware = NULL;
8290733d839SShawn Guo 	rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOUEVENT, name,
830061132d2SLuis R. Rodriguez 				     dev, GFP_KERNEL, NULL,
831061132d2SLuis R. Rodriguez 				     trigger_async_request_cb);
832061132d2SLuis R. Rodriguez 	if (rc) {
833061132d2SLuis R. Rodriguez 		pr_info("async load of '%s' failed: %d\n", name, rc);
834061132d2SLuis R. Rodriguez 		kfree(name);
835061132d2SLuis R. Rodriguez 		goto out;
836061132d2SLuis R. Rodriguez 	}
837061132d2SLuis R. Rodriguez 	/* Free 'name' ASAP, to test for race conditions */
838061132d2SLuis R. Rodriguez 	kfree(name);
839061132d2SLuis R. Rodriguez 
840061132d2SLuis R. Rodriguez 	wait_for_completion(&async_fw_done);
841061132d2SLuis R. Rodriguez 
842061132d2SLuis R. Rodriguez 	if (test_firmware) {
843061132d2SLuis R. Rodriguez 		pr_info("loaded: %zu\n", test_firmware->size);
844061132d2SLuis R. Rodriguez 		rc = count;
845061132d2SLuis R. Rodriguez 	} else {
846061132d2SLuis R. Rodriguez 		pr_err("failed to async load firmware\n");
847061132d2SLuis R. Rodriguez 		rc = -ENODEV;
848061132d2SLuis R. Rodriguez 	}
849061132d2SLuis R. Rodriguez 
850061132d2SLuis R. Rodriguez out:
851061132d2SLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
852061132d2SLuis R. Rodriguez 
853061132d2SLuis R. Rodriguez 	return rc;
854061132d2SLuis R. Rodriguez }
855061132d2SLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_custom_fallback);
856061132d2SLuis R. Rodriguez 
test_fw_run_batch_request(void * data)857c92316bfSLuis R. Rodriguez static int test_fw_run_batch_request(void *data)
858c92316bfSLuis R. Rodriguez {
859c92316bfSLuis R. Rodriguez 	struct test_batched_req *req = data;
860c92316bfSLuis R. Rodriguez 
861c92316bfSLuis R. Rodriguez 	if (!req) {
862c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = -EINVAL;
863c92316bfSLuis R. Rodriguez 		return -EINVAL;
864c92316bfSLuis R. Rodriguez 	}
865c92316bfSLuis R. Rodriguez 
8667feebfa4SScott Branden 	if (test_fw_config->into_buf) {
8677feebfa4SScott Branden 		void *test_buf;
8687feebfa4SScott Branden 
8697feebfa4SScott Branden 		test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL);
8707feebfa4SScott Branden 		if (!test_buf)
8717feebfa4SScott Branden 			return -ENOMEM;
8727feebfa4SScott Branden 
8735d90e05cSScott Branden 		if (test_fw_config->partial)
8745d90e05cSScott Branden 			req->rc = request_partial_firmware_into_buf
8755d90e05cSScott Branden 						(&req->fw,
8767feebfa4SScott Branden 						 req->name,
8777feebfa4SScott Branden 						 req->dev,
8787feebfa4SScott Branden 						 test_buf,
8795d90e05cSScott Branden 						 test_fw_config->buf_size,
8805d90e05cSScott Branden 						 test_fw_config->file_offset);
8815d90e05cSScott Branden 		else
8825d90e05cSScott Branden 			req->rc = request_firmware_into_buf
8835d90e05cSScott Branden 						(&req->fw,
8845d90e05cSScott Branden 						 req->name,
8855d90e05cSScott Branden 						 req->dev,
8865d90e05cSScott Branden 						 test_buf,
8875d90e05cSScott Branden 						 test_fw_config->buf_size);
8887feebfa4SScott Branden 		if (!req->fw)
8897feebfa4SScott Branden 			kfree(test_buf);
890*48e15602SMirsad Goran Todorovac 		else
891*48e15602SMirsad Goran Todorovac 			req->fw_buf = test_buf;
8927feebfa4SScott Branden 	} else {
8937feebfa4SScott Branden 		req->rc = test_fw_config->req_firmware(&req->fw,
8947feebfa4SScott Branden 						       req->name,
8957feebfa4SScott Branden 						       req->dev);
8967feebfa4SScott Branden 	}
8977feebfa4SScott Branden 
898c92316bfSLuis R. Rodriguez 	if (req->rc) {
899c92316bfSLuis R. Rodriguez 		pr_info("#%u: batched sync load failed: %d\n",
900c92316bfSLuis R. Rodriguez 			req->idx, req->rc);
901c92316bfSLuis R. Rodriguez 		if (!test_fw_config->test_result)
902c92316bfSLuis R. Rodriguez 			test_fw_config->test_result = req->rc;
903c92316bfSLuis R. Rodriguez 	} else if (req->fw) {
904c92316bfSLuis R. Rodriguez 		req->sent = true;
905c92316bfSLuis R. Rodriguez 		pr_info("#%u: batched sync loaded %zu\n",
906c92316bfSLuis R. Rodriguez 			req->idx, req->fw->size);
907c92316bfSLuis R. Rodriguez 	}
908c92316bfSLuis R. Rodriguez 	complete(&req->completion);
909c92316bfSLuis R. Rodriguez 
910c92316bfSLuis R. Rodriguez 	req->task = NULL;
911c92316bfSLuis R. Rodriguez 
912c92316bfSLuis R. Rodriguez 	return 0;
913c92316bfSLuis R. Rodriguez }
914c92316bfSLuis R. Rodriguez 
915c92316bfSLuis R. Rodriguez /*
916c92316bfSLuis R. Rodriguez  * We use a kthread as otherwise the kernel serializes all our sync requests
917c92316bfSLuis R. Rodriguez  * and we would not be able to mimic batched requests on a sync call. Batched
918c92316bfSLuis R. Rodriguez  * requests on a sync call can for instance happen on a device driver when
919c92316bfSLuis R. Rodriguez  * multiple cards are used and firmware loading happens outside of probe.
920c92316bfSLuis R. Rodriguez  */
trigger_batched_requests_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)921c92316bfSLuis R. Rodriguez static ssize_t trigger_batched_requests_store(struct device *dev,
922c92316bfSLuis R. Rodriguez 					      struct device_attribute *attr,
923c92316bfSLuis R. Rodriguez 					      const char *buf, size_t count)
924c92316bfSLuis R. Rodriguez {
925c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
926c92316bfSLuis R. Rodriguez 	int rc;
927c92316bfSLuis R. Rodriguez 	u8 i;
928c92316bfSLuis R. Rodriguez 
929c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
930c92316bfSLuis R. Rodriguez 
931be37bed7SMirsad Goran Todorovac 	if (test_fw_config->reqs) {
932be37bed7SMirsad Goran Todorovac 		rc = -EBUSY;
933be37bed7SMirsad Goran Todorovac 		goto out_bail;
934be37bed7SMirsad Goran Todorovac 	}
935be37bed7SMirsad Goran Todorovac 
936fad953ceSKees Cook 	test_fw_config->reqs =
937fad953ceSKees Cook 		vzalloc(array3_size(sizeof(struct test_batched_req),
938fad953ceSKees Cook 				    test_fw_config->num_requests, 2));
939c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs) {
940c92316bfSLuis R. Rodriguez 		rc = -ENOMEM;
941c92316bfSLuis R. Rodriguez 		goto out_unlock;
942c92316bfSLuis R. Rodriguez 	}
943c92316bfSLuis R. Rodriguez 
944c92316bfSLuis R. Rodriguez 	pr_info("batched sync firmware loading '%s' %u times\n",
945c92316bfSLuis R. Rodriguez 		test_fw_config->name, test_fw_config->num_requests);
946c92316bfSLuis R. Rodriguez 
947c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
948c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
949c92316bfSLuis R. Rodriguez 		req->fw = NULL;
950c92316bfSLuis R. Rodriguez 		req->idx = i;
951c92316bfSLuis R. Rodriguez 		req->name = test_fw_config->name;
952*48e15602SMirsad Goran Todorovac 		req->fw_buf = NULL;
953c92316bfSLuis R. Rodriguez 		req->dev = dev;
954c92316bfSLuis R. Rodriguez 		init_completion(&req->completion);
955c92316bfSLuis R. Rodriguez 		req->task = kthread_run(test_fw_run_batch_request, req,
956c92316bfSLuis R. Rodriguez 					     "%s-%u", KBUILD_MODNAME, req->idx);
957c92316bfSLuis R. Rodriguez 		if (!req->task || IS_ERR(req->task)) {
958c92316bfSLuis R. Rodriguez 			pr_err("Setting up thread %u failed\n", req->idx);
959c92316bfSLuis R. Rodriguez 			req->task = NULL;
960c92316bfSLuis R. Rodriguez 			rc = -ENOMEM;
961c92316bfSLuis R. Rodriguez 			goto out_bail;
962c92316bfSLuis R. Rodriguez 		}
963c92316bfSLuis R. Rodriguez 	}
964c92316bfSLuis R. Rodriguez 
965c92316bfSLuis R. Rodriguez 	rc = count;
966c92316bfSLuis R. Rodriguez 
967c92316bfSLuis R. Rodriguez 	/*
968c92316bfSLuis R. Rodriguez 	 * We require an explicit release to enable more time and delay of
969c92316bfSLuis R. Rodriguez 	 * calling release_firmware() to improve our chances of forcing a
970c92316bfSLuis R. Rodriguez 	 * batched request. If we instead called release_firmware() right away
971c92316bfSLuis R. Rodriguez 	 * then we might miss on an opportunity of having a successful firmware
972c92316bfSLuis R. Rodriguez 	 * request pass on the opportunity to be come a batched request.
973c92316bfSLuis R. Rodriguez 	 */
974c92316bfSLuis R. Rodriguez 
975c92316bfSLuis R. Rodriguez out_bail:
976c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
977c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
978c92316bfSLuis R. Rodriguez 		if (req->task || req->sent)
979c92316bfSLuis R. Rodriguez 			wait_for_completion(&req->completion);
980c92316bfSLuis R. Rodriguez 	}
981c92316bfSLuis R. Rodriguez 
982c92316bfSLuis R. Rodriguez 	/* Override any worker error if we had a general setup error */
983c92316bfSLuis R. Rodriguez 	if (rc < 0)
984c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = rc;
985c92316bfSLuis R. Rodriguez 
986c92316bfSLuis R. Rodriguez out_unlock:
987c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
988c92316bfSLuis R. Rodriguez 
989c92316bfSLuis R. Rodriguez 	return rc;
990c92316bfSLuis R. Rodriguez }
991c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests);
992c92316bfSLuis R. Rodriguez 
993c92316bfSLuis R. Rodriguez /*
994c92316bfSLuis R. Rodriguez  * We wait for each callback to return with the lock held, no need to lock here
995c92316bfSLuis R. Rodriguez  */
trigger_batched_cb(const struct firmware * fw,void * context)996c92316bfSLuis R. Rodriguez static void trigger_batched_cb(const struct firmware *fw, void *context)
997c92316bfSLuis R. Rodriguez {
998c92316bfSLuis R. Rodriguez 	struct test_batched_req *req = context;
999c92316bfSLuis R. Rodriguez 
1000c92316bfSLuis R. Rodriguez 	if (!req) {
1001c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = -EINVAL;
1002c92316bfSLuis R. Rodriguez 		return;
1003c92316bfSLuis R. Rodriguez 	}
1004c92316bfSLuis R. Rodriguez 
1005c92316bfSLuis R. Rodriguez 	/* forces *some* batched requests to queue up */
1006c92316bfSLuis R. Rodriguez 	if (!req->idx)
1007c92316bfSLuis R. Rodriguez 		ssleep(2);
1008c92316bfSLuis R. Rodriguez 
1009c92316bfSLuis R. Rodriguez 	req->fw = fw;
1010c92316bfSLuis R. Rodriguez 
1011c92316bfSLuis R. Rodriguez 	/*
1012c92316bfSLuis R. Rodriguez 	 * Unfortunately the firmware API gives us nothing other than a null FW
1013c92316bfSLuis R. Rodriguez 	 * if the firmware was not found on async requests.  Best we can do is
1014c92316bfSLuis R. Rodriguez 	 * just assume -ENOENT. A better API would pass the actual return
1015c92316bfSLuis R. Rodriguez 	 * value to the callback.
1016c92316bfSLuis R. Rodriguez 	 */
1017c92316bfSLuis R. Rodriguez 	if (!fw && !test_fw_config->test_result)
1018c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = -ENOENT;
1019c92316bfSLuis R. Rodriguez 
1020c92316bfSLuis R. Rodriguez 	complete(&req->completion);
1021c92316bfSLuis R. Rodriguez }
1022c92316bfSLuis R. Rodriguez 
1023c92316bfSLuis R. Rodriguez static
trigger_batched_requests_async_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)1024c92316bfSLuis R. Rodriguez ssize_t trigger_batched_requests_async_store(struct device *dev,
1025c92316bfSLuis R. Rodriguez 					     struct device_attribute *attr,
1026c92316bfSLuis R. Rodriguez 					     const char *buf, size_t count)
1027c92316bfSLuis R. Rodriguez {
1028c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
1029c92316bfSLuis R. Rodriguez 	bool send_uevent;
1030c92316bfSLuis R. Rodriguez 	int rc;
1031c92316bfSLuis R. Rodriguez 	u8 i;
1032c92316bfSLuis R. Rodriguez 
1033c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
1034c92316bfSLuis R. Rodriguez 
1035be37bed7SMirsad Goran Todorovac 	if (test_fw_config->reqs) {
1036be37bed7SMirsad Goran Todorovac 		rc = -EBUSY;
1037be37bed7SMirsad Goran Todorovac 		goto out_bail;
1038be37bed7SMirsad Goran Todorovac 	}
1039be37bed7SMirsad Goran Todorovac 
1040fad953ceSKees Cook 	test_fw_config->reqs =
1041fad953ceSKees Cook 		vzalloc(array3_size(sizeof(struct test_batched_req),
1042fad953ceSKees Cook 				    test_fw_config->num_requests, 2));
1043c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs) {
1044c92316bfSLuis R. Rodriguez 		rc = -ENOMEM;
1045c92316bfSLuis R. Rodriguez 		goto out;
1046c92316bfSLuis R. Rodriguez 	}
1047c92316bfSLuis R. Rodriguez 
1048c92316bfSLuis R. Rodriguez 	pr_info("batched loading '%s' custom fallback mechanism %u times\n",
1049c92316bfSLuis R. Rodriguez 		test_fw_config->name, test_fw_config->num_requests);
1050c92316bfSLuis R. Rodriguez 
10510733d839SShawn Guo 	send_uevent = test_fw_config->send_uevent ? FW_ACTION_UEVENT :
10520733d839SShawn Guo 		FW_ACTION_NOUEVENT;
1053c92316bfSLuis R. Rodriguez 
1054c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
1055c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
1056c92316bfSLuis R. Rodriguez 		req->name = test_fw_config->name;
1057*48e15602SMirsad Goran Todorovac 		req->fw_buf = NULL;
1058c92316bfSLuis R. Rodriguez 		req->fw = NULL;
1059c92316bfSLuis R. Rodriguez 		req->idx = i;
1060c92316bfSLuis R. Rodriguez 		init_completion(&req->completion);
1061c92316bfSLuis R. Rodriguez 		rc = request_firmware_nowait(THIS_MODULE, send_uevent,
1062c92316bfSLuis R. Rodriguez 					     req->name,
1063c92316bfSLuis R. Rodriguez 					     dev, GFP_KERNEL, req,
1064c92316bfSLuis R. Rodriguez 					     trigger_batched_cb);
1065c92316bfSLuis R. Rodriguez 		if (rc) {
1066c92316bfSLuis R. Rodriguez 			pr_info("#%u: batched async load failed setup: %d\n",
1067c92316bfSLuis R. Rodriguez 				i, rc);
1068c92316bfSLuis R. Rodriguez 			req->rc = rc;
1069c92316bfSLuis R. Rodriguez 			goto out_bail;
1070c92316bfSLuis R. Rodriguez 		} else
1071c92316bfSLuis R. Rodriguez 			req->sent = true;
1072c92316bfSLuis R. Rodriguez 	}
1073c92316bfSLuis R. Rodriguez 
1074c92316bfSLuis R. Rodriguez 	rc = count;
1075c92316bfSLuis R. Rodriguez 
1076c92316bfSLuis R. Rodriguez out_bail:
1077c92316bfSLuis R. Rodriguez 
1078c92316bfSLuis R. Rodriguez 	/*
1079c92316bfSLuis R. Rodriguez 	 * We require an explicit release to enable more time and delay of
1080c92316bfSLuis R. Rodriguez 	 * calling release_firmware() to improve our chances of forcing a
1081c92316bfSLuis R. Rodriguez 	 * batched request. If we instead called release_firmware() right away
1082c92316bfSLuis R. Rodriguez 	 * then we might miss on an opportunity of having a successful firmware
1083c92316bfSLuis R. Rodriguez 	 * request pass on the opportunity to be come a batched request.
1084c92316bfSLuis R. Rodriguez 	 */
1085c92316bfSLuis R. Rodriguez 
1086c92316bfSLuis R. Rodriguez 	for (i = 0; i < test_fw_config->num_requests; i++) {
1087c92316bfSLuis R. Rodriguez 		req = &test_fw_config->reqs[i];
1088c92316bfSLuis R. Rodriguez 		if (req->sent)
1089c92316bfSLuis R. Rodriguez 			wait_for_completion(&req->completion);
1090c92316bfSLuis R. Rodriguez 	}
1091c92316bfSLuis R. Rodriguez 
1092c92316bfSLuis R. Rodriguez 	/* Override any worker error if we had a general setup error */
1093c92316bfSLuis R. Rodriguez 	if (rc < 0)
1094c92316bfSLuis R. Rodriguez 		test_fw_config->test_result = rc;
1095c92316bfSLuis R. Rodriguez 
1096c92316bfSLuis R. Rodriguez out:
1097c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
1098c92316bfSLuis R. Rodriguez 
1099c92316bfSLuis R. Rodriguez 	return rc;
1100c92316bfSLuis R. Rodriguez }
1101c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests_async);
1102c92316bfSLuis R. Rodriguez 
upload_release(struct test_firmware_upload * tst)1103a31ad463SRuss Weight static void upload_release(struct test_firmware_upload *tst)
1104a31ad463SRuss Weight {
1105a31ad463SRuss Weight 	firmware_upload_unregister(tst->fwl);
1106a31ad463SRuss Weight 	kfree(tst->buf);
1107a31ad463SRuss Weight 	kfree(tst->name);
1108a31ad463SRuss Weight 	kfree(tst);
1109a31ad463SRuss Weight }
1110a31ad463SRuss Weight 
upload_release_all(void)1111a31ad463SRuss Weight static void upload_release_all(void)
1112a31ad463SRuss Weight {
1113a31ad463SRuss Weight 	struct test_firmware_upload *tst, *tmp;
1114a31ad463SRuss Weight 
1115a31ad463SRuss Weight 	list_for_each_entry_safe(tst, tmp, &test_upload_list, node) {
1116a31ad463SRuss Weight 		list_del(&tst->node);
1117a31ad463SRuss Weight 		upload_release(tst);
1118a31ad463SRuss Weight 	}
1119a31ad463SRuss Weight 	test_fw_config->upload_name = NULL;
1120a31ad463SRuss Weight }
1121a31ad463SRuss Weight 
11224a4e975bSRuss Weight /*
11234a4e975bSRuss Weight  * This table is replicated from .../firmware_loader/sysfs_upload.c
11244a4e975bSRuss Weight  * and needs to be kept in sync.
11254a4e975bSRuss Weight  */
11264a4e975bSRuss Weight static const char * const fw_upload_err_str[] = {
11274a4e975bSRuss Weight 	[FW_UPLOAD_ERR_NONE]	     = "none",
11284a4e975bSRuss Weight 	[FW_UPLOAD_ERR_HW_ERROR]     = "hw-error",
11294a4e975bSRuss Weight 	[FW_UPLOAD_ERR_TIMEOUT]	     = "timeout",
11304a4e975bSRuss Weight 	[FW_UPLOAD_ERR_CANCELED]     = "user-abort",
11314a4e975bSRuss Weight 	[FW_UPLOAD_ERR_BUSY]	     = "device-busy",
11324a4e975bSRuss Weight 	[FW_UPLOAD_ERR_INVALID_SIZE] = "invalid-file-size",
11334a4e975bSRuss Weight 	[FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
11344a4e975bSRuss Weight 	[FW_UPLOAD_ERR_WEAROUT]	     = "flash-wearout",
11354a4e975bSRuss Weight };
11364a4e975bSRuss Weight 
upload_err_inject_error(struct test_firmware_upload * tst,const u8 * p,const char * prog)11374a4e975bSRuss Weight static void upload_err_inject_error(struct test_firmware_upload *tst,
11384a4e975bSRuss Weight 				    const u8 *p, const char *prog)
11394a4e975bSRuss Weight {
11404a4e975bSRuss Weight 	enum fw_upload_err err;
11414a4e975bSRuss Weight 
11424a4e975bSRuss Weight 	for (err = FW_UPLOAD_ERR_NONE + 1; err < FW_UPLOAD_ERR_MAX; err++) {
11434a4e975bSRuss Weight 		if (strncmp(p, fw_upload_err_str[err],
11444a4e975bSRuss Weight 			    strlen(fw_upload_err_str[err])) == 0) {
11454a4e975bSRuss Weight 			tst->inject.prog = prog;
11464a4e975bSRuss Weight 			tst->inject.err_code = err;
11474a4e975bSRuss Weight 			return;
11484a4e975bSRuss Weight 		}
11494a4e975bSRuss Weight 	}
11504a4e975bSRuss Weight }
11514a4e975bSRuss Weight 
upload_err_inject_prog(struct test_firmware_upload * tst,const u8 * p)11524a4e975bSRuss Weight static void upload_err_inject_prog(struct test_firmware_upload *tst,
11534a4e975bSRuss Weight 				   const u8 *p)
11544a4e975bSRuss Weight {
11554a4e975bSRuss Weight 	static const char * const progs[] = {
11564a4e975bSRuss Weight 		"preparing:", "transferring:", "programming:"
11574a4e975bSRuss Weight 	};
11584a4e975bSRuss Weight 	int i;
11594a4e975bSRuss Weight 
11604a4e975bSRuss Weight 	for (i = 0; i < ARRAY_SIZE(progs); i++) {
11614a4e975bSRuss Weight 		if (strncmp(p, progs[i], strlen(progs[i])) == 0) {
11624a4e975bSRuss Weight 			upload_err_inject_error(tst, p + strlen(progs[i]),
11634a4e975bSRuss Weight 						progs[i]);
11644a4e975bSRuss Weight 			return;
11654a4e975bSRuss Weight 		}
11664a4e975bSRuss Weight 	}
11674a4e975bSRuss Weight }
11684a4e975bSRuss Weight 
11694a4e975bSRuss Weight #define FIVE_MINUTES_MS	(5 * 60 * 1000)
11704a4e975bSRuss Weight static enum fw_upload_err
fw_upload_wait_on_cancel(struct test_firmware_upload * tst)11714a4e975bSRuss Weight fw_upload_wait_on_cancel(struct test_firmware_upload *tst)
11724a4e975bSRuss Weight {
11734a4e975bSRuss Weight 	int ms_delay;
11744a4e975bSRuss Weight 
11754a4e975bSRuss Weight 	for (ms_delay = 0; ms_delay < FIVE_MINUTES_MS; ms_delay += 100) {
11764a4e975bSRuss Weight 		msleep(100);
11774a4e975bSRuss Weight 		if (tst->cancel_request)
11784a4e975bSRuss Weight 			return FW_UPLOAD_ERR_CANCELED;
11794a4e975bSRuss Weight 	}
11804a4e975bSRuss Weight 	return FW_UPLOAD_ERR_NONE;
11814a4e975bSRuss Weight }
11824a4e975bSRuss Weight 
test_fw_upload_prepare(struct fw_upload * fwl,const u8 * data,u32 size)1183a31ad463SRuss Weight static enum fw_upload_err test_fw_upload_prepare(struct fw_upload *fwl,
1184a31ad463SRuss Weight 						 const u8 *data, u32 size)
1185a31ad463SRuss Weight {
1186a31ad463SRuss Weight 	struct test_firmware_upload *tst = fwl->dd_handle;
11874a4e975bSRuss Weight 	enum fw_upload_err ret = FW_UPLOAD_ERR_NONE;
11884a4e975bSRuss Weight 	const char *progress = "preparing:";
1189a31ad463SRuss Weight 
1190a31ad463SRuss Weight 	tst->cancel_request = false;
1191a31ad463SRuss Weight 
11924a4e975bSRuss Weight 	if (!size || size > TEST_UPLOAD_MAX_SIZE) {
11934a4e975bSRuss Weight 		ret = FW_UPLOAD_ERR_INVALID_SIZE;
11944a4e975bSRuss Weight 		goto err_out;
11954a4e975bSRuss Weight 	}
11964a4e975bSRuss Weight 
11974a4e975bSRuss Weight 	if (strncmp(data, "inject:", strlen("inject:")) == 0)
11984a4e975bSRuss Weight 		upload_err_inject_prog(tst, data + strlen("inject:"));
1199a31ad463SRuss Weight 
1200a31ad463SRuss Weight 	memset(tst->buf, 0, TEST_UPLOAD_MAX_SIZE);
1201a31ad463SRuss Weight 	tst->size = size;
1202a31ad463SRuss Weight 
12034a4e975bSRuss Weight 	if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
12044a4e975bSRuss Weight 	    strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
1205a31ad463SRuss Weight 		return FW_UPLOAD_ERR_NONE;
12064a4e975bSRuss Weight 
12074a4e975bSRuss Weight 	if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
12084a4e975bSRuss Weight 		ret = fw_upload_wait_on_cancel(tst);
12094a4e975bSRuss Weight 	else
12104a4e975bSRuss Weight 		ret = tst->inject.err_code;
12114a4e975bSRuss Weight 
12124a4e975bSRuss Weight err_out:
12134a4e975bSRuss Weight 	/*
12144a4e975bSRuss Weight 	 * The cleanup op only executes if the prepare op succeeds.
12154a4e975bSRuss Weight 	 * If the prepare op fails, it must do it's own clean-up.
12164a4e975bSRuss Weight 	 */
12174a4e975bSRuss Weight 	tst->inject.err_code = FW_UPLOAD_ERR_NONE;
12184a4e975bSRuss Weight 	tst->inject.prog = NULL;
12194a4e975bSRuss Weight 
12204a4e975bSRuss Weight 	return ret;
1221a31ad463SRuss Weight }
1222a31ad463SRuss Weight 
test_fw_upload_write(struct fw_upload * fwl,const u8 * data,u32 offset,u32 size,u32 * written)1223a31ad463SRuss Weight static enum fw_upload_err test_fw_upload_write(struct fw_upload *fwl,
1224a31ad463SRuss Weight 					       const u8 *data, u32 offset,
1225a31ad463SRuss Weight 					       u32 size, u32 *written)
1226a31ad463SRuss Weight {
1227a31ad463SRuss Weight 	struct test_firmware_upload *tst = fwl->dd_handle;
12284a4e975bSRuss Weight 	const char *progress = "transferring:";
1229a31ad463SRuss Weight 	u32 blk_size;
1230a31ad463SRuss Weight 
1231a31ad463SRuss Weight 	if (tst->cancel_request)
1232a31ad463SRuss Weight 		return FW_UPLOAD_ERR_CANCELED;
1233a31ad463SRuss Weight 
1234a31ad463SRuss Weight 	blk_size = min_t(u32, TEST_UPLOAD_BLK_SIZE, size);
1235a31ad463SRuss Weight 	memcpy(tst->buf + offset, data + offset, blk_size);
1236a31ad463SRuss Weight 
1237a31ad463SRuss Weight 	*written = blk_size;
12384a4e975bSRuss Weight 
12394a4e975bSRuss Weight 	if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
12404a4e975bSRuss Weight 	    strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
1241a31ad463SRuss Weight 		return FW_UPLOAD_ERR_NONE;
12424a4e975bSRuss Weight 
12434a4e975bSRuss Weight 	if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
12444a4e975bSRuss Weight 		return fw_upload_wait_on_cancel(tst);
12454a4e975bSRuss Weight 
12464a4e975bSRuss Weight 	return tst->inject.err_code;
1247a31ad463SRuss Weight }
1248a31ad463SRuss Weight 
test_fw_upload_complete(struct fw_upload * fwl)1249a31ad463SRuss Weight static enum fw_upload_err test_fw_upload_complete(struct fw_upload *fwl)
1250a31ad463SRuss Weight {
1251a31ad463SRuss Weight 	struct test_firmware_upload *tst = fwl->dd_handle;
12524a4e975bSRuss Weight 	const char *progress = "programming:";
1253a31ad463SRuss Weight 
1254a31ad463SRuss Weight 	if (tst->cancel_request)
1255a31ad463SRuss Weight 		return FW_UPLOAD_ERR_CANCELED;
1256a31ad463SRuss Weight 
12574a4e975bSRuss Weight 	if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
12584a4e975bSRuss Weight 	    strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
1259a31ad463SRuss Weight 		return FW_UPLOAD_ERR_NONE;
12604a4e975bSRuss Weight 
12614a4e975bSRuss Weight 	if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
12624a4e975bSRuss Weight 		return fw_upload_wait_on_cancel(tst);
12634a4e975bSRuss Weight 
12644a4e975bSRuss Weight 	return tst->inject.err_code;
1265a31ad463SRuss Weight }
1266a31ad463SRuss Weight 
test_fw_upload_cancel(struct fw_upload * fwl)1267a31ad463SRuss Weight static void test_fw_upload_cancel(struct fw_upload *fwl)
1268a31ad463SRuss Weight {
1269a31ad463SRuss Weight 	struct test_firmware_upload *tst = fwl->dd_handle;
1270a31ad463SRuss Weight 
1271a31ad463SRuss Weight 	tst->cancel_request = true;
1272a31ad463SRuss Weight }
1273a31ad463SRuss Weight 
test_fw_cleanup(struct fw_upload * fwl)12744a4e975bSRuss Weight static void test_fw_cleanup(struct fw_upload *fwl)
12754a4e975bSRuss Weight {
12764a4e975bSRuss Weight 	struct test_firmware_upload *tst = fwl->dd_handle;
12774a4e975bSRuss Weight 
12784a4e975bSRuss Weight 	tst->inject.err_code = FW_UPLOAD_ERR_NONE;
12794a4e975bSRuss Weight 	tst->inject.prog = NULL;
12804a4e975bSRuss Weight }
12814a4e975bSRuss Weight 
1282a31ad463SRuss Weight static const struct fw_upload_ops upload_test_ops = {
1283a31ad463SRuss Weight 	.prepare = test_fw_upload_prepare,
1284a31ad463SRuss Weight 	.write = test_fw_upload_write,
1285a31ad463SRuss Weight 	.poll_complete = test_fw_upload_complete,
1286a31ad463SRuss Weight 	.cancel = test_fw_upload_cancel,
12874a4e975bSRuss Weight 	.cleanup = test_fw_cleanup
1288a31ad463SRuss Weight };
1289a31ad463SRuss Weight 
upload_register_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)1290a31ad463SRuss Weight static ssize_t upload_register_store(struct device *dev,
1291a31ad463SRuss Weight 				     struct device_attribute *attr,
1292a31ad463SRuss Weight 				     const char *buf, size_t count)
1293a31ad463SRuss Weight {
1294a31ad463SRuss Weight 	struct test_firmware_upload *tst;
1295a31ad463SRuss Weight 	struct fw_upload *fwl;
1296a31ad463SRuss Weight 	char *name;
1297a31ad463SRuss Weight 	int ret;
1298a31ad463SRuss Weight 
1299a31ad463SRuss Weight 	name = kstrndup(buf, count, GFP_KERNEL);
1300a31ad463SRuss Weight 	if (!name)
1301a31ad463SRuss Weight 		return -ENOMEM;
1302a31ad463SRuss Weight 
1303a31ad463SRuss Weight 	mutex_lock(&test_fw_mutex);
1304a31ad463SRuss Weight 	tst = upload_lookup_name(name);
1305a31ad463SRuss Weight 	if (tst) {
1306a31ad463SRuss Weight 		ret = -EEXIST;
1307a31ad463SRuss Weight 		goto free_name;
1308a31ad463SRuss Weight 	}
1309a31ad463SRuss Weight 
1310a31ad463SRuss Weight 	tst = kzalloc(sizeof(*tst), GFP_KERNEL);
1311a31ad463SRuss Weight 	if (!tst) {
1312a31ad463SRuss Weight 		ret = -ENOMEM;
1313a31ad463SRuss Weight 		goto free_name;
1314a31ad463SRuss Weight 	}
1315a31ad463SRuss Weight 
1316a31ad463SRuss Weight 	tst->name = name;
1317a31ad463SRuss Weight 	tst->buf = kzalloc(TEST_UPLOAD_MAX_SIZE, GFP_KERNEL);
1318a31ad463SRuss Weight 	if (!tst->buf) {
1319a31ad463SRuss Weight 		ret = -ENOMEM;
1320a31ad463SRuss Weight 		goto free_tst;
1321a31ad463SRuss Weight 	}
1322a31ad463SRuss Weight 
1323a31ad463SRuss Weight 	fwl = firmware_upload_register(THIS_MODULE, dev, tst->name,
1324a31ad463SRuss Weight 				       &upload_test_ops, tst);
1325a31ad463SRuss Weight 	if (IS_ERR(fwl)) {
1326a31ad463SRuss Weight 		ret = PTR_ERR(fwl);
1327a31ad463SRuss Weight 		goto free_buf;
1328a31ad463SRuss Weight 	}
1329a31ad463SRuss Weight 
1330a31ad463SRuss Weight 	tst->fwl = fwl;
1331a31ad463SRuss Weight 	list_add_tail(&tst->node, &test_upload_list);
1332a31ad463SRuss Weight 	mutex_unlock(&test_fw_mutex);
1333a31ad463SRuss Weight 	return count;
1334a31ad463SRuss Weight 
1335a31ad463SRuss Weight free_buf:
1336a31ad463SRuss Weight 	kfree(tst->buf);
1337a31ad463SRuss Weight 
1338a31ad463SRuss Weight free_tst:
1339a31ad463SRuss Weight 	kfree(tst);
1340a31ad463SRuss Weight 
1341a31ad463SRuss Weight free_name:
1342a31ad463SRuss Weight 	mutex_unlock(&test_fw_mutex);
1343a31ad463SRuss Weight 	kfree(name);
1344a31ad463SRuss Weight 
1345a31ad463SRuss Weight 	return ret;
1346a31ad463SRuss Weight }
1347a31ad463SRuss Weight static DEVICE_ATTR_WO(upload_register);
1348a31ad463SRuss Weight 
upload_unregister_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)1349a31ad463SRuss Weight static ssize_t upload_unregister_store(struct device *dev,
1350a31ad463SRuss Weight 				       struct device_attribute *attr,
1351a31ad463SRuss Weight 				       const char *buf, size_t count)
1352a31ad463SRuss Weight {
1353a31ad463SRuss Weight 	struct test_firmware_upload *tst;
1354a31ad463SRuss Weight 	int ret = count;
1355a31ad463SRuss Weight 
1356a31ad463SRuss Weight 	mutex_lock(&test_fw_mutex);
1357a31ad463SRuss Weight 	tst = upload_lookup_name(buf);
1358a31ad463SRuss Weight 	if (!tst) {
1359a31ad463SRuss Weight 		ret = -EINVAL;
1360a31ad463SRuss Weight 		goto out;
1361a31ad463SRuss Weight 	}
1362a31ad463SRuss Weight 
1363a31ad463SRuss Weight 	if (test_fw_config->upload_name == tst->name)
1364a31ad463SRuss Weight 		test_fw_config->upload_name = NULL;
1365a31ad463SRuss Weight 
1366a31ad463SRuss Weight 	list_del(&tst->node);
1367a31ad463SRuss Weight 	upload_release(tst);
1368a31ad463SRuss Weight 
1369a31ad463SRuss Weight out:
1370a31ad463SRuss Weight 	mutex_unlock(&test_fw_mutex);
1371a31ad463SRuss Weight 	return ret;
1372a31ad463SRuss Weight }
1373a31ad463SRuss Weight static DEVICE_ATTR_WO(upload_unregister);
1374a31ad463SRuss Weight 
test_result_show(struct device * dev,struct device_attribute * attr,char * buf)1375c92316bfSLuis R. Rodriguez static ssize_t test_result_show(struct device *dev,
1376c92316bfSLuis R. Rodriguez 				struct device_attribute *attr,
1377c92316bfSLuis R. Rodriguez 				char *buf)
1378c92316bfSLuis R. Rodriguez {
1379c92316bfSLuis R. Rodriguez 	return test_dev_config_show_int(buf, test_fw_config->test_result);
1380c92316bfSLuis R. Rodriguez }
1381c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(test_result);
1382c92316bfSLuis R. Rodriguez 
release_all_firmware_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)1383c92316bfSLuis R. Rodriguez static ssize_t release_all_firmware_store(struct device *dev,
1384c92316bfSLuis R. Rodriguez 					  struct device_attribute *attr,
1385c92316bfSLuis R. Rodriguez 					  const char *buf, size_t count)
1386c92316bfSLuis R. Rodriguez {
1387c92316bfSLuis R. Rodriguez 	test_release_all_firmware();
1388c92316bfSLuis R. Rodriguez 	return count;
1389c92316bfSLuis R. Rodriguez }
1390c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(release_all_firmware);
1391c92316bfSLuis R. Rodriguez 
read_firmware_show(struct device * dev,struct device_attribute * attr,char * buf)1392c92316bfSLuis R. Rodriguez static ssize_t read_firmware_show(struct device *dev,
1393c92316bfSLuis R. Rodriguez 				  struct device_attribute *attr,
1394c92316bfSLuis R. Rodriguez 				  char *buf)
1395c92316bfSLuis R. Rodriguez {
1396c92316bfSLuis R. Rodriguez 	struct test_batched_req *req;
1397c92316bfSLuis R. Rodriguez 	u8 idx;
1398c92316bfSLuis R. Rodriguez 	ssize_t rc = 0;
1399c92316bfSLuis R. Rodriguez 
1400c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
1401c92316bfSLuis R. Rodriguez 
1402c92316bfSLuis R. Rodriguez 	idx = test_fw_config->read_fw_idx;
1403c92316bfSLuis R. Rodriguez 	if (idx >= test_fw_config->num_requests) {
1404c92316bfSLuis R. Rodriguez 		rc = -ERANGE;
1405c92316bfSLuis R. Rodriguez 		goto out;
1406c92316bfSLuis R. Rodriguez 	}
1407c92316bfSLuis R. Rodriguez 
1408c92316bfSLuis R. Rodriguez 	if (!test_fw_config->reqs) {
1409c92316bfSLuis R. Rodriguez 		rc = -EINVAL;
1410c92316bfSLuis R. Rodriguez 		goto out;
1411c92316bfSLuis R. Rodriguez 	}
1412c92316bfSLuis R. Rodriguez 
1413c92316bfSLuis R. Rodriguez 	req = &test_fw_config->reqs[idx];
1414c92316bfSLuis R. Rodriguez 	if (!req->fw) {
1415c92316bfSLuis R. Rodriguez 		pr_err("#%u: failed to async load firmware\n", idx);
1416c92316bfSLuis R. Rodriguez 		rc = -ENOENT;
1417c92316bfSLuis R. Rodriguez 		goto out;
1418c92316bfSLuis R. Rodriguez 	}
1419c92316bfSLuis R. Rodriguez 
1420c92316bfSLuis R. Rodriguez 	pr_info("#%u: loaded %zu\n", idx, req->fw->size);
1421c92316bfSLuis R. Rodriguez 
1422c92316bfSLuis R. Rodriguez 	if (req->fw->size > PAGE_SIZE) {
1423c92316bfSLuis R. Rodriguez 		pr_err("Testing interface must use PAGE_SIZE firmware for now\n");
1424c92316bfSLuis R. Rodriguez 		rc = -EINVAL;
14258bb0a886SColin Ian King 		goto out;
1426c92316bfSLuis R. Rodriguez 	}
1427c92316bfSLuis R. Rodriguez 	memcpy(buf, req->fw->data, req->fw->size);
1428c92316bfSLuis R. Rodriguez 
1429c92316bfSLuis R. Rodriguez 	rc = req->fw->size;
1430c92316bfSLuis R. Rodriguez out:
1431c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
1432c92316bfSLuis R. Rodriguez 
1433c92316bfSLuis R. Rodriguez 	return rc;
1434c92316bfSLuis R. Rodriguez }
1435c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(read_firmware);
1436c92316bfSLuis R. Rodriguez 
upload_read_show(struct device * dev,struct device_attribute * attr,char * buf)1437a31ad463SRuss Weight static ssize_t upload_read_show(struct device *dev,
1438a31ad463SRuss Weight 				struct device_attribute *attr,
1439a31ad463SRuss Weight 				char *buf)
1440a31ad463SRuss Weight {
1441185b29c6SDan Carpenter 	struct test_firmware_upload *tst = NULL;
1442185b29c6SDan Carpenter 	struct test_firmware_upload *tst_iter;
1443a31ad463SRuss Weight 	int ret = -EINVAL;
1444a31ad463SRuss Weight 
1445a31ad463SRuss Weight 	if (!test_fw_config->upload_name) {
1446a31ad463SRuss Weight 		pr_err("Set config_upload_name before using upload_read\n");
1447a31ad463SRuss Weight 		return -EINVAL;
1448a31ad463SRuss Weight 	}
1449a31ad463SRuss Weight 
1450a31ad463SRuss Weight 	mutex_lock(&test_fw_mutex);
1451185b29c6SDan Carpenter 	list_for_each_entry(tst_iter, &test_upload_list, node)
1452185b29c6SDan Carpenter 		if (tst_iter->name == test_fw_config->upload_name) {
1453185b29c6SDan Carpenter 			tst = tst_iter;
1454a31ad463SRuss Weight 			break;
1455185b29c6SDan Carpenter 		}
1456a31ad463SRuss Weight 
1457185b29c6SDan Carpenter 	if (!tst) {
1458a31ad463SRuss Weight 		pr_err("Firmware name not found: %s\n",
1459a31ad463SRuss Weight 		       test_fw_config->upload_name);
1460a31ad463SRuss Weight 		goto out;
1461a31ad463SRuss Weight 	}
1462a31ad463SRuss Weight 
1463a31ad463SRuss Weight 	if (tst->size > PAGE_SIZE) {
1464a31ad463SRuss Weight 		pr_err("Testing interface must use PAGE_SIZE firmware for now\n");
1465a31ad463SRuss Weight 		goto out;
1466a31ad463SRuss Weight 	}
1467a31ad463SRuss Weight 
1468a31ad463SRuss Weight 	memcpy(buf, tst->buf, tst->size);
1469a31ad463SRuss Weight 	ret = tst->size;
1470a31ad463SRuss Weight out:
1471a31ad463SRuss Weight 	mutex_unlock(&test_fw_mutex);
1472a31ad463SRuss Weight 	return ret;
1473a31ad463SRuss Weight }
1474a31ad463SRuss Weight static DEVICE_ATTR_RO(upload_read);
1475a31ad463SRuss Weight 
1476083a93b0SLuis R. Rodriguez #define TEST_FW_DEV_ATTR(name)          &dev_attr_##name.attr
1477083a93b0SLuis R. Rodriguez 
1478083a93b0SLuis R. Rodriguez static struct attribute *test_dev_attrs[] = {
1479c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(reset),
1480c92316bfSLuis R. Rodriguez 
1481c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config),
1482c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_name),
1483c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_num_requests),
14847feebfa4SScott Branden 	TEST_FW_DEV_ATTR(config_into_buf),
14855d90e05cSScott Branden 	TEST_FW_DEV_ATTR(config_buf_size),
14865d90e05cSScott Branden 	TEST_FW_DEV_ATTR(config_file_offset),
14875d90e05cSScott Branden 	TEST_FW_DEV_ATTR(config_partial),
1488c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_sync_direct),
1489c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_send_uevent),
1490c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(config_read_fw_idx),
1491a31ad463SRuss Weight 	TEST_FW_DEV_ATTR(config_upload_name),
1492c92316bfSLuis R. Rodriguez 
1493c92316bfSLuis R. Rodriguez 	/* These don't use the config at all - they could be ported! */
1494083a93b0SLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_request),
1495083a93b0SLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_async_request),
1496061132d2SLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_custom_fallback),
1497548193cbSHans de Goede #ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
1498548193cbSHans de Goede 	TEST_FW_DEV_ATTR(trigger_request_platform),
1499548193cbSHans de Goede #endif
1500c92316bfSLuis R. Rodriguez 
1501c92316bfSLuis R. Rodriguez 	/* These use the config and can use the test_result */
1502c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_batched_requests),
1503c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(trigger_batched_requests_async),
1504c92316bfSLuis R. Rodriguez 
1505c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(release_all_firmware),
1506c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(test_result),
1507c92316bfSLuis R. Rodriguez 	TEST_FW_DEV_ATTR(read_firmware),
1508a31ad463SRuss Weight 	TEST_FW_DEV_ATTR(upload_read),
1509a31ad463SRuss Weight 	TEST_FW_DEV_ATTR(upload_register),
1510a31ad463SRuss Weight 	TEST_FW_DEV_ATTR(upload_unregister),
1511083a93b0SLuis R. Rodriguez 	NULL,
1512083a93b0SLuis R. Rodriguez };
1513083a93b0SLuis R. Rodriguez 
1514083a93b0SLuis R. Rodriguez ATTRIBUTE_GROUPS(test_dev);
1515083a93b0SLuis R. Rodriguez 
151667fd553cSLuis R. Rodriguez static struct miscdevice test_fw_misc_device = {
151767fd553cSLuis R. Rodriguez 	.minor          = MISC_DYNAMIC_MINOR,
151867fd553cSLuis R. Rodriguez 	.name           = "test_firmware",
151967fd553cSLuis R. Rodriguez 	.fops           = &test_fw_fops,
1520083a93b0SLuis R. Rodriguez 	.groups 	= test_dev_groups,
152167fd553cSLuis R. Rodriguez };
152267fd553cSLuis R. Rodriguez 
test_firmware_init(void)15230a8adf58SKees Cook static int __init test_firmware_init(void)
15240a8adf58SKees Cook {
15250a8adf58SKees Cook 	int rc;
15260a8adf58SKees Cook 
1527c92316bfSLuis R. Rodriguez 	test_fw_config = kzalloc(sizeof(struct test_config), GFP_KERNEL);
1528c92316bfSLuis R. Rodriguez 	if (!test_fw_config)
1529c92316bfSLuis R. Rodriguez 		return -ENOMEM;
1530c92316bfSLuis R. Rodriguez 
1531c92316bfSLuis R. Rodriguez 	rc = __test_firmware_config_init();
1532d4fddac5SWenwen Wang 	if (rc) {
1533d4fddac5SWenwen Wang 		kfree(test_fw_config);
1534d4fddac5SWenwen Wang 		pr_err("could not init firmware test config: %d\n", rc);
1535c92316bfSLuis R. Rodriguez 		return rc;
1536d4fddac5SWenwen Wang 	}
1537c92316bfSLuis R. Rodriguez 
15380a8adf58SKees Cook 	rc = misc_register(&test_fw_misc_device);
15390a8adf58SKees Cook 	if (rc) {
15407610615eSZhengchao Shao 		__test_firmware_config_free();
1541c92316bfSLuis R. Rodriguez 		kfree(test_fw_config);
15420a8adf58SKees Cook 		pr_err("could not register misc device: %d\n", rc);
15430a8adf58SKees Cook 		return rc;
15440a8adf58SKees Cook 	}
1545eb910947SBrian Norris 
15460a8adf58SKees Cook 	pr_warn("interface ready\n");
15470a8adf58SKees Cook 
15480a8adf58SKees Cook 	return 0;
15490a8adf58SKees Cook }
15500a8adf58SKees Cook 
15510a8adf58SKees Cook module_init(test_firmware_init);
15520a8adf58SKees Cook 
test_firmware_exit(void)15530a8adf58SKees Cook static void __exit test_firmware_exit(void)
15540a8adf58SKees Cook {
1555c92316bfSLuis R. Rodriguez 	mutex_lock(&test_fw_mutex);
15560a8adf58SKees Cook 	release_firmware(test_firmware);
15570a8adf58SKees Cook 	misc_deregister(&test_fw_misc_device);
1558a31ad463SRuss Weight 	upload_release_all();
1559c92316bfSLuis R. Rodriguez 	__test_firmware_config_free();
1560c92316bfSLuis R. Rodriguez 	kfree(test_fw_config);
1561c92316bfSLuis R. Rodriguez 	mutex_unlock(&test_fw_mutex);
1562c92316bfSLuis R. Rodriguez 
15630a8adf58SKees Cook 	pr_warn("removed interface\n");
15640a8adf58SKees Cook }
15650a8adf58SKees Cook 
15660a8adf58SKees Cook module_exit(test_firmware_exit);
15670a8adf58SKees Cook 
15680a8adf58SKees Cook MODULE_AUTHOR("Kees Cook <keescook@chromium.org>");
15690a8adf58SKees Cook MODULE_LICENSE("GPL");
1570