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