162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * This module provides an interface to trigger and test firmware loading.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * It is designed to be used for basic evaluation of the firmware loading
662306a36Sopenharmony_ci * subsystem (for example when validating firmware verification). It lacks
762306a36Sopenharmony_ci * any extra dependencies, and will not normally be loaded by the system
862306a36Sopenharmony_ci * unless explicitly requested by name.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/printk.h>
1662306a36Sopenharmony_ci#include <linux/completion.h>
1762306a36Sopenharmony_ci#include <linux/firmware.h>
1862306a36Sopenharmony_ci#include <linux/device.h>
1962306a36Sopenharmony_ci#include <linux/fs.h>
2062306a36Sopenharmony_ci#include <linux/miscdevice.h>
2162306a36Sopenharmony_ci#include <linux/sizes.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/uaccess.h>
2462306a36Sopenharmony_ci#include <linux/delay.h>
2562306a36Sopenharmony_ci#include <linux/kstrtox.h>
2662306a36Sopenharmony_ci#include <linux/kthread.h>
2762306a36Sopenharmony_ci#include <linux/vmalloc.h>
2862306a36Sopenharmony_ci#include <linux/efi_embedded_fw.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ciMODULE_IMPORT_NS(TEST_FIRMWARE);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define TEST_FIRMWARE_NAME	"test-firmware.bin"
3362306a36Sopenharmony_ci#define TEST_FIRMWARE_NUM_REQS	4
3462306a36Sopenharmony_ci#define TEST_FIRMWARE_BUF_SIZE	SZ_1K
3562306a36Sopenharmony_ci#define TEST_UPLOAD_MAX_SIZE	SZ_2K
3662306a36Sopenharmony_ci#define TEST_UPLOAD_BLK_SIZE	37	/* Avoid powers of two in testing */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic DEFINE_MUTEX(test_fw_mutex);
3962306a36Sopenharmony_cistatic const struct firmware *test_firmware;
4062306a36Sopenharmony_cistatic LIST_HEAD(test_upload_list);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct test_batched_req {
4362306a36Sopenharmony_ci	u8 idx;
4462306a36Sopenharmony_ci	int rc;
4562306a36Sopenharmony_ci	bool sent;
4662306a36Sopenharmony_ci	const struct firmware *fw;
4762306a36Sopenharmony_ci	const char *name;
4862306a36Sopenharmony_ci	const char *fw_buf;
4962306a36Sopenharmony_ci	struct completion completion;
5062306a36Sopenharmony_ci	struct task_struct *task;
5162306a36Sopenharmony_ci	struct device *dev;
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/**
5562306a36Sopenharmony_ci * struct test_config - represents configuration for the test for different triggers
5662306a36Sopenharmony_ci *
5762306a36Sopenharmony_ci * @name: the name of the firmware file to look for
5862306a36Sopenharmony_ci * @into_buf: when the into_buf is used if this is true
5962306a36Sopenharmony_ci *	request_firmware_into_buf() will be used instead.
6062306a36Sopenharmony_ci * @buf_size: size of buf to allocate when into_buf is true
6162306a36Sopenharmony_ci * @file_offset: file offset to request when calling request_firmware_into_buf
6262306a36Sopenharmony_ci * @partial: partial read opt when calling request_firmware_into_buf
6362306a36Sopenharmony_ci * @sync_direct: when the sync trigger is used if this is true
6462306a36Sopenharmony_ci *	request_firmware_direct() will be used instead.
6562306a36Sopenharmony_ci * @send_uevent: whether or not to send a uevent for async requests
6662306a36Sopenharmony_ci * @num_requests: number of requests to try per test case. This is trigger
6762306a36Sopenharmony_ci *	specific.
6862306a36Sopenharmony_ci * @reqs: stores all requests information
6962306a36Sopenharmony_ci * @read_fw_idx: index of thread from which we want to read firmware results
7062306a36Sopenharmony_ci *	from through the read_fw trigger.
7162306a36Sopenharmony_ci * @upload_name: firmware name to be used with upload_read sysfs node
7262306a36Sopenharmony_ci * @test_result: a test may use this to collect the result from the call
7362306a36Sopenharmony_ci *	of the request_firmware*() calls used in their tests. In order of
7462306a36Sopenharmony_ci *	priority we always keep first any setup error. If no setup errors were
7562306a36Sopenharmony_ci *	found then we move on to the first error encountered while running the
7662306a36Sopenharmony_ci *	API. Note that for async calls this typically will be a successful
7762306a36Sopenharmony_ci *	result (0) unless of course you've used bogus parameters, or the system
7862306a36Sopenharmony_ci *	is out of memory.  In the async case the callback is expected to do a
7962306a36Sopenharmony_ci *	bit more homework to figure out what happened, unfortunately the only
8062306a36Sopenharmony_ci *	information passed today on error is the fact that no firmware was
8162306a36Sopenharmony_ci *	found so we can only assume -ENOENT on async calls if the firmware is
8262306a36Sopenharmony_ci *	NULL.
8362306a36Sopenharmony_ci *
8462306a36Sopenharmony_ci *	Errors you can expect:
8562306a36Sopenharmony_ci *
8662306a36Sopenharmony_ci *	API specific:
8762306a36Sopenharmony_ci *
8862306a36Sopenharmony_ci *	0:		success for sync, for async it means request was sent
8962306a36Sopenharmony_ci *	-EINVAL:	invalid parameters or request
9062306a36Sopenharmony_ci *	-ENOENT:	files not found
9162306a36Sopenharmony_ci *
9262306a36Sopenharmony_ci *	System environment:
9362306a36Sopenharmony_ci *
9462306a36Sopenharmony_ci *	-ENOMEM:	memory pressure on system
9562306a36Sopenharmony_ci *	-ENODEV:	out of number of devices to test
9662306a36Sopenharmony_ci *	-EINVAL:	an unexpected error has occurred
9762306a36Sopenharmony_ci * @req_firmware: if @sync_direct is true this is set to
9862306a36Sopenharmony_ci *	request_firmware_direct(), otherwise request_firmware()
9962306a36Sopenharmony_ci */
10062306a36Sopenharmony_cistruct test_config {
10162306a36Sopenharmony_ci	char *name;
10262306a36Sopenharmony_ci	bool into_buf;
10362306a36Sopenharmony_ci	size_t buf_size;
10462306a36Sopenharmony_ci	size_t file_offset;
10562306a36Sopenharmony_ci	bool partial;
10662306a36Sopenharmony_ci	bool sync_direct;
10762306a36Sopenharmony_ci	bool send_uevent;
10862306a36Sopenharmony_ci	u8 num_requests;
10962306a36Sopenharmony_ci	u8 read_fw_idx;
11062306a36Sopenharmony_ci	char *upload_name;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/*
11362306a36Sopenharmony_ci	 * These below don't belong her but we'll move them once we create
11462306a36Sopenharmony_ci	 * a struct fw_test_device and stuff the misc_dev under there later.
11562306a36Sopenharmony_ci	 */
11662306a36Sopenharmony_ci	struct test_batched_req *reqs;
11762306a36Sopenharmony_ci	int test_result;
11862306a36Sopenharmony_ci	int (*req_firmware)(const struct firmware **fw, const char *name,
11962306a36Sopenharmony_ci			    struct device *device);
12062306a36Sopenharmony_ci};
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistruct upload_inject_err {
12362306a36Sopenharmony_ci	const char *prog;
12462306a36Sopenharmony_ci	enum fw_upload_err err_code;
12562306a36Sopenharmony_ci};
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistruct test_firmware_upload {
12862306a36Sopenharmony_ci	char *name;
12962306a36Sopenharmony_ci	struct list_head node;
13062306a36Sopenharmony_ci	char *buf;
13162306a36Sopenharmony_ci	size_t size;
13262306a36Sopenharmony_ci	bool cancel_request;
13362306a36Sopenharmony_ci	struct upload_inject_err inject;
13462306a36Sopenharmony_ci	struct fw_upload *fwl;
13562306a36Sopenharmony_ci};
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic struct test_config *test_fw_config;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic struct test_firmware_upload *upload_lookup_name(const char *name)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct test_firmware_upload *tst;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	list_for_each_entry(tst, &test_upload_list, node)
14462306a36Sopenharmony_ci		if (strncmp(name, tst->name, strlen(tst->name)) == 0)
14562306a36Sopenharmony_ci			return tst;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return NULL;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic ssize_t test_fw_misc_read(struct file *f, char __user *buf,
15162306a36Sopenharmony_ci				 size_t size, loff_t *offset)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	ssize_t rc = 0;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
15662306a36Sopenharmony_ci	if (test_firmware)
15762306a36Sopenharmony_ci		rc = simple_read_from_buffer(buf, size, offset,
15862306a36Sopenharmony_ci					     test_firmware->data,
15962306a36Sopenharmony_ci					     test_firmware->size);
16062306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
16162306a36Sopenharmony_ci	return rc;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic const struct file_operations test_fw_fops = {
16562306a36Sopenharmony_ci	.owner          = THIS_MODULE,
16662306a36Sopenharmony_ci	.read           = test_fw_misc_read,
16762306a36Sopenharmony_ci};
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic void __test_release_all_firmware(void)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct test_batched_req *req;
17262306a36Sopenharmony_ci	u8 i;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	if (!test_fw_config->reqs)
17562306a36Sopenharmony_ci		return;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	for (i = 0; i < test_fw_config->num_requests; i++) {
17862306a36Sopenharmony_ci		req = &test_fw_config->reqs[i];
17962306a36Sopenharmony_ci		if (req->fw) {
18062306a36Sopenharmony_ci			if (req->fw_buf) {
18162306a36Sopenharmony_ci				kfree_const(req->fw_buf);
18262306a36Sopenharmony_ci				req->fw_buf = NULL;
18362306a36Sopenharmony_ci			}
18462306a36Sopenharmony_ci			release_firmware(req->fw);
18562306a36Sopenharmony_ci			req->fw = NULL;
18662306a36Sopenharmony_ci		}
18762306a36Sopenharmony_ci	}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	vfree(test_fw_config->reqs);
19062306a36Sopenharmony_ci	test_fw_config->reqs = NULL;
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic void test_release_all_firmware(void)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
19662306a36Sopenharmony_ci	__test_release_all_firmware();
19762306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic void __test_firmware_config_free(void)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	__test_release_all_firmware();
20462306a36Sopenharmony_ci	kfree_const(test_fw_config->name);
20562306a36Sopenharmony_ci	test_fw_config->name = NULL;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci/*
20962306a36Sopenharmony_ci * XXX: move to kstrncpy() once merged.
21062306a36Sopenharmony_ci *
21162306a36Sopenharmony_ci * Users should use kfree_const() when freeing these.
21262306a36Sopenharmony_ci */
21362306a36Sopenharmony_cistatic int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	*dst = kstrndup(name, count, gfp);
21662306a36Sopenharmony_ci	if (!*dst)
21762306a36Sopenharmony_ci		return -ENOMEM;
21862306a36Sopenharmony_ci	return count;
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic int __test_firmware_config_init(void)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	int ret;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	ret = __kstrncpy(&test_fw_config->name, TEST_FIRMWARE_NAME,
22662306a36Sopenharmony_ci			 strlen(TEST_FIRMWARE_NAME), GFP_KERNEL);
22762306a36Sopenharmony_ci	if (ret < 0)
22862306a36Sopenharmony_ci		goto out;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS;
23162306a36Sopenharmony_ci	test_fw_config->send_uevent = true;
23262306a36Sopenharmony_ci	test_fw_config->into_buf = false;
23362306a36Sopenharmony_ci	test_fw_config->buf_size = TEST_FIRMWARE_BUF_SIZE;
23462306a36Sopenharmony_ci	test_fw_config->file_offset = 0;
23562306a36Sopenharmony_ci	test_fw_config->partial = false;
23662306a36Sopenharmony_ci	test_fw_config->sync_direct = false;
23762306a36Sopenharmony_ci	test_fw_config->req_firmware = request_firmware;
23862306a36Sopenharmony_ci	test_fw_config->test_result = 0;
23962306a36Sopenharmony_ci	test_fw_config->reqs = NULL;
24062306a36Sopenharmony_ci	test_fw_config->upload_name = NULL;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return 0;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ciout:
24562306a36Sopenharmony_ci	__test_firmware_config_free();
24662306a36Sopenharmony_ci	return ret;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic ssize_t reset_store(struct device *dev,
25062306a36Sopenharmony_ci			   struct device_attribute *attr,
25162306a36Sopenharmony_ci			   const char *buf, size_t count)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	int ret;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	__test_firmware_config_free();
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	ret = __test_firmware_config_init();
26062306a36Sopenharmony_ci	if (ret < 0) {
26162306a36Sopenharmony_ci		ret = -ENOMEM;
26262306a36Sopenharmony_ci		pr_err("could not alloc settings for config trigger: %d\n",
26362306a36Sopenharmony_ci		       ret);
26462306a36Sopenharmony_ci		goto out;
26562306a36Sopenharmony_ci	}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	pr_info("reset\n");
26862306a36Sopenharmony_ci	ret = count;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ciout:
27162306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	return ret;
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_cistatic DEVICE_ATTR_WO(reset);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic ssize_t config_show(struct device *dev,
27862306a36Sopenharmony_ci			   struct device_attribute *attr,
27962306a36Sopenharmony_ci			   char *buf)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	int len = 0;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	len += scnprintf(buf, PAGE_SIZE - len,
28662306a36Sopenharmony_ci			"Custom trigger configuration for: %s\n",
28762306a36Sopenharmony_ci			dev_name(dev));
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	if (test_fw_config->name)
29062306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len,
29162306a36Sopenharmony_ci				"name:\t%s\n",
29262306a36Sopenharmony_ci				test_fw_config->name);
29362306a36Sopenharmony_ci	else
29462306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len,
29562306a36Sopenharmony_ci				"name:\tEMPTY\n");
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
29862306a36Sopenharmony_ci			"num_requests:\t%u\n", test_fw_config->num_requests);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
30162306a36Sopenharmony_ci			"send_uevent:\t\t%s\n",
30262306a36Sopenharmony_ci			test_fw_config->send_uevent ?
30362306a36Sopenharmony_ci			"FW_ACTION_UEVENT" :
30462306a36Sopenharmony_ci			"FW_ACTION_NOUEVENT");
30562306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
30662306a36Sopenharmony_ci			"into_buf:\t\t%s\n",
30762306a36Sopenharmony_ci			test_fw_config->into_buf ? "true" : "false");
30862306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
30962306a36Sopenharmony_ci			"buf_size:\t%zu\n", test_fw_config->buf_size);
31062306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
31162306a36Sopenharmony_ci			"file_offset:\t%zu\n", test_fw_config->file_offset);
31262306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
31362306a36Sopenharmony_ci			"partial:\t\t%s\n",
31462306a36Sopenharmony_ci			test_fw_config->partial ? "true" : "false");
31562306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
31662306a36Sopenharmony_ci			"sync_direct:\t\t%s\n",
31762306a36Sopenharmony_ci			test_fw_config->sync_direct ? "true" : "false");
31862306a36Sopenharmony_ci	len += scnprintf(buf + len, PAGE_SIZE - len,
31962306a36Sopenharmony_ci			"read_fw_idx:\t%u\n", test_fw_config->read_fw_idx);
32062306a36Sopenharmony_ci	if (test_fw_config->upload_name)
32162306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len,
32262306a36Sopenharmony_ci				"upload_name:\t%s\n",
32362306a36Sopenharmony_ci				test_fw_config->upload_name);
32462306a36Sopenharmony_ci	else
32562306a36Sopenharmony_ci		len += scnprintf(buf + len, PAGE_SIZE - len,
32662306a36Sopenharmony_ci				"upload_name:\tEMPTY\n");
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	return len;
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(config);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic ssize_t config_name_store(struct device *dev,
33562306a36Sopenharmony_ci				 struct device_attribute *attr,
33662306a36Sopenharmony_ci				 const char *buf, size_t count)
33762306a36Sopenharmony_ci{
33862306a36Sopenharmony_ci	int ret;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
34162306a36Sopenharmony_ci	kfree_const(test_fw_config->name);
34262306a36Sopenharmony_ci	ret = __kstrncpy(&test_fw_config->name, buf, count, GFP_KERNEL);
34362306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	return ret;
34662306a36Sopenharmony_ci}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci/*
34962306a36Sopenharmony_ci * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE.
35062306a36Sopenharmony_ci */
35162306a36Sopenharmony_cistatic ssize_t config_test_show_str(char *dst,
35262306a36Sopenharmony_ci				    char *src)
35362306a36Sopenharmony_ci{
35462306a36Sopenharmony_ci	int len;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
35762306a36Sopenharmony_ci	len = snprintf(dst, PAGE_SIZE, "%s\n", src);
35862306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	return len;
36162306a36Sopenharmony_ci}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_cistatic inline int __test_dev_config_update_bool(const char *buf, size_t size,
36462306a36Sopenharmony_ci				       bool *cfg)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	int ret;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	if (kstrtobool(buf, cfg) < 0)
36962306a36Sopenharmony_ci		ret = -EINVAL;
37062306a36Sopenharmony_ci	else
37162306a36Sopenharmony_ci		ret = size;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	return ret;
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic int test_dev_config_update_bool(const char *buf, size_t size,
37762306a36Sopenharmony_ci				       bool *cfg)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	int ret;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
38262306a36Sopenharmony_ci	ret = __test_dev_config_update_bool(buf, size, cfg);
38362306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	return ret;
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic ssize_t test_dev_config_show_bool(char *buf, bool val)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%d\n", val);
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic int __test_dev_config_update_size_t(
39462306a36Sopenharmony_ci					 const char *buf,
39562306a36Sopenharmony_ci					 size_t size,
39662306a36Sopenharmony_ci					 size_t *cfg)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	int ret;
39962306a36Sopenharmony_ci	long new;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	ret = kstrtol(buf, 10, &new);
40262306a36Sopenharmony_ci	if (ret)
40362306a36Sopenharmony_ci		return ret;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	*(size_t *)cfg = new;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	/* Always return full write size even if we didn't consume all */
40862306a36Sopenharmony_ci	return size;
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic ssize_t test_dev_config_show_size_t(char *buf, size_t val)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%zu\n", val);
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic ssize_t test_dev_config_show_int(char *buf, int val)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%d\n", val);
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_cistatic int __test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg)
42262306a36Sopenharmony_ci{
42362306a36Sopenharmony_ci	u8 val;
42462306a36Sopenharmony_ci	int ret;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	ret = kstrtou8(buf, 10, &val);
42762306a36Sopenharmony_ci	if (ret)
42862306a36Sopenharmony_ci		return ret;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	*(u8 *)cfg = val;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	/* Always return full write size even if we didn't consume all */
43362306a36Sopenharmony_ci	return size;
43462306a36Sopenharmony_ci}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_cistatic int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg)
43762306a36Sopenharmony_ci{
43862306a36Sopenharmony_ci	int ret;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
44162306a36Sopenharmony_ci	ret = __test_dev_config_update_u8(buf, size, cfg);
44262306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	return ret;
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_cistatic ssize_t test_dev_config_show_u8(char *buf, u8 val)
44862306a36Sopenharmony_ci{
44962306a36Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%u\n", val);
45062306a36Sopenharmony_ci}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_cistatic ssize_t config_name_show(struct device *dev,
45362306a36Sopenharmony_ci				struct device_attribute *attr,
45462306a36Sopenharmony_ci				char *buf)
45562306a36Sopenharmony_ci{
45662306a36Sopenharmony_ci	return config_test_show_str(buf, test_fw_config->name);
45762306a36Sopenharmony_ci}
45862306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_name);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_cistatic ssize_t config_upload_name_store(struct device *dev,
46162306a36Sopenharmony_ci					struct device_attribute *attr,
46262306a36Sopenharmony_ci					const char *buf, size_t count)
46362306a36Sopenharmony_ci{
46462306a36Sopenharmony_ci	struct test_firmware_upload *tst;
46562306a36Sopenharmony_ci	int ret = count;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
46862306a36Sopenharmony_ci	tst = upload_lookup_name(buf);
46962306a36Sopenharmony_ci	if (tst)
47062306a36Sopenharmony_ci		test_fw_config->upload_name = tst->name;
47162306a36Sopenharmony_ci	else
47262306a36Sopenharmony_ci		ret = -EINVAL;
47362306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	return ret;
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_cistatic ssize_t config_upload_name_show(struct device *dev,
47962306a36Sopenharmony_ci				       struct device_attribute *attr,
48062306a36Sopenharmony_ci				       char *buf)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	return config_test_show_str(buf, test_fw_config->upload_name);
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_upload_name);
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_cistatic ssize_t config_num_requests_store(struct device *dev,
48762306a36Sopenharmony_ci					 struct device_attribute *attr,
48862306a36Sopenharmony_ci					 const char *buf, size_t count)
48962306a36Sopenharmony_ci{
49062306a36Sopenharmony_ci	int rc;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
49362306a36Sopenharmony_ci	if (test_fw_config->reqs) {
49462306a36Sopenharmony_ci		pr_err("Must call release_all_firmware prior to changing config\n");
49562306a36Sopenharmony_ci		rc = -EINVAL;
49662306a36Sopenharmony_ci		mutex_unlock(&test_fw_mutex);
49762306a36Sopenharmony_ci		goto out;
49862306a36Sopenharmony_ci	}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	rc = __test_dev_config_update_u8(buf, count,
50162306a36Sopenharmony_ci					 &test_fw_config->num_requests);
50262306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ciout:
50562306a36Sopenharmony_ci	return rc;
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic ssize_t config_num_requests_show(struct device *dev,
50962306a36Sopenharmony_ci					struct device_attribute *attr,
51062306a36Sopenharmony_ci					char *buf)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	return test_dev_config_show_u8(buf, test_fw_config->num_requests);
51362306a36Sopenharmony_ci}
51462306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_num_requests);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic ssize_t config_into_buf_store(struct device *dev,
51762306a36Sopenharmony_ci				     struct device_attribute *attr,
51862306a36Sopenharmony_ci				     const char *buf, size_t count)
51962306a36Sopenharmony_ci{
52062306a36Sopenharmony_ci	return test_dev_config_update_bool(buf,
52162306a36Sopenharmony_ci					   count,
52262306a36Sopenharmony_ci					   &test_fw_config->into_buf);
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic ssize_t config_into_buf_show(struct device *dev,
52662306a36Sopenharmony_ci				    struct device_attribute *attr,
52762306a36Sopenharmony_ci				    char *buf)
52862306a36Sopenharmony_ci{
52962306a36Sopenharmony_ci	return test_dev_config_show_bool(buf, test_fw_config->into_buf);
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_into_buf);
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_cistatic ssize_t config_buf_size_store(struct device *dev,
53462306a36Sopenharmony_ci				     struct device_attribute *attr,
53562306a36Sopenharmony_ci				     const char *buf, size_t count)
53662306a36Sopenharmony_ci{
53762306a36Sopenharmony_ci	int rc;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
54062306a36Sopenharmony_ci	if (test_fw_config->reqs) {
54162306a36Sopenharmony_ci		pr_err("Must call release_all_firmware prior to changing config\n");
54262306a36Sopenharmony_ci		rc = -EINVAL;
54362306a36Sopenharmony_ci		mutex_unlock(&test_fw_mutex);
54462306a36Sopenharmony_ci		goto out;
54562306a36Sopenharmony_ci	}
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	rc = __test_dev_config_update_size_t(buf, count,
54862306a36Sopenharmony_ci					     &test_fw_config->buf_size);
54962306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ciout:
55262306a36Sopenharmony_ci	return rc;
55362306a36Sopenharmony_ci}
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_cistatic ssize_t config_buf_size_show(struct device *dev,
55662306a36Sopenharmony_ci				    struct device_attribute *attr,
55762306a36Sopenharmony_ci				    char *buf)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	return test_dev_config_show_size_t(buf, test_fw_config->buf_size);
56062306a36Sopenharmony_ci}
56162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_buf_size);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_cistatic ssize_t config_file_offset_store(struct device *dev,
56462306a36Sopenharmony_ci					struct device_attribute *attr,
56562306a36Sopenharmony_ci					const char *buf, size_t count)
56662306a36Sopenharmony_ci{
56762306a36Sopenharmony_ci	int rc;
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
57062306a36Sopenharmony_ci	if (test_fw_config->reqs) {
57162306a36Sopenharmony_ci		pr_err("Must call release_all_firmware prior to changing config\n");
57262306a36Sopenharmony_ci		rc = -EINVAL;
57362306a36Sopenharmony_ci		mutex_unlock(&test_fw_mutex);
57462306a36Sopenharmony_ci		goto out;
57562306a36Sopenharmony_ci	}
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	rc = __test_dev_config_update_size_t(buf, count,
57862306a36Sopenharmony_ci					     &test_fw_config->file_offset);
57962306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ciout:
58262306a36Sopenharmony_ci	return rc;
58362306a36Sopenharmony_ci}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_cistatic ssize_t config_file_offset_show(struct device *dev,
58662306a36Sopenharmony_ci				       struct device_attribute *attr,
58762306a36Sopenharmony_ci				       char *buf)
58862306a36Sopenharmony_ci{
58962306a36Sopenharmony_ci	return test_dev_config_show_size_t(buf, test_fw_config->file_offset);
59062306a36Sopenharmony_ci}
59162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_file_offset);
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_cistatic ssize_t config_partial_store(struct device *dev,
59462306a36Sopenharmony_ci				    struct device_attribute *attr,
59562306a36Sopenharmony_ci				    const char *buf, size_t count)
59662306a36Sopenharmony_ci{
59762306a36Sopenharmony_ci	return test_dev_config_update_bool(buf,
59862306a36Sopenharmony_ci					   count,
59962306a36Sopenharmony_ci					   &test_fw_config->partial);
60062306a36Sopenharmony_ci}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_cistatic ssize_t config_partial_show(struct device *dev,
60362306a36Sopenharmony_ci				   struct device_attribute *attr,
60462306a36Sopenharmony_ci				   char *buf)
60562306a36Sopenharmony_ci{
60662306a36Sopenharmony_ci	return test_dev_config_show_bool(buf, test_fw_config->partial);
60762306a36Sopenharmony_ci}
60862306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_partial);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_cistatic ssize_t config_sync_direct_store(struct device *dev,
61162306a36Sopenharmony_ci					struct device_attribute *attr,
61262306a36Sopenharmony_ci					const char *buf, size_t count)
61362306a36Sopenharmony_ci{
61462306a36Sopenharmony_ci	int rc = test_dev_config_update_bool(buf, count,
61562306a36Sopenharmony_ci					     &test_fw_config->sync_direct);
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	if (rc == count)
61862306a36Sopenharmony_ci		test_fw_config->req_firmware = test_fw_config->sync_direct ?
61962306a36Sopenharmony_ci				       request_firmware_direct :
62062306a36Sopenharmony_ci				       request_firmware;
62162306a36Sopenharmony_ci	return rc;
62262306a36Sopenharmony_ci}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_cistatic ssize_t config_sync_direct_show(struct device *dev,
62562306a36Sopenharmony_ci				       struct device_attribute *attr,
62662306a36Sopenharmony_ci				       char *buf)
62762306a36Sopenharmony_ci{
62862306a36Sopenharmony_ci	return test_dev_config_show_bool(buf, test_fw_config->sync_direct);
62962306a36Sopenharmony_ci}
63062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_sync_direct);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic ssize_t config_send_uevent_store(struct device *dev,
63362306a36Sopenharmony_ci					struct device_attribute *attr,
63462306a36Sopenharmony_ci					const char *buf, size_t count)
63562306a36Sopenharmony_ci{
63662306a36Sopenharmony_ci	return test_dev_config_update_bool(buf, count,
63762306a36Sopenharmony_ci					   &test_fw_config->send_uevent);
63862306a36Sopenharmony_ci}
63962306a36Sopenharmony_ci
64062306a36Sopenharmony_cistatic ssize_t config_send_uevent_show(struct device *dev,
64162306a36Sopenharmony_ci				       struct device_attribute *attr,
64262306a36Sopenharmony_ci				       char *buf)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	return test_dev_config_show_bool(buf, test_fw_config->send_uevent);
64562306a36Sopenharmony_ci}
64662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_send_uevent);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_cistatic ssize_t config_read_fw_idx_store(struct device *dev,
64962306a36Sopenharmony_ci					struct device_attribute *attr,
65062306a36Sopenharmony_ci					const char *buf, size_t count)
65162306a36Sopenharmony_ci{
65262306a36Sopenharmony_ci	return test_dev_config_update_u8(buf, count,
65362306a36Sopenharmony_ci					 &test_fw_config->read_fw_idx);
65462306a36Sopenharmony_ci}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_cistatic ssize_t config_read_fw_idx_show(struct device *dev,
65762306a36Sopenharmony_ci				       struct device_attribute *attr,
65862306a36Sopenharmony_ci				       char *buf)
65962306a36Sopenharmony_ci{
66062306a36Sopenharmony_ci	return test_dev_config_show_u8(buf, test_fw_config->read_fw_idx);
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(config_read_fw_idx);
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_cistatic ssize_t trigger_request_store(struct device *dev,
66662306a36Sopenharmony_ci				     struct device_attribute *attr,
66762306a36Sopenharmony_ci				     const char *buf, size_t count)
66862306a36Sopenharmony_ci{
66962306a36Sopenharmony_ci	int rc;
67062306a36Sopenharmony_ci	char *name;
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci	name = kstrndup(buf, count, GFP_KERNEL);
67362306a36Sopenharmony_ci	if (!name)
67462306a36Sopenharmony_ci		return -ENOMEM;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	pr_info("loading '%s'\n", name);
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
67962306a36Sopenharmony_ci	release_firmware(test_firmware);
68062306a36Sopenharmony_ci	if (test_fw_config->reqs)
68162306a36Sopenharmony_ci		__test_release_all_firmware();
68262306a36Sopenharmony_ci	test_firmware = NULL;
68362306a36Sopenharmony_ci	rc = request_firmware(&test_firmware, name, dev);
68462306a36Sopenharmony_ci	if (rc) {
68562306a36Sopenharmony_ci		pr_info("load of '%s' failed: %d\n", name, rc);
68662306a36Sopenharmony_ci		goto out;
68762306a36Sopenharmony_ci	}
68862306a36Sopenharmony_ci	pr_info("loaded: %zu\n", test_firmware->size);
68962306a36Sopenharmony_ci	rc = count;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ciout:
69262306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	kfree(name);
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	return rc;
69762306a36Sopenharmony_ci}
69862306a36Sopenharmony_cistatic DEVICE_ATTR_WO(trigger_request);
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
70162306a36Sopenharmony_ciextern struct list_head efi_embedded_fw_list;
70262306a36Sopenharmony_ciextern bool efi_embedded_fw_checked;
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_cistatic ssize_t trigger_request_platform_store(struct device *dev,
70562306a36Sopenharmony_ci					      struct device_attribute *attr,
70662306a36Sopenharmony_ci					      const char *buf, size_t count)
70762306a36Sopenharmony_ci{
70862306a36Sopenharmony_ci	static const u8 test_data[] = {
70962306a36Sopenharmony_ci		0x55, 0xaa, 0x55, 0xaa, 0x01, 0x02, 0x03, 0x04,
71062306a36Sopenharmony_ci		0x55, 0xaa, 0x55, 0xaa, 0x05, 0x06, 0x07, 0x08,
71162306a36Sopenharmony_ci		0x55, 0xaa, 0x55, 0xaa, 0x10, 0x20, 0x30, 0x40,
71262306a36Sopenharmony_ci		0x55, 0xaa, 0x55, 0xaa, 0x50, 0x60, 0x70, 0x80
71362306a36Sopenharmony_ci	};
71462306a36Sopenharmony_ci	struct efi_embedded_fw efi_embedded_fw;
71562306a36Sopenharmony_ci	const struct firmware *firmware = NULL;
71662306a36Sopenharmony_ci	bool saved_efi_embedded_fw_checked;
71762306a36Sopenharmony_ci	char *name;
71862306a36Sopenharmony_ci	int rc;
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci	name = kstrndup(buf, count, GFP_KERNEL);
72162306a36Sopenharmony_ci	if (!name)
72262306a36Sopenharmony_ci		return -ENOMEM;
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci	pr_info("inserting test platform fw '%s'\n", name);
72562306a36Sopenharmony_ci	efi_embedded_fw.name = name;
72662306a36Sopenharmony_ci	efi_embedded_fw.data = (void *)test_data;
72762306a36Sopenharmony_ci	efi_embedded_fw.length = sizeof(test_data);
72862306a36Sopenharmony_ci	list_add(&efi_embedded_fw.list, &efi_embedded_fw_list);
72962306a36Sopenharmony_ci	saved_efi_embedded_fw_checked = efi_embedded_fw_checked;
73062306a36Sopenharmony_ci	efi_embedded_fw_checked = true;
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ci	pr_info("loading '%s'\n", name);
73362306a36Sopenharmony_ci	rc = firmware_request_platform(&firmware, name, dev);
73462306a36Sopenharmony_ci	if (rc) {
73562306a36Sopenharmony_ci		pr_info("load of '%s' failed: %d\n", name, rc);
73662306a36Sopenharmony_ci		goto out;
73762306a36Sopenharmony_ci	}
73862306a36Sopenharmony_ci	if (firmware->size != sizeof(test_data) ||
73962306a36Sopenharmony_ci	    memcmp(firmware->data, test_data, sizeof(test_data)) != 0) {
74062306a36Sopenharmony_ci		pr_info("firmware contents mismatch for '%s'\n", name);
74162306a36Sopenharmony_ci		rc = -EINVAL;
74262306a36Sopenharmony_ci		goto out;
74362306a36Sopenharmony_ci	}
74462306a36Sopenharmony_ci	pr_info("loaded: %zu\n", firmware->size);
74562306a36Sopenharmony_ci	rc = count;
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ciout:
74862306a36Sopenharmony_ci	efi_embedded_fw_checked = saved_efi_embedded_fw_checked;
74962306a36Sopenharmony_ci	release_firmware(firmware);
75062306a36Sopenharmony_ci	list_del(&efi_embedded_fw.list);
75162306a36Sopenharmony_ci	kfree(name);
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	return rc;
75462306a36Sopenharmony_ci}
75562306a36Sopenharmony_cistatic DEVICE_ATTR_WO(trigger_request_platform);
75662306a36Sopenharmony_ci#endif
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_cistatic DECLARE_COMPLETION(async_fw_done);
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_cistatic void trigger_async_request_cb(const struct firmware *fw, void *context)
76162306a36Sopenharmony_ci{
76262306a36Sopenharmony_ci	test_firmware = fw;
76362306a36Sopenharmony_ci	complete(&async_fw_done);
76462306a36Sopenharmony_ci}
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_cistatic ssize_t trigger_async_request_store(struct device *dev,
76762306a36Sopenharmony_ci					   struct device_attribute *attr,
76862306a36Sopenharmony_ci					   const char *buf, size_t count)
76962306a36Sopenharmony_ci{
77062306a36Sopenharmony_ci	int rc;
77162306a36Sopenharmony_ci	char *name;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	name = kstrndup(buf, count, GFP_KERNEL);
77462306a36Sopenharmony_ci	if (!name)
77562306a36Sopenharmony_ci		return -ENOMEM;
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	pr_info("loading '%s'\n", name);
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
78062306a36Sopenharmony_ci	release_firmware(test_firmware);
78162306a36Sopenharmony_ci	test_firmware = NULL;
78262306a36Sopenharmony_ci	if (test_fw_config->reqs)
78362306a36Sopenharmony_ci		__test_release_all_firmware();
78462306a36Sopenharmony_ci	rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL,
78562306a36Sopenharmony_ci				     NULL, trigger_async_request_cb);
78662306a36Sopenharmony_ci	if (rc) {
78762306a36Sopenharmony_ci		pr_info("async load of '%s' failed: %d\n", name, rc);
78862306a36Sopenharmony_ci		kfree(name);
78962306a36Sopenharmony_ci		goto out;
79062306a36Sopenharmony_ci	}
79162306a36Sopenharmony_ci	/* Free 'name' ASAP, to test for race conditions */
79262306a36Sopenharmony_ci	kfree(name);
79362306a36Sopenharmony_ci
79462306a36Sopenharmony_ci	wait_for_completion(&async_fw_done);
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci	if (test_firmware) {
79762306a36Sopenharmony_ci		pr_info("loaded: %zu\n", test_firmware->size);
79862306a36Sopenharmony_ci		rc = count;
79962306a36Sopenharmony_ci	} else {
80062306a36Sopenharmony_ci		pr_err("failed to async load firmware\n");
80162306a36Sopenharmony_ci		rc = -ENOMEM;
80262306a36Sopenharmony_ci	}
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ciout:
80562306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci	return rc;
80862306a36Sopenharmony_ci}
80962306a36Sopenharmony_cistatic DEVICE_ATTR_WO(trigger_async_request);
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_cistatic ssize_t trigger_custom_fallback_store(struct device *dev,
81262306a36Sopenharmony_ci					     struct device_attribute *attr,
81362306a36Sopenharmony_ci					     const char *buf, size_t count)
81462306a36Sopenharmony_ci{
81562306a36Sopenharmony_ci	int rc;
81662306a36Sopenharmony_ci	char *name;
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci	name = kstrndup(buf, count, GFP_KERNEL);
81962306a36Sopenharmony_ci	if (!name)
82062306a36Sopenharmony_ci		return -ENOMEM;
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci	pr_info("loading '%s' using custom fallback mechanism\n", name);
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
82562306a36Sopenharmony_ci	release_firmware(test_firmware);
82662306a36Sopenharmony_ci	if (test_fw_config->reqs)
82762306a36Sopenharmony_ci		__test_release_all_firmware();
82862306a36Sopenharmony_ci	test_firmware = NULL;
82962306a36Sopenharmony_ci	rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOUEVENT, name,
83062306a36Sopenharmony_ci				     dev, GFP_KERNEL, NULL,
83162306a36Sopenharmony_ci				     trigger_async_request_cb);
83262306a36Sopenharmony_ci	if (rc) {
83362306a36Sopenharmony_ci		pr_info("async load of '%s' failed: %d\n", name, rc);
83462306a36Sopenharmony_ci		kfree(name);
83562306a36Sopenharmony_ci		goto out;
83662306a36Sopenharmony_ci	}
83762306a36Sopenharmony_ci	/* Free 'name' ASAP, to test for race conditions */
83862306a36Sopenharmony_ci	kfree(name);
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	wait_for_completion(&async_fw_done);
84162306a36Sopenharmony_ci
84262306a36Sopenharmony_ci	if (test_firmware) {
84362306a36Sopenharmony_ci		pr_info("loaded: %zu\n", test_firmware->size);
84462306a36Sopenharmony_ci		rc = count;
84562306a36Sopenharmony_ci	} else {
84662306a36Sopenharmony_ci		pr_err("failed to async load firmware\n");
84762306a36Sopenharmony_ci		rc = -ENODEV;
84862306a36Sopenharmony_ci	}
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ciout:
85162306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	return rc;
85462306a36Sopenharmony_ci}
85562306a36Sopenharmony_cistatic DEVICE_ATTR_WO(trigger_custom_fallback);
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_cistatic int test_fw_run_batch_request(void *data)
85862306a36Sopenharmony_ci{
85962306a36Sopenharmony_ci	struct test_batched_req *req = data;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	if (!req) {
86262306a36Sopenharmony_ci		test_fw_config->test_result = -EINVAL;
86362306a36Sopenharmony_ci		return -EINVAL;
86462306a36Sopenharmony_ci	}
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci	if (test_fw_config->into_buf) {
86762306a36Sopenharmony_ci		void *test_buf;
86862306a36Sopenharmony_ci
86962306a36Sopenharmony_ci		test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL);
87062306a36Sopenharmony_ci		if (!test_buf)
87162306a36Sopenharmony_ci			return -ENOMEM;
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci		if (test_fw_config->partial)
87462306a36Sopenharmony_ci			req->rc = request_partial_firmware_into_buf
87562306a36Sopenharmony_ci						(&req->fw,
87662306a36Sopenharmony_ci						 req->name,
87762306a36Sopenharmony_ci						 req->dev,
87862306a36Sopenharmony_ci						 test_buf,
87962306a36Sopenharmony_ci						 test_fw_config->buf_size,
88062306a36Sopenharmony_ci						 test_fw_config->file_offset);
88162306a36Sopenharmony_ci		else
88262306a36Sopenharmony_ci			req->rc = request_firmware_into_buf
88362306a36Sopenharmony_ci						(&req->fw,
88462306a36Sopenharmony_ci						 req->name,
88562306a36Sopenharmony_ci						 req->dev,
88662306a36Sopenharmony_ci						 test_buf,
88762306a36Sopenharmony_ci						 test_fw_config->buf_size);
88862306a36Sopenharmony_ci		if (!req->fw)
88962306a36Sopenharmony_ci			kfree(test_buf);
89062306a36Sopenharmony_ci		else
89162306a36Sopenharmony_ci			req->fw_buf = test_buf;
89262306a36Sopenharmony_ci	} else {
89362306a36Sopenharmony_ci		req->rc = test_fw_config->req_firmware(&req->fw,
89462306a36Sopenharmony_ci						       req->name,
89562306a36Sopenharmony_ci						       req->dev);
89662306a36Sopenharmony_ci	}
89762306a36Sopenharmony_ci
89862306a36Sopenharmony_ci	if (req->rc) {
89962306a36Sopenharmony_ci		pr_info("#%u: batched sync load failed: %d\n",
90062306a36Sopenharmony_ci			req->idx, req->rc);
90162306a36Sopenharmony_ci		if (!test_fw_config->test_result)
90262306a36Sopenharmony_ci			test_fw_config->test_result = req->rc;
90362306a36Sopenharmony_ci	} else if (req->fw) {
90462306a36Sopenharmony_ci		req->sent = true;
90562306a36Sopenharmony_ci		pr_info("#%u: batched sync loaded %zu\n",
90662306a36Sopenharmony_ci			req->idx, req->fw->size);
90762306a36Sopenharmony_ci	}
90862306a36Sopenharmony_ci	complete(&req->completion);
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	req->task = NULL;
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci	return 0;
91362306a36Sopenharmony_ci}
91462306a36Sopenharmony_ci
91562306a36Sopenharmony_ci/*
91662306a36Sopenharmony_ci * We use a kthread as otherwise the kernel serializes all our sync requests
91762306a36Sopenharmony_ci * and we would not be able to mimic batched requests on a sync call. Batched
91862306a36Sopenharmony_ci * requests on a sync call can for instance happen on a device driver when
91962306a36Sopenharmony_ci * multiple cards are used and firmware loading happens outside of probe.
92062306a36Sopenharmony_ci */
92162306a36Sopenharmony_cistatic ssize_t trigger_batched_requests_store(struct device *dev,
92262306a36Sopenharmony_ci					      struct device_attribute *attr,
92362306a36Sopenharmony_ci					      const char *buf, size_t count)
92462306a36Sopenharmony_ci{
92562306a36Sopenharmony_ci	struct test_batched_req *req;
92662306a36Sopenharmony_ci	int rc;
92762306a36Sopenharmony_ci	u8 i;
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
93062306a36Sopenharmony_ci
93162306a36Sopenharmony_ci	if (test_fw_config->reqs) {
93262306a36Sopenharmony_ci		rc = -EBUSY;
93362306a36Sopenharmony_ci		goto out_bail;
93462306a36Sopenharmony_ci	}
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	test_fw_config->reqs =
93762306a36Sopenharmony_ci		vzalloc(array3_size(sizeof(struct test_batched_req),
93862306a36Sopenharmony_ci				    test_fw_config->num_requests, 2));
93962306a36Sopenharmony_ci	if (!test_fw_config->reqs) {
94062306a36Sopenharmony_ci		rc = -ENOMEM;
94162306a36Sopenharmony_ci		goto out_unlock;
94262306a36Sopenharmony_ci	}
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci	pr_info("batched sync firmware loading '%s' %u times\n",
94562306a36Sopenharmony_ci		test_fw_config->name, test_fw_config->num_requests);
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ci	for (i = 0; i < test_fw_config->num_requests; i++) {
94862306a36Sopenharmony_ci		req = &test_fw_config->reqs[i];
94962306a36Sopenharmony_ci		req->fw = NULL;
95062306a36Sopenharmony_ci		req->idx = i;
95162306a36Sopenharmony_ci		req->name = test_fw_config->name;
95262306a36Sopenharmony_ci		req->fw_buf = NULL;
95362306a36Sopenharmony_ci		req->dev = dev;
95462306a36Sopenharmony_ci		init_completion(&req->completion);
95562306a36Sopenharmony_ci		req->task = kthread_run(test_fw_run_batch_request, req,
95662306a36Sopenharmony_ci					     "%s-%u", KBUILD_MODNAME, req->idx);
95762306a36Sopenharmony_ci		if (!req->task || IS_ERR(req->task)) {
95862306a36Sopenharmony_ci			pr_err("Setting up thread %u failed\n", req->idx);
95962306a36Sopenharmony_ci			req->task = NULL;
96062306a36Sopenharmony_ci			rc = -ENOMEM;
96162306a36Sopenharmony_ci			goto out_bail;
96262306a36Sopenharmony_ci		}
96362306a36Sopenharmony_ci	}
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci	rc = count;
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci	/*
96862306a36Sopenharmony_ci	 * We require an explicit release to enable more time and delay of
96962306a36Sopenharmony_ci	 * calling release_firmware() to improve our chances of forcing a
97062306a36Sopenharmony_ci	 * batched request. If we instead called release_firmware() right away
97162306a36Sopenharmony_ci	 * then we might miss on an opportunity of having a successful firmware
97262306a36Sopenharmony_ci	 * request pass on the opportunity to be come a batched request.
97362306a36Sopenharmony_ci	 */
97462306a36Sopenharmony_ci
97562306a36Sopenharmony_ciout_bail:
97662306a36Sopenharmony_ci	for (i = 0; i < test_fw_config->num_requests; i++) {
97762306a36Sopenharmony_ci		req = &test_fw_config->reqs[i];
97862306a36Sopenharmony_ci		if (req->task || req->sent)
97962306a36Sopenharmony_ci			wait_for_completion(&req->completion);
98062306a36Sopenharmony_ci	}
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	/* Override any worker error if we had a general setup error */
98362306a36Sopenharmony_ci	if (rc < 0)
98462306a36Sopenharmony_ci		test_fw_config->test_result = rc;
98562306a36Sopenharmony_ci
98662306a36Sopenharmony_ciout_unlock:
98762306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
98862306a36Sopenharmony_ci
98962306a36Sopenharmony_ci	return rc;
99062306a36Sopenharmony_ci}
99162306a36Sopenharmony_cistatic DEVICE_ATTR_WO(trigger_batched_requests);
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_ci/*
99462306a36Sopenharmony_ci * We wait for each callback to return with the lock held, no need to lock here
99562306a36Sopenharmony_ci */
99662306a36Sopenharmony_cistatic void trigger_batched_cb(const struct firmware *fw, void *context)
99762306a36Sopenharmony_ci{
99862306a36Sopenharmony_ci	struct test_batched_req *req = context;
99962306a36Sopenharmony_ci
100062306a36Sopenharmony_ci	if (!req) {
100162306a36Sopenharmony_ci		test_fw_config->test_result = -EINVAL;
100262306a36Sopenharmony_ci		return;
100362306a36Sopenharmony_ci	}
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci	/* forces *some* batched requests to queue up */
100662306a36Sopenharmony_ci	if (!req->idx)
100762306a36Sopenharmony_ci		ssleep(2);
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	req->fw = fw;
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci	/*
101262306a36Sopenharmony_ci	 * Unfortunately the firmware API gives us nothing other than a null FW
101362306a36Sopenharmony_ci	 * if the firmware was not found on async requests.  Best we can do is
101462306a36Sopenharmony_ci	 * just assume -ENOENT. A better API would pass the actual return
101562306a36Sopenharmony_ci	 * value to the callback.
101662306a36Sopenharmony_ci	 */
101762306a36Sopenharmony_ci	if (!fw && !test_fw_config->test_result)
101862306a36Sopenharmony_ci		test_fw_config->test_result = -ENOENT;
101962306a36Sopenharmony_ci
102062306a36Sopenharmony_ci	complete(&req->completion);
102162306a36Sopenharmony_ci}
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_cistatic
102462306a36Sopenharmony_cissize_t trigger_batched_requests_async_store(struct device *dev,
102562306a36Sopenharmony_ci					     struct device_attribute *attr,
102662306a36Sopenharmony_ci					     const char *buf, size_t count)
102762306a36Sopenharmony_ci{
102862306a36Sopenharmony_ci	struct test_batched_req *req;
102962306a36Sopenharmony_ci	bool send_uevent;
103062306a36Sopenharmony_ci	int rc;
103162306a36Sopenharmony_ci	u8 i;
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
103462306a36Sopenharmony_ci
103562306a36Sopenharmony_ci	if (test_fw_config->reqs) {
103662306a36Sopenharmony_ci		rc = -EBUSY;
103762306a36Sopenharmony_ci		goto out_bail;
103862306a36Sopenharmony_ci	}
103962306a36Sopenharmony_ci
104062306a36Sopenharmony_ci	test_fw_config->reqs =
104162306a36Sopenharmony_ci		vzalloc(array3_size(sizeof(struct test_batched_req),
104262306a36Sopenharmony_ci				    test_fw_config->num_requests, 2));
104362306a36Sopenharmony_ci	if (!test_fw_config->reqs) {
104462306a36Sopenharmony_ci		rc = -ENOMEM;
104562306a36Sopenharmony_ci		goto out;
104662306a36Sopenharmony_ci	}
104762306a36Sopenharmony_ci
104862306a36Sopenharmony_ci	pr_info("batched loading '%s' custom fallback mechanism %u times\n",
104962306a36Sopenharmony_ci		test_fw_config->name, test_fw_config->num_requests);
105062306a36Sopenharmony_ci
105162306a36Sopenharmony_ci	send_uevent = test_fw_config->send_uevent ? FW_ACTION_UEVENT :
105262306a36Sopenharmony_ci		FW_ACTION_NOUEVENT;
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_ci	for (i = 0; i < test_fw_config->num_requests; i++) {
105562306a36Sopenharmony_ci		req = &test_fw_config->reqs[i];
105662306a36Sopenharmony_ci		req->name = test_fw_config->name;
105762306a36Sopenharmony_ci		req->fw_buf = NULL;
105862306a36Sopenharmony_ci		req->fw = NULL;
105962306a36Sopenharmony_ci		req->idx = i;
106062306a36Sopenharmony_ci		init_completion(&req->completion);
106162306a36Sopenharmony_ci		rc = request_firmware_nowait(THIS_MODULE, send_uevent,
106262306a36Sopenharmony_ci					     req->name,
106362306a36Sopenharmony_ci					     dev, GFP_KERNEL, req,
106462306a36Sopenharmony_ci					     trigger_batched_cb);
106562306a36Sopenharmony_ci		if (rc) {
106662306a36Sopenharmony_ci			pr_info("#%u: batched async load failed setup: %d\n",
106762306a36Sopenharmony_ci				i, rc);
106862306a36Sopenharmony_ci			req->rc = rc;
106962306a36Sopenharmony_ci			goto out_bail;
107062306a36Sopenharmony_ci		} else
107162306a36Sopenharmony_ci			req->sent = true;
107262306a36Sopenharmony_ci	}
107362306a36Sopenharmony_ci
107462306a36Sopenharmony_ci	rc = count;
107562306a36Sopenharmony_ci
107662306a36Sopenharmony_ciout_bail:
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_ci	/*
107962306a36Sopenharmony_ci	 * We require an explicit release to enable more time and delay of
108062306a36Sopenharmony_ci	 * calling release_firmware() to improve our chances of forcing a
108162306a36Sopenharmony_ci	 * batched request. If we instead called release_firmware() right away
108262306a36Sopenharmony_ci	 * then we might miss on an opportunity of having a successful firmware
108362306a36Sopenharmony_ci	 * request pass on the opportunity to be come a batched request.
108462306a36Sopenharmony_ci	 */
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci	for (i = 0; i < test_fw_config->num_requests; i++) {
108762306a36Sopenharmony_ci		req = &test_fw_config->reqs[i];
108862306a36Sopenharmony_ci		if (req->sent)
108962306a36Sopenharmony_ci			wait_for_completion(&req->completion);
109062306a36Sopenharmony_ci	}
109162306a36Sopenharmony_ci
109262306a36Sopenharmony_ci	/* Override any worker error if we had a general setup error */
109362306a36Sopenharmony_ci	if (rc < 0)
109462306a36Sopenharmony_ci		test_fw_config->test_result = rc;
109562306a36Sopenharmony_ci
109662306a36Sopenharmony_ciout:
109762306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
109862306a36Sopenharmony_ci
109962306a36Sopenharmony_ci	return rc;
110062306a36Sopenharmony_ci}
110162306a36Sopenharmony_cistatic DEVICE_ATTR_WO(trigger_batched_requests_async);
110262306a36Sopenharmony_ci
110362306a36Sopenharmony_cistatic void upload_release(struct test_firmware_upload *tst)
110462306a36Sopenharmony_ci{
110562306a36Sopenharmony_ci	firmware_upload_unregister(tst->fwl);
110662306a36Sopenharmony_ci	kfree(tst->buf);
110762306a36Sopenharmony_ci	kfree(tst->name);
110862306a36Sopenharmony_ci	kfree(tst);
110962306a36Sopenharmony_ci}
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_cistatic void upload_release_all(void)
111262306a36Sopenharmony_ci{
111362306a36Sopenharmony_ci	struct test_firmware_upload *tst, *tmp;
111462306a36Sopenharmony_ci
111562306a36Sopenharmony_ci	list_for_each_entry_safe(tst, tmp, &test_upload_list, node) {
111662306a36Sopenharmony_ci		list_del(&tst->node);
111762306a36Sopenharmony_ci		upload_release(tst);
111862306a36Sopenharmony_ci	}
111962306a36Sopenharmony_ci	test_fw_config->upload_name = NULL;
112062306a36Sopenharmony_ci}
112162306a36Sopenharmony_ci
112262306a36Sopenharmony_ci/*
112362306a36Sopenharmony_ci * This table is replicated from .../firmware_loader/sysfs_upload.c
112462306a36Sopenharmony_ci * and needs to be kept in sync.
112562306a36Sopenharmony_ci */
112662306a36Sopenharmony_cistatic const char * const fw_upload_err_str[] = {
112762306a36Sopenharmony_ci	[FW_UPLOAD_ERR_NONE]	     = "none",
112862306a36Sopenharmony_ci	[FW_UPLOAD_ERR_HW_ERROR]     = "hw-error",
112962306a36Sopenharmony_ci	[FW_UPLOAD_ERR_TIMEOUT]	     = "timeout",
113062306a36Sopenharmony_ci	[FW_UPLOAD_ERR_CANCELED]     = "user-abort",
113162306a36Sopenharmony_ci	[FW_UPLOAD_ERR_BUSY]	     = "device-busy",
113262306a36Sopenharmony_ci	[FW_UPLOAD_ERR_INVALID_SIZE] = "invalid-file-size",
113362306a36Sopenharmony_ci	[FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
113462306a36Sopenharmony_ci	[FW_UPLOAD_ERR_WEAROUT]	     = "flash-wearout",
113562306a36Sopenharmony_ci};
113662306a36Sopenharmony_ci
113762306a36Sopenharmony_cistatic void upload_err_inject_error(struct test_firmware_upload *tst,
113862306a36Sopenharmony_ci				    const u8 *p, const char *prog)
113962306a36Sopenharmony_ci{
114062306a36Sopenharmony_ci	enum fw_upload_err err;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	for (err = FW_UPLOAD_ERR_NONE + 1; err < FW_UPLOAD_ERR_MAX; err++) {
114362306a36Sopenharmony_ci		if (strncmp(p, fw_upload_err_str[err],
114462306a36Sopenharmony_ci			    strlen(fw_upload_err_str[err])) == 0) {
114562306a36Sopenharmony_ci			tst->inject.prog = prog;
114662306a36Sopenharmony_ci			tst->inject.err_code = err;
114762306a36Sopenharmony_ci			return;
114862306a36Sopenharmony_ci		}
114962306a36Sopenharmony_ci	}
115062306a36Sopenharmony_ci}
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_cistatic void upload_err_inject_prog(struct test_firmware_upload *tst,
115362306a36Sopenharmony_ci				   const u8 *p)
115462306a36Sopenharmony_ci{
115562306a36Sopenharmony_ci	static const char * const progs[] = {
115662306a36Sopenharmony_ci		"preparing:", "transferring:", "programming:"
115762306a36Sopenharmony_ci	};
115862306a36Sopenharmony_ci	int i;
115962306a36Sopenharmony_ci
116062306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(progs); i++) {
116162306a36Sopenharmony_ci		if (strncmp(p, progs[i], strlen(progs[i])) == 0) {
116262306a36Sopenharmony_ci			upload_err_inject_error(tst, p + strlen(progs[i]),
116362306a36Sopenharmony_ci						progs[i]);
116462306a36Sopenharmony_ci			return;
116562306a36Sopenharmony_ci		}
116662306a36Sopenharmony_ci	}
116762306a36Sopenharmony_ci}
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_ci#define FIVE_MINUTES_MS	(5 * 60 * 1000)
117062306a36Sopenharmony_cistatic enum fw_upload_err
117162306a36Sopenharmony_cifw_upload_wait_on_cancel(struct test_firmware_upload *tst)
117262306a36Sopenharmony_ci{
117362306a36Sopenharmony_ci	int ms_delay;
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci	for (ms_delay = 0; ms_delay < FIVE_MINUTES_MS; ms_delay += 100) {
117662306a36Sopenharmony_ci		msleep(100);
117762306a36Sopenharmony_ci		if (tst->cancel_request)
117862306a36Sopenharmony_ci			return FW_UPLOAD_ERR_CANCELED;
117962306a36Sopenharmony_ci	}
118062306a36Sopenharmony_ci	return FW_UPLOAD_ERR_NONE;
118162306a36Sopenharmony_ci}
118262306a36Sopenharmony_ci
118362306a36Sopenharmony_cistatic enum fw_upload_err test_fw_upload_prepare(struct fw_upload *fwl,
118462306a36Sopenharmony_ci						 const u8 *data, u32 size)
118562306a36Sopenharmony_ci{
118662306a36Sopenharmony_ci	struct test_firmware_upload *tst = fwl->dd_handle;
118762306a36Sopenharmony_ci	enum fw_upload_err ret = FW_UPLOAD_ERR_NONE;
118862306a36Sopenharmony_ci	const char *progress = "preparing:";
118962306a36Sopenharmony_ci
119062306a36Sopenharmony_ci	tst->cancel_request = false;
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_ci	if (!size || size > TEST_UPLOAD_MAX_SIZE) {
119362306a36Sopenharmony_ci		ret = FW_UPLOAD_ERR_INVALID_SIZE;
119462306a36Sopenharmony_ci		goto err_out;
119562306a36Sopenharmony_ci	}
119662306a36Sopenharmony_ci
119762306a36Sopenharmony_ci	if (strncmp(data, "inject:", strlen("inject:")) == 0)
119862306a36Sopenharmony_ci		upload_err_inject_prog(tst, data + strlen("inject:"));
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci	memset(tst->buf, 0, TEST_UPLOAD_MAX_SIZE);
120162306a36Sopenharmony_ci	tst->size = size;
120262306a36Sopenharmony_ci
120362306a36Sopenharmony_ci	if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
120462306a36Sopenharmony_ci	    strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
120562306a36Sopenharmony_ci		return FW_UPLOAD_ERR_NONE;
120662306a36Sopenharmony_ci
120762306a36Sopenharmony_ci	if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
120862306a36Sopenharmony_ci		ret = fw_upload_wait_on_cancel(tst);
120962306a36Sopenharmony_ci	else
121062306a36Sopenharmony_ci		ret = tst->inject.err_code;
121162306a36Sopenharmony_ci
121262306a36Sopenharmony_cierr_out:
121362306a36Sopenharmony_ci	/*
121462306a36Sopenharmony_ci	 * The cleanup op only executes if the prepare op succeeds.
121562306a36Sopenharmony_ci	 * If the prepare op fails, it must do it's own clean-up.
121662306a36Sopenharmony_ci	 */
121762306a36Sopenharmony_ci	tst->inject.err_code = FW_UPLOAD_ERR_NONE;
121862306a36Sopenharmony_ci	tst->inject.prog = NULL;
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci	return ret;
122162306a36Sopenharmony_ci}
122262306a36Sopenharmony_ci
122362306a36Sopenharmony_cistatic enum fw_upload_err test_fw_upload_write(struct fw_upload *fwl,
122462306a36Sopenharmony_ci					       const u8 *data, u32 offset,
122562306a36Sopenharmony_ci					       u32 size, u32 *written)
122662306a36Sopenharmony_ci{
122762306a36Sopenharmony_ci	struct test_firmware_upload *tst = fwl->dd_handle;
122862306a36Sopenharmony_ci	const char *progress = "transferring:";
122962306a36Sopenharmony_ci	u32 blk_size;
123062306a36Sopenharmony_ci
123162306a36Sopenharmony_ci	if (tst->cancel_request)
123262306a36Sopenharmony_ci		return FW_UPLOAD_ERR_CANCELED;
123362306a36Sopenharmony_ci
123462306a36Sopenharmony_ci	blk_size = min_t(u32, TEST_UPLOAD_BLK_SIZE, size);
123562306a36Sopenharmony_ci	memcpy(tst->buf + offset, data + offset, blk_size);
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_ci	*written = blk_size;
123862306a36Sopenharmony_ci
123962306a36Sopenharmony_ci	if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
124062306a36Sopenharmony_ci	    strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
124162306a36Sopenharmony_ci		return FW_UPLOAD_ERR_NONE;
124262306a36Sopenharmony_ci
124362306a36Sopenharmony_ci	if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
124462306a36Sopenharmony_ci		return fw_upload_wait_on_cancel(tst);
124562306a36Sopenharmony_ci
124662306a36Sopenharmony_ci	return tst->inject.err_code;
124762306a36Sopenharmony_ci}
124862306a36Sopenharmony_ci
124962306a36Sopenharmony_cistatic enum fw_upload_err test_fw_upload_complete(struct fw_upload *fwl)
125062306a36Sopenharmony_ci{
125162306a36Sopenharmony_ci	struct test_firmware_upload *tst = fwl->dd_handle;
125262306a36Sopenharmony_ci	const char *progress = "programming:";
125362306a36Sopenharmony_ci
125462306a36Sopenharmony_ci	if (tst->cancel_request)
125562306a36Sopenharmony_ci		return FW_UPLOAD_ERR_CANCELED;
125662306a36Sopenharmony_ci
125762306a36Sopenharmony_ci	if (tst->inject.err_code == FW_UPLOAD_ERR_NONE ||
125862306a36Sopenharmony_ci	    strncmp(tst->inject.prog, progress, strlen(progress)) != 0)
125962306a36Sopenharmony_ci		return FW_UPLOAD_ERR_NONE;
126062306a36Sopenharmony_ci
126162306a36Sopenharmony_ci	if (tst->inject.err_code == FW_UPLOAD_ERR_CANCELED)
126262306a36Sopenharmony_ci		return fw_upload_wait_on_cancel(tst);
126362306a36Sopenharmony_ci
126462306a36Sopenharmony_ci	return tst->inject.err_code;
126562306a36Sopenharmony_ci}
126662306a36Sopenharmony_ci
126762306a36Sopenharmony_cistatic void test_fw_upload_cancel(struct fw_upload *fwl)
126862306a36Sopenharmony_ci{
126962306a36Sopenharmony_ci	struct test_firmware_upload *tst = fwl->dd_handle;
127062306a36Sopenharmony_ci
127162306a36Sopenharmony_ci	tst->cancel_request = true;
127262306a36Sopenharmony_ci}
127362306a36Sopenharmony_ci
127462306a36Sopenharmony_cistatic void test_fw_cleanup(struct fw_upload *fwl)
127562306a36Sopenharmony_ci{
127662306a36Sopenharmony_ci	struct test_firmware_upload *tst = fwl->dd_handle;
127762306a36Sopenharmony_ci
127862306a36Sopenharmony_ci	tst->inject.err_code = FW_UPLOAD_ERR_NONE;
127962306a36Sopenharmony_ci	tst->inject.prog = NULL;
128062306a36Sopenharmony_ci}
128162306a36Sopenharmony_ci
128262306a36Sopenharmony_cistatic const struct fw_upload_ops upload_test_ops = {
128362306a36Sopenharmony_ci	.prepare = test_fw_upload_prepare,
128462306a36Sopenharmony_ci	.write = test_fw_upload_write,
128562306a36Sopenharmony_ci	.poll_complete = test_fw_upload_complete,
128662306a36Sopenharmony_ci	.cancel = test_fw_upload_cancel,
128762306a36Sopenharmony_ci	.cleanup = test_fw_cleanup
128862306a36Sopenharmony_ci};
128962306a36Sopenharmony_ci
129062306a36Sopenharmony_cistatic ssize_t upload_register_store(struct device *dev,
129162306a36Sopenharmony_ci				     struct device_attribute *attr,
129262306a36Sopenharmony_ci				     const char *buf, size_t count)
129362306a36Sopenharmony_ci{
129462306a36Sopenharmony_ci	struct test_firmware_upload *tst;
129562306a36Sopenharmony_ci	struct fw_upload *fwl;
129662306a36Sopenharmony_ci	char *name;
129762306a36Sopenharmony_ci	int ret;
129862306a36Sopenharmony_ci
129962306a36Sopenharmony_ci	name = kstrndup(buf, count, GFP_KERNEL);
130062306a36Sopenharmony_ci	if (!name)
130162306a36Sopenharmony_ci		return -ENOMEM;
130262306a36Sopenharmony_ci
130362306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
130462306a36Sopenharmony_ci	tst = upload_lookup_name(name);
130562306a36Sopenharmony_ci	if (tst) {
130662306a36Sopenharmony_ci		ret = -EEXIST;
130762306a36Sopenharmony_ci		goto free_name;
130862306a36Sopenharmony_ci	}
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_ci	tst = kzalloc(sizeof(*tst), GFP_KERNEL);
131162306a36Sopenharmony_ci	if (!tst) {
131262306a36Sopenharmony_ci		ret = -ENOMEM;
131362306a36Sopenharmony_ci		goto free_name;
131462306a36Sopenharmony_ci	}
131562306a36Sopenharmony_ci
131662306a36Sopenharmony_ci	tst->name = name;
131762306a36Sopenharmony_ci	tst->buf = kzalloc(TEST_UPLOAD_MAX_SIZE, GFP_KERNEL);
131862306a36Sopenharmony_ci	if (!tst->buf) {
131962306a36Sopenharmony_ci		ret = -ENOMEM;
132062306a36Sopenharmony_ci		goto free_tst;
132162306a36Sopenharmony_ci	}
132262306a36Sopenharmony_ci
132362306a36Sopenharmony_ci	fwl = firmware_upload_register(THIS_MODULE, dev, tst->name,
132462306a36Sopenharmony_ci				       &upload_test_ops, tst);
132562306a36Sopenharmony_ci	if (IS_ERR(fwl)) {
132662306a36Sopenharmony_ci		ret = PTR_ERR(fwl);
132762306a36Sopenharmony_ci		goto free_buf;
132862306a36Sopenharmony_ci	}
132962306a36Sopenharmony_ci
133062306a36Sopenharmony_ci	tst->fwl = fwl;
133162306a36Sopenharmony_ci	list_add_tail(&tst->node, &test_upload_list);
133262306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
133362306a36Sopenharmony_ci	return count;
133462306a36Sopenharmony_ci
133562306a36Sopenharmony_cifree_buf:
133662306a36Sopenharmony_ci	kfree(tst->buf);
133762306a36Sopenharmony_ci
133862306a36Sopenharmony_cifree_tst:
133962306a36Sopenharmony_ci	kfree(tst);
134062306a36Sopenharmony_ci
134162306a36Sopenharmony_cifree_name:
134262306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
134362306a36Sopenharmony_ci	kfree(name);
134462306a36Sopenharmony_ci
134562306a36Sopenharmony_ci	return ret;
134662306a36Sopenharmony_ci}
134762306a36Sopenharmony_cistatic DEVICE_ATTR_WO(upload_register);
134862306a36Sopenharmony_ci
134962306a36Sopenharmony_cistatic ssize_t upload_unregister_store(struct device *dev,
135062306a36Sopenharmony_ci				       struct device_attribute *attr,
135162306a36Sopenharmony_ci				       const char *buf, size_t count)
135262306a36Sopenharmony_ci{
135362306a36Sopenharmony_ci	struct test_firmware_upload *tst;
135462306a36Sopenharmony_ci	int ret = count;
135562306a36Sopenharmony_ci
135662306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
135762306a36Sopenharmony_ci	tst = upload_lookup_name(buf);
135862306a36Sopenharmony_ci	if (!tst) {
135962306a36Sopenharmony_ci		ret = -EINVAL;
136062306a36Sopenharmony_ci		goto out;
136162306a36Sopenharmony_ci	}
136262306a36Sopenharmony_ci
136362306a36Sopenharmony_ci	if (test_fw_config->upload_name == tst->name)
136462306a36Sopenharmony_ci		test_fw_config->upload_name = NULL;
136562306a36Sopenharmony_ci
136662306a36Sopenharmony_ci	list_del(&tst->node);
136762306a36Sopenharmony_ci	upload_release(tst);
136862306a36Sopenharmony_ci
136962306a36Sopenharmony_ciout:
137062306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
137162306a36Sopenharmony_ci	return ret;
137262306a36Sopenharmony_ci}
137362306a36Sopenharmony_cistatic DEVICE_ATTR_WO(upload_unregister);
137462306a36Sopenharmony_ci
137562306a36Sopenharmony_cistatic ssize_t test_result_show(struct device *dev,
137662306a36Sopenharmony_ci				struct device_attribute *attr,
137762306a36Sopenharmony_ci				char *buf)
137862306a36Sopenharmony_ci{
137962306a36Sopenharmony_ci	return test_dev_config_show_int(buf, test_fw_config->test_result);
138062306a36Sopenharmony_ci}
138162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(test_result);
138262306a36Sopenharmony_ci
138362306a36Sopenharmony_cistatic ssize_t release_all_firmware_store(struct device *dev,
138462306a36Sopenharmony_ci					  struct device_attribute *attr,
138562306a36Sopenharmony_ci					  const char *buf, size_t count)
138662306a36Sopenharmony_ci{
138762306a36Sopenharmony_ci	test_release_all_firmware();
138862306a36Sopenharmony_ci	return count;
138962306a36Sopenharmony_ci}
139062306a36Sopenharmony_cistatic DEVICE_ATTR_WO(release_all_firmware);
139162306a36Sopenharmony_ci
139262306a36Sopenharmony_cistatic ssize_t read_firmware_show(struct device *dev,
139362306a36Sopenharmony_ci				  struct device_attribute *attr,
139462306a36Sopenharmony_ci				  char *buf)
139562306a36Sopenharmony_ci{
139662306a36Sopenharmony_ci	struct test_batched_req *req;
139762306a36Sopenharmony_ci	u8 idx;
139862306a36Sopenharmony_ci	ssize_t rc = 0;
139962306a36Sopenharmony_ci
140062306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
140162306a36Sopenharmony_ci
140262306a36Sopenharmony_ci	idx = test_fw_config->read_fw_idx;
140362306a36Sopenharmony_ci	if (idx >= test_fw_config->num_requests) {
140462306a36Sopenharmony_ci		rc = -ERANGE;
140562306a36Sopenharmony_ci		goto out;
140662306a36Sopenharmony_ci	}
140762306a36Sopenharmony_ci
140862306a36Sopenharmony_ci	if (!test_fw_config->reqs) {
140962306a36Sopenharmony_ci		rc = -EINVAL;
141062306a36Sopenharmony_ci		goto out;
141162306a36Sopenharmony_ci	}
141262306a36Sopenharmony_ci
141362306a36Sopenharmony_ci	req = &test_fw_config->reqs[idx];
141462306a36Sopenharmony_ci	if (!req->fw) {
141562306a36Sopenharmony_ci		pr_err("#%u: failed to async load firmware\n", idx);
141662306a36Sopenharmony_ci		rc = -ENOENT;
141762306a36Sopenharmony_ci		goto out;
141862306a36Sopenharmony_ci	}
141962306a36Sopenharmony_ci
142062306a36Sopenharmony_ci	pr_info("#%u: loaded %zu\n", idx, req->fw->size);
142162306a36Sopenharmony_ci
142262306a36Sopenharmony_ci	if (req->fw->size > PAGE_SIZE) {
142362306a36Sopenharmony_ci		pr_err("Testing interface must use PAGE_SIZE firmware for now\n");
142462306a36Sopenharmony_ci		rc = -EINVAL;
142562306a36Sopenharmony_ci		goto out;
142662306a36Sopenharmony_ci	}
142762306a36Sopenharmony_ci	memcpy(buf, req->fw->data, req->fw->size);
142862306a36Sopenharmony_ci
142962306a36Sopenharmony_ci	rc = req->fw->size;
143062306a36Sopenharmony_ciout:
143162306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
143262306a36Sopenharmony_ci
143362306a36Sopenharmony_ci	return rc;
143462306a36Sopenharmony_ci}
143562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(read_firmware);
143662306a36Sopenharmony_ci
143762306a36Sopenharmony_cistatic ssize_t upload_read_show(struct device *dev,
143862306a36Sopenharmony_ci				struct device_attribute *attr,
143962306a36Sopenharmony_ci				char *buf)
144062306a36Sopenharmony_ci{
144162306a36Sopenharmony_ci	struct test_firmware_upload *tst = NULL;
144262306a36Sopenharmony_ci	struct test_firmware_upload *tst_iter;
144362306a36Sopenharmony_ci	int ret = -EINVAL;
144462306a36Sopenharmony_ci
144562306a36Sopenharmony_ci	if (!test_fw_config->upload_name) {
144662306a36Sopenharmony_ci		pr_err("Set config_upload_name before using upload_read\n");
144762306a36Sopenharmony_ci		return -EINVAL;
144862306a36Sopenharmony_ci	}
144962306a36Sopenharmony_ci
145062306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
145162306a36Sopenharmony_ci	list_for_each_entry(tst_iter, &test_upload_list, node)
145262306a36Sopenharmony_ci		if (tst_iter->name == test_fw_config->upload_name) {
145362306a36Sopenharmony_ci			tst = tst_iter;
145462306a36Sopenharmony_ci			break;
145562306a36Sopenharmony_ci		}
145662306a36Sopenharmony_ci
145762306a36Sopenharmony_ci	if (!tst) {
145862306a36Sopenharmony_ci		pr_err("Firmware name not found: %s\n",
145962306a36Sopenharmony_ci		       test_fw_config->upload_name);
146062306a36Sopenharmony_ci		goto out;
146162306a36Sopenharmony_ci	}
146262306a36Sopenharmony_ci
146362306a36Sopenharmony_ci	if (tst->size > PAGE_SIZE) {
146462306a36Sopenharmony_ci		pr_err("Testing interface must use PAGE_SIZE firmware for now\n");
146562306a36Sopenharmony_ci		goto out;
146662306a36Sopenharmony_ci	}
146762306a36Sopenharmony_ci
146862306a36Sopenharmony_ci	memcpy(buf, tst->buf, tst->size);
146962306a36Sopenharmony_ci	ret = tst->size;
147062306a36Sopenharmony_ciout:
147162306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
147262306a36Sopenharmony_ci	return ret;
147362306a36Sopenharmony_ci}
147462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(upload_read);
147562306a36Sopenharmony_ci
147662306a36Sopenharmony_ci#define TEST_FW_DEV_ATTR(name)          &dev_attr_##name.attr
147762306a36Sopenharmony_ci
147862306a36Sopenharmony_cistatic struct attribute *test_dev_attrs[] = {
147962306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(reset),
148062306a36Sopenharmony_ci
148162306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config),
148262306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_name),
148362306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_num_requests),
148462306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_into_buf),
148562306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_buf_size),
148662306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_file_offset),
148762306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_partial),
148862306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_sync_direct),
148962306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_send_uevent),
149062306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_read_fw_idx),
149162306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(config_upload_name),
149262306a36Sopenharmony_ci
149362306a36Sopenharmony_ci	/* These don't use the config at all - they could be ported! */
149462306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(trigger_request),
149562306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(trigger_async_request),
149662306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(trigger_custom_fallback),
149762306a36Sopenharmony_ci#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
149862306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(trigger_request_platform),
149962306a36Sopenharmony_ci#endif
150062306a36Sopenharmony_ci
150162306a36Sopenharmony_ci	/* These use the config and can use the test_result */
150262306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(trigger_batched_requests),
150362306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(trigger_batched_requests_async),
150462306a36Sopenharmony_ci
150562306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(release_all_firmware),
150662306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(test_result),
150762306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(read_firmware),
150862306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(upload_read),
150962306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(upload_register),
151062306a36Sopenharmony_ci	TEST_FW_DEV_ATTR(upload_unregister),
151162306a36Sopenharmony_ci	NULL,
151262306a36Sopenharmony_ci};
151362306a36Sopenharmony_ci
151462306a36Sopenharmony_ciATTRIBUTE_GROUPS(test_dev);
151562306a36Sopenharmony_ci
151662306a36Sopenharmony_cistatic struct miscdevice test_fw_misc_device = {
151762306a36Sopenharmony_ci	.minor          = MISC_DYNAMIC_MINOR,
151862306a36Sopenharmony_ci	.name           = "test_firmware",
151962306a36Sopenharmony_ci	.fops           = &test_fw_fops,
152062306a36Sopenharmony_ci	.groups 	= test_dev_groups,
152162306a36Sopenharmony_ci};
152262306a36Sopenharmony_ci
152362306a36Sopenharmony_cistatic int __init test_firmware_init(void)
152462306a36Sopenharmony_ci{
152562306a36Sopenharmony_ci	int rc;
152662306a36Sopenharmony_ci
152762306a36Sopenharmony_ci	test_fw_config = kzalloc(sizeof(struct test_config), GFP_KERNEL);
152862306a36Sopenharmony_ci	if (!test_fw_config)
152962306a36Sopenharmony_ci		return -ENOMEM;
153062306a36Sopenharmony_ci
153162306a36Sopenharmony_ci	rc = __test_firmware_config_init();
153262306a36Sopenharmony_ci	if (rc) {
153362306a36Sopenharmony_ci		kfree(test_fw_config);
153462306a36Sopenharmony_ci		pr_err("could not init firmware test config: %d\n", rc);
153562306a36Sopenharmony_ci		return rc;
153662306a36Sopenharmony_ci	}
153762306a36Sopenharmony_ci
153862306a36Sopenharmony_ci	rc = misc_register(&test_fw_misc_device);
153962306a36Sopenharmony_ci	if (rc) {
154062306a36Sopenharmony_ci		__test_firmware_config_free();
154162306a36Sopenharmony_ci		kfree(test_fw_config);
154262306a36Sopenharmony_ci		pr_err("could not register misc device: %d\n", rc);
154362306a36Sopenharmony_ci		return rc;
154462306a36Sopenharmony_ci	}
154562306a36Sopenharmony_ci
154662306a36Sopenharmony_ci	pr_warn("interface ready\n");
154762306a36Sopenharmony_ci
154862306a36Sopenharmony_ci	return 0;
154962306a36Sopenharmony_ci}
155062306a36Sopenharmony_ci
155162306a36Sopenharmony_cimodule_init(test_firmware_init);
155262306a36Sopenharmony_ci
155362306a36Sopenharmony_cistatic void __exit test_firmware_exit(void)
155462306a36Sopenharmony_ci{
155562306a36Sopenharmony_ci	mutex_lock(&test_fw_mutex);
155662306a36Sopenharmony_ci	release_firmware(test_firmware);
155762306a36Sopenharmony_ci	misc_deregister(&test_fw_misc_device);
155862306a36Sopenharmony_ci	upload_release_all();
155962306a36Sopenharmony_ci	__test_firmware_config_free();
156062306a36Sopenharmony_ci	kfree(test_fw_config);
156162306a36Sopenharmony_ci	mutex_unlock(&test_fw_mutex);
156262306a36Sopenharmony_ci
156362306a36Sopenharmony_ci	pr_warn("removed interface\n");
156462306a36Sopenharmony_ci}
156562306a36Sopenharmony_ci
156662306a36Sopenharmony_cimodule_exit(test_firmware_exit);
156762306a36Sopenharmony_ci
156862306a36Sopenharmony_ciMODULE_AUTHOR("Kees Cook <keescook@chromium.org>");
156962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1570