forked from mirrors/linux
		
	test_firmware: Add test support for firmware upload
Add support for testing the firmware upload driver. There are four sysfs
nodes added:
upload_register: write-only
  Write the name of the firmware device node to be created
upload_unregister: write-only
  Write the name of the firmware device node to be destroyed
config_upload_name: read/write
  Set the name to be used by upload_read
upload_read: read-only
  Read back the data associated with the firmware device node named
  in config_upload_name
You can create multiple, concurrent firmware device nodes for firmware
upload testing. Read firmware back and validate it using config_upload_name
and upload_red.
Example:
    $ cd /sys/devices/virtual/misc/test_firmware
    $ echo -n fw1 > upload_register
    $ ls fw1
    cancel  data  device  error  loading  power  remaining_size  status
    subsystem  uevent
    $ dd if=/dev/urandom of=/tmp/random-firmware.bin bs=512 count=4
    4+0 records in
    4+0 records out
    2048 bytes (2.0 kB, 2.0 KiB) copied, 0.000131959 s, 15.5 MB/s
    $ echo 1 > fw1/loading
    $ cat /tmp/random-firmware.bin > fw1/data
    $ echo 0 > fw1/loading
    $ cat fw1/status
    idle
    $ cat fw1/error
    $ echo -n fw1 > config_upload_name
    $ cmp /tmp/random-firmware.bin upload_read
    $ echo $?
    0
    $ echo -n fw1 > upload_unregister
Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
Reviewed-by: Tianfei zhang <tianfei.zhang@intel.com>
Tested-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
Signed-off-by: Russ Weight <russell.h.weight@intel.com>
Link: https://lore.kernel.org/r/20220421212204.36052-7-russell.h.weight@intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
			
			
This commit is contained in:
		
							parent
							
								
									536fd8184b
								
							
						
					
					
						commit
						a31ad463b7
					
				
					 1 changed files with 261 additions and 0 deletions
				
			
		|  | @ -31,9 +31,12 @@ MODULE_IMPORT_NS(TEST_FIRMWARE); | ||||||
| #define TEST_FIRMWARE_NAME	"test-firmware.bin" | #define TEST_FIRMWARE_NAME	"test-firmware.bin" | ||||||
| #define TEST_FIRMWARE_NUM_REQS	4 | #define TEST_FIRMWARE_NUM_REQS	4 | ||||||
| #define TEST_FIRMWARE_BUF_SIZE	SZ_1K | #define TEST_FIRMWARE_BUF_SIZE	SZ_1K | ||||||
|  | #define TEST_UPLOAD_MAX_SIZE	SZ_2K | ||||||
|  | #define TEST_UPLOAD_BLK_SIZE	37	/* Avoid powers of two in testing */ | ||||||
| 
 | 
 | ||||||
| static DEFINE_MUTEX(test_fw_mutex); | static DEFINE_MUTEX(test_fw_mutex); | ||||||
| static const struct firmware *test_firmware; | static const struct firmware *test_firmware; | ||||||
|  | static LIST_HEAD(test_upload_list); | ||||||
| 
 | 
 | ||||||
| struct test_batched_req { | struct test_batched_req { | ||||||
| 	u8 idx; | 	u8 idx; | ||||||
|  | @ -63,6 +66,7 @@ struct test_batched_req { | ||||||
|  * @reqs: stores all requests information |  * @reqs: stores all requests information | ||||||
|  * @read_fw_idx: index of thread from which we want to read firmware results |  * @read_fw_idx: index of thread from which we want to read firmware results | ||||||
|  *	from through the read_fw trigger. |  *	from through the read_fw trigger. | ||||||
|  |  * @upload_name: firmware name to be used with upload_read sysfs node | ||||||
|  * @test_result: a test may use this to collect the result from the call |  * @test_result: a test may use this to collect the result from the call | ||||||
|  *	of the request_firmware*() calls used in their tests. In order of |  *	of the request_firmware*() calls used in their tests. In order of | ||||||
|  *	priority we always keep first any setup error. If no setup errors were |  *	priority we always keep first any setup error. If no setup errors were | ||||||
|  | @ -101,6 +105,7 @@ struct test_config { | ||||||
| 	bool send_uevent; | 	bool send_uevent; | ||||||
| 	u8 num_requests; | 	u8 num_requests; | ||||||
| 	u8 read_fw_idx; | 	u8 read_fw_idx; | ||||||
|  | 	char *upload_name; | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * These below don't belong her but we'll move them once we create | 	 * These below don't belong her but we'll move them once we create | ||||||
|  | @ -112,8 +117,28 @@ struct test_config { | ||||||
| 			    struct device *device); | 			    struct device *device); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct test_firmware_upload { | ||||||
|  | 	char *name; | ||||||
|  | 	struct list_head node; | ||||||
|  | 	char *buf; | ||||||
|  | 	size_t size; | ||||||
|  | 	bool cancel_request; | ||||||
|  | 	struct fw_upload *fwl; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| static struct test_config *test_fw_config; | static struct test_config *test_fw_config; | ||||||
| 
 | 
 | ||||||
|  | static struct test_firmware_upload *upload_lookup_name(const char *name) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst; | ||||||
|  | 
 | ||||||
|  | 	list_for_each_entry(tst, &test_upload_list, node) | ||||||
|  | 		if (strncmp(name, tst->name, strlen(tst->name)) == 0) | ||||||
|  | 			return tst; | ||||||
|  | 
 | ||||||
|  | 	return NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static ssize_t test_fw_misc_read(struct file *f, char __user *buf, | static ssize_t test_fw_misc_read(struct file *f, char __user *buf, | ||||||
| 				 size_t size, loff_t *offset) | 				 size_t size, loff_t *offset) | ||||||
| { | { | ||||||
|  | @ -198,6 +223,7 @@ static int __test_firmware_config_init(void) | ||||||
| 	test_fw_config->req_firmware = request_firmware; | 	test_fw_config->req_firmware = request_firmware; | ||||||
| 	test_fw_config->test_result = 0; | 	test_fw_config->test_result = 0; | ||||||
| 	test_fw_config->reqs = NULL; | 	test_fw_config->reqs = NULL; | ||||||
|  | 	test_fw_config->upload_name = NULL; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| 
 | 
 | ||||||
|  | @ -277,6 +303,13 @@ static ssize_t config_show(struct device *dev, | ||||||
| 			test_fw_config->sync_direct ? "true" : "false"); | 			test_fw_config->sync_direct ? "true" : "false"); | ||||||
| 	len += scnprintf(buf + len, PAGE_SIZE - len, | 	len += scnprintf(buf + len, PAGE_SIZE - len, | ||||||
| 			"read_fw_idx:\t%u\n", test_fw_config->read_fw_idx); | 			"read_fw_idx:\t%u\n", test_fw_config->read_fw_idx); | ||||||
|  | 	if (test_fw_config->upload_name) | ||||||
|  | 		len += scnprintf(buf + len, PAGE_SIZE - len, | ||||||
|  | 				"upload_name:\t%s\n", | ||||||
|  | 				test_fw_config->upload_name); | ||||||
|  | 	else | ||||||
|  | 		len += scnprintf(buf + len, PAGE_SIZE - len, | ||||||
|  | 				"upload_name:\tEMTPY\n"); | ||||||
| 
 | 
 | ||||||
| 	mutex_unlock(&test_fw_mutex); | 	mutex_unlock(&test_fw_mutex); | ||||||
| 
 | 
 | ||||||
|  | @ -392,6 +425,32 @@ static ssize_t config_name_show(struct device *dev, | ||||||
| } | } | ||||||
| static DEVICE_ATTR_RW(config_name); | static DEVICE_ATTR_RW(config_name); | ||||||
| 
 | 
 | ||||||
|  | static ssize_t config_upload_name_store(struct device *dev, | ||||||
|  | 					struct device_attribute *attr, | ||||||
|  | 					const char *buf, size_t count) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst; | ||||||
|  | 	int ret = count; | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&test_fw_mutex); | ||||||
|  | 	tst = upload_lookup_name(buf); | ||||||
|  | 	if (tst) | ||||||
|  | 		test_fw_config->upload_name = tst->name; | ||||||
|  | 	else | ||||||
|  | 		ret = -EINVAL; | ||||||
|  | 	mutex_unlock(&test_fw_mutex); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static ssize_t config_upload_name_show(struct device *dev, | ||||||
|  | 				       struct device_attribute *attr, | ||||||
|  | 				       char *buf) | ||||||
|  | { | ||||||
|  | 	return config_test_show_str(buf, test_fw_config->upload_name); | ||||||
|  | } | ||||||
|  | static DEVICE_ATTR_RW(config_upload_name); | ||||||
|  | 
 | ||||||
| static ssize_t config_num_requests_store(struct device *dev, | static ssize_t config_num_requests_store(struct device *dev, | ||||||
| 					 struct device_attribute *attr, | 					 struct device_attribute *attr, | ||||||
| 					 const char *buf, size_t count) | 					 const char *buf, size_t count) | ||||||
|  | @ -989,6 +1048,167 @@ ssize_t trigger_batched_requests_async_store(struct device *dev, | ||||||
| } | } | ||||||
| static DEVICE_ATTR_WO(trigger_batched_requests_async); | static DEVICE_ATTR_WO(trigger_batched_requests_async); | ||||||
| 
 | 
 | ||||||
|  | static void upload_release(struct test_firmware_upload *tst) | ||||||
|  | { | ||||||
|  | 	firmware_upload_unregister(tst->fwl); | ||||||
|  | 	kfree(tst->buf); | ||||||
|  | 	kfree(tst->name); | ||||||
|  | 	kfree(tst); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void upload_release_all(void) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst, *tmp; | ||||||
|  | 
 | ||||||
|  | 	list_for_each_entry_safe(tst, tmp, &test_upload_list, node) { | ||||||
|  | 		list_del(&tst->node); | ||||||
|  | 		upload_release(tst); | ||||||
|  | 	} | ||||||
|  | 	test_fw_config->upload_name = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static enum fw_upload_err test_fw_upload_prepare(struct fw_upload *fwl, | ||||||
|  | 						 const u8 *data, u32 size) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst = fwl->dd_handle; | ||||||
|  | 
 | ||||||
|  | 	tst->cancel_request = false; | ||||||
|  | 
 | ||||||
|  | 	if (!size || size > TEST_UPLOAD_MAX_SIZE) | ||||||
|  | 		return FW_UPLOAD_ERR_INVALID_SIZE; | ||||||
|  | 
 | ||||||
|  | 	memset(tst->buf, 0, TEST_UPLOAD_MAX_SIZE); | ||||||
|  | 	tst->size = size; | ||||||
|  | 
 | ||||||
|  | 	return FW_UPLOAD_ERR_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static enum fw_upload_err test_fw_upload_write(struct fw_upload *fwl, | ||||||
|  | 					       const u8 *data, u32 offset, | ||||||
|  | 					       u32 size, u32 *written) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst = fwl->dd_handle; | ||||||
|  | 	u32 blk_size; | ||||||
|  | 
 | ||||||
|  | 	if (tst->cancel_request) | ||||||
|  | 		return FW_UPLOAD_ERR_CANCELED; | ||||||
|  | 
 | ||||||
|  | 	blk_size = min_t(u32, TEST_UPLOAD_BLK_SIZE, size); | ||||||
|  | 	memcpy(tst->buf + offset, data + offset, blk_size); | ||||||
|  | 
 | ||||||
|  | 	*written = blk_size; | ||||||
|  | 	return FW_UPLOAD_ERR_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static enum fw_upload_err test_fw_upload_complete(struct fw_upload *fwl) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst = fwl->dd_handle; | ||||||
|  | 
 | ||||||
|  | 	if (tst->cancel_request) | ||||||
|  | 		return FW_UPLOAD_ERR_CANCELED; | ||||||
|  | 
 | ||||||
|  | 	return FW_UPLOAD_ERR_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void test_fw_upload_cancel(struct fw_upload *fwl) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst = fwl->dd_handle; | ||||||
|  | 
 | ||||||
|  | 	tst->cancel_request = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct fw_upload_ops upload_test_ops = { | ||||||
|  | 	.prepare = test_fw_upload_prepare, | ||||||
|  | 	.write = test_fw_upload_write, | ||||||
|  | 	.poll_complete = test_fw_upload_complete, | ||||||
|  | 	.cancel = test_fw_upload_cancel, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static ssize_t upload_register_store(struct device *dev, | ||||||
|  | 				     struct device_attribute *attr, | ||||||
|  | 				     const char *buf, size_t count) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst; | ||||||
|  | 	struct fw_upload *fwl; | ||||||
|  | 	char *name; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	name = kstrndup(buf, count, GFP_KERNEL); | ||||||
|  | 	if (!name) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&test_fw_mutex); | ||||||
|  | 	tst = upload_lookup_name(name); | ||||||
|  | 	if (tst) { | ||||||
|  | 		ret = -EEXIST; | ||||||
|  | 		goto free_name; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tst = kzalloc(sizeof(*tst), GFP_KERNEL); | ||||||
|  | 	if (!tst) { | ||||||
|  | 		ret = -ENOMEM; | ||||||
|  | 		goto free_name; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tst->name = name; | ||||||
|  | 	tst->buf = kzalloc(TEST_UPLOAD_MAX_SIZE, GFP_KERNEL); | ||||||
|  | 	if (!tst->buf) { | ||||||
|  | 		ret = -ENOMEM; | ||||||
|  | 		goto free_tst; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fwl = firmware_upload_register(THIS_MODULE, dev, tst->name, | ||||||
|  | 				       &upload_test_ops, tst); | ||||||
|  | 	if (IS_ERR(fwl)) { | ||||||
|  | 		ret = PTR_ERR(fwl); | ||||||
|  | 		goto free_buf; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tst->fwl = fwl; | ||||||
|  | 	list_add_tail(&tst->node, &test_upload_list); | ||||||
|  | 	mutex_unlock(&test_fw_mutex); | ||||||
|  | 	return count; | ||||||
|  | 
 | ||||||
|  | free_buf: | ||||||
|  | 	kfree(tst->buf); | ||||||
|  | 
 | ||||||
|  | free_tst: | ||||||
|  | 	kfree(tst); | ||||||
|  | 
 | ||||||
|  | free_name: | ||||||
|  | 	mutex_unlock(&test_fw_mutex); | ||||||
|  | 	kfree(name); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | static DEVICE_ATTR_WO(upload_register); | ||||||
|  | 
 | ||||||
|  | static ssize_t upload_unregister_store(struct device *dev, | ||||||
|  | 				       struct device_attribute *attr, | ||||||
|  | 				       const char *buf, size_t count) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst; | ||||||
|  | 	int ret = count; | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&test_fw_mutex); | ||||||
|  | 	tst = upload_lookup_name(buf); | ||||||
|  | 	if (!tst) { | ||||||
|  | 		ret = -EINVAL; | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (test_fw_config->upload_name == tst->name) | ||||||
|  | 		test_fw_config->upload_name = NULL; | ||||||
|  | 
 | ||||||
|  | 	list_del(&tst->node); | ||||||
|  | 	upload_release(tst); | ||||||
|  | 
 | ||||||
|  | out: | ||||||
|  | 	mutex_unlock(&test_fw_mutex); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | static DEVICE_ATTR_WO(upload_unregister); | ||||||
|  | 
 | ||||||
| static ssize_t test_result_show(struct device *dev, | static ssize_t test_result_show(struct device *dev, | ||||||
| 				struct device_attribute *attr, | 				struct device_attribute *attr, | ||||||
| 				char *buf) | 				char *buf) | ||||||
|  | @ -1051,6 +1271,42 @@ static ssize_t read_firmware_show(struct device *dev, | ||||||
| } | } | ||||||
| static DEVICE_ATTR_RO(read_firmware); | static DEVICE_ATTR_RO(read_firmware); | ||||||
| 
 | 
 | ||||||
|  | static ssize_t upload_read_show(struct device *dev, | ||||||
|  | 				struct device_attribute *attr, | ||||||
|  | 				char *buf) | ||||||
|  | { | ||||||
|  | 	struct test_firmware_upload *tst; | ||||||
|  | 	int ret = -EINVAL; | ||||||
|  | 
 | ||||||
|  | 	if (!test_fw_config->upload_name) { | ||||||
|  | 		pr_err("Set config_upload_name before using upload_read\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mutex_lock(&test_fw_mutex); | ||||||
|  | 	list_for_each_entry(tst, &test_upload_list, node) | ||||||
|  | 		if (tst->name == test_fw_config->upload_name) | ||||||
|  | 			break; | ||||||
|  | 
 | ||||||
|  | 	if (tst->name != test_fw_config->upload_name) { | ||||||
|  | 		pr_err("Firmware name not found: %s\n", | ||||||
|  | 		       test_fw_config->upload_name); | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (tst->size > PAGE_SIZE) { | ||||||
|  | 		pr_err("Testing interface must use PAGE_SIZE firmware for now\n"); | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	memcpy(buf, tst->buf, tst->size); | ||||||
|  | 	ret = tst->size; | ||||||
|  | out: | ||||||
|  | 	mutex_unlock(&test_fw_mutex); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | static DEVICE_ATTR_RO(upload_read); | ||||||
|  | 
 | ||||||
| #define TEST_FW_DEV_ATTR(name)          &dev_attr_##name.attr | #define TEST_FW_DEV_ATTR(name)          &dev_attr_##name.attr | ||||||
| 
 | 
 | ||||||
| static struct attribute *test_dev_attrs[] = { | static struct attribute *test_dev_attrs[] = { | ||||||
|  | @ -1066,6 +1322,7 @@ static struct attribute *test_dev_attrs[] = { | ||||||
| 	TEST_FW_DEV_ATTR(config_sync_direct), | 	TEST_FW_DEV_ATTR(config_sync_direct), | ||||||
| 	TEST_FW_DEV_ATTR(config_send_uevent), | 	TEST_FW_DEV_ATTR(config_send_uevent), | ||||||
| 	TEST_FW_DEV_ATTR(config_read_fw_idx), | 	TEST_FW_DEV_ATTR(config_read_fw_idx), | ||||||
|  | 	TEST_FW_DEV_ATTR(config_upload_name), | ||||||
| 
 | 
 | ||||||
| 	/* These don't use the config at all - they could be ported! */ | 	/* These don't use the config at all - they could be ported! */ | ||||||
| 	TEST_FW_DEV_ATTR(trigger_request), | 	TEST_FW_DEV_ATTR(trigger_request), | ||||||
|  | @ -1082,6 +1339,9 @@ static struct attribute *test_dev_attrs[] = { | ||||||
| 	TEST_FW_DEV_ATTR(release_all_firmware), | 	TEST_FW_DEV_ATTR(release_all_firmware), | ||||||
| 	TEST_FW_DEV_ATTR(test_result), | 	TEST_FW_DEV_ATTR(test_result), | ||||||
| 	TEST_FW_DEV_ATTR(read_firmware), | 	TEST_FW_DEV_ATTR(read_firmware), | ||||||
|  | 	TEST_FW_DEV_ATTR(upload_read), | ||||||
|  | 	TEST_FW_DEV_ATTR(upload_register), | ||||||
|  | 	TEST_FW_DEV_ATTR(upload_unregister), | ||||||
| 	NULL, | 	NULL, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -1128,6 +1388,7 @@ static void __exit test_firmware_exit(void) | ||||||
| 	mutex_lock(&test_fw_mutex); | 	mutex_lock(&test_fw_mutex); | ||||||
| 	release_firmware(test_firmware); | 	release_firmware(test_firmware); | ||||||
| 	misc_deregister(&test_fw_misc_device); | 	misc_deregister(&test_fw_misc_device); | ||||||
|  | 	upload_release_all(); | ||||||
| 	__test_firmware_config_free(); | 	__test_firmware_config_free(); | ||||||
| 	kfree(test_fw_config); | 	kfree(test_fw_config); | ||||||
| 	mutex_unlock(&test_fw_mutex); | 	mutex_unlock(&test_fw_mutex); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Russ Weight
						Russ Weight