109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 20a8adf58SKees Cook /* 30a8adf58SKees Cook * This module provides an interface to trigger and test firmware loading. 40a8adf58SKees Cook * 50a8adf58SKees Cook * It is designed to be used for basic evaluation of the firmware loading 60a8adf58SKees Cook * subsystem (for example when validating firmware verification). It lacks 70a8adf58SKees Cook * any extra dependencies, and will not normally be loaded by the system 80a8adf58SKees Cook * unless explicitly requested by name. 90a8adf58SKees Cook */ 100a8adf58SKees Cook 110a8adf58SKees Cook #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 120a8adf58SKees Cook 130a8adf58SKees Cook #include <linux/init.h> 140a8adf58SKees Cook #include <linux/module.h> 150a8adf58SKees Cook #include <linux/printk.h> 16eb910947SBrian Norris #include <linux/completion.h> 170a8adf58SKees Cook #include <linux/firmware.h> 180a8adf58SKees Cook #include <linux/device.h> 190a8adf58SKees Cook #include <linux/fs.h> 200a8adf58SKees Cook #include <linux/miscdevice.h> 217feebfa4SScott Branden #include <linux/sizes.h> 220a8adf58SKees Cook #include <linux/slab.h> 230a8adf58SKees Cook #include <linux/uaccess.h> 24c92316bfSLuis R. Rodriguez #include <linux/delay.h> 25c92316bfSLuis R. Rodriguez #include <linux/kthread.h> 26514c6032SRandy Dunlap #include <linux/vmalloc.h> 27548193cbSHans de Goede #include <linux/efi_embedded_fw.h> 28c92316bfSLuis R. Rodriguez 29baaabecfSKees Cook MODULE_IMPORT_NS(TEST_FIRMWARE); 30baaabecfSKees Cook 31c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NAME "test-firmware.bin" 32c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NUM_REQS 4 337feebfa4SScott Branden #define TEST_FIRMWARE_BUF_SIZE SZ_1K 340a8adf58SKees Cook 350a8adf58SKees Cook static DEFINE_MUTEX(test_fw_mutex); 360a8adf58SKees Cook static const struct firmware *test_firmware; 370a8adf58SKees Cook 38c92316bfSLuis R. Rodriguez struct test_batched_req { 39c92316bfSLuis R. Rodriguez u8 idx; 40c92316bfSLuis R. Rodriguez int rc; 41c92316bfSLuis R. Rodriguez bool sent; 42c92316bfSLuis R. Rodriguez const struct firmware *fw; 43c92316bfSLuis R. Rodriguez const char *name; 44c92316bfSLuis R. Rodriguez struct completion completion; 45c92316bfSLuis R. Rodriguez struct task_struct *task; 46c92316bfSLuis R. Rodriguez struct device *dev; 47c92316bfSLuis R. Rodriguez }; 48c92316bfSLuis R. Rodriguez 49c92316bfSLuis R. Rodriguez /** 50c92316bfSLuis R. Rodriguez * test_config - represents configuration for the test for different triggers 51c92316bfSLuis R. Rodriguez * 52c92316bfSLuis R. Rodriguez * @name: the name of the firmware file to look for 537feebfa4SScott Branden * @into_buf: when the into_buf is used if this is true 547feebfa4SScott Branden * request_firmware_into_buf() will be used instead. 555d90e05cSScott Branden * @buf_size: size of buf to allocate when into_buf is true 565d90e05cSScott Branden * @file_offset: file offset to request when calling request_firmware_into_buf 575d90e05cSScott Branden * @partial: partial read opt when calling request_firmware_into_buf 58c92316bfSLuis R. Rodriguez * @sync_direct: when the sync trigger is used if this is true 59c92316bfSLuis R. Rodriguez * request_firmware_direct() will be used instead. 60c92316bfSLuis R. Rodriguez * @send_uevent: whether or not to send a uevent for async requests 61c92316bfSLuis R. Rodriguez * @num_requests: number of requests to try per test case. This is trigger 62c92316bfSLuis R. Rodriguez * specific. 63c92316bfSLuis R. Rodriguez * @reqs: stores all requests information 64c92316bfSLuis R. Rodriguez * @read_fw_idx: index of thread from which we want to read firmware results 65c92316bfSLuis R. Rodriguez * from through the read_fw trigger. 66c92316bfSLuis R. Rodriguez * @test_result: a test may use this to collect the result from the call 67c92316bfSLuis R. Rodriguez * of the request_firmware*() calls used in their tests. In order of 68c92316bfSLuis R. Rodriguez * priority we always keep first any setup error. If no setup errors were 69c92316bfSLuis R. Rodriguez * found then we move on to the first error encountered while running the 70c92316bfSLuis R. Rodriguez * API. Note that for async calls this typically will be a successful 71c92316bfSLuis R. Rodriguez * result (0) unless of course you've used bogus parameters, or the system 72c92316bfSLuis R. Rodriguez * is out of memory. In the async case the callback is expected to do a 73c92316bfSLuis R. Rodriguez * bit more homework to figure out what happened, unfortunately the only 74c92316bfSLuis R. Rodriguez * information passed today on error is the fact that no firmware was 75c92316bfSLuis R. Rodriguez * found so we can only assume -ENOENT on async calls if the firmware is 76c92316bfSLuis R. Rodriguez * NULL. 77c92316bfSLuis R. Rodriguez * 78c92316bfSLuis R. Rodriguez * Errors you can expect: 79c92316bfSLuis R. Rodriguez * 80c92316bfSLuis R. Rodriguez * API specific: 81c92316bfSLuis R. Rodriguez * 82c92316bfSLuis R. Rodriguez * 0: success for sync, for async it means request was sent 83c92316bfSLuis R. Rodriguez * -EINVAL: invalid parameters or request 84c92316bfSLuis R. Rodriguez * -ENOENT: files not found 85c92316bfSLuis R. Rodriguez * 86c92316bfSLuis R. Rodriguez * System environment: 87c92316bfSLuis R. Rodriguez * 88c92316bfSLuis R. Rodriguez * -ENOMEM: memory pressure on system 89c92316bfSLuis R. Rodriguez * -ENODEV: out of number of devices to test 90c92316bfSLuis R. Rodriguez * -EINVAL: an unexpected error has occurred 91c92316bfSLuis R. Rodriguez * @req_firmware: if @sync_direct is true this is set to 92c92316bfSLuis R. Rodriguez * request_firmware_direct(), otherwise request_firmware() 93c92316bfSLuis R. Rodriguez */ 94c92316bfSLuis R. Rodriguez struct test_config { 95c92316bfSLuis R. Rodriguez char *name; 967feebfa4SScott Branden bool into_buf; 975d90e05cSScott Branden size_t buf_size; 985d90e05cSScott Branden size_t file_offset; 995d90e05cSScott Branden bool partial; 100c92316bfSLuis R. Rodriguez bool sync_direct; 101c92316bfSLuis R. Rodriguez bool send_uevent; 102c92316bfSLuis R. Rodriguez u8 num_requests; 103c92316bfSLuis R. Rodriguez u8 read_fw_idx; 104c92316bfSLuis R. Rodriguez 105c92316bfSLuis R. Rodriguez /* 106c92316bfSLuis R. Rodriguez * These below don't belong her but we'll move them once we create 107c92316bfSLuis R. Rodriguez * a struct fw_test_device and stuff the misc_dev under there later. 108c92316bfSLuis R. Rodriguez */ 109c92316bfSLuis R. Rodriguez struct test_batched_req *reqs; 110c92316bfSLuis R. Rodriguez int test_result; 111c92316bfSLuis R. Rodriguez int (*req_firmware)(const struct firmware **fw, const char *name, 112c92316bfSLuis R. Rodriguez struct device *device); 113c92316bfSLuis R. Rodriguez }; 114c92316bfSLuis R. Rodriguez 11576f8ab1bSWei Yongjun static struct test_config *test_fw_config; 116c92316bfSLuis R. Rodriguez 1170a8adf58SKees Cook static ssize_t test_fw_misc_read(struct file *f, char __user *buf, 1180a8adf58SKees Cook size_t size, loff_t *offset) 1190a8adf58SKees Cook { 1200a8adf58SKees Cook ssize_t rc = 0; 1210a8adf58SKees Cook 1220a8adf58SKees Cook mutex_lock(&test_fw_mutex); 1230a8adf58SKees Cook if (test_firmware) 1240a8adf58SKees Cook rc = simple_read_from_buffer(buf, size, offset, 1250a8adf58SKees Cook test_firmware->data, 1260a8adf58SKees Cook test_firmware->size); 1270a8adf58SKees Cook mutex_unlock(&test_fw_mutex); 1280a8adf58SKees Cook return rc; 1290a8adf58SKees Cook } 1300a8adf58SKees Cook 1310a8adf58SKees Cook static const struct file_operations test_fw_fops = { 1320a8adf58SKees Cook .owner = THIS_MODULE, 1330a8adf58SKees Cook .read = test_fw_misc_read, 1340a8adf58SKees Cook }; 1350a8adf58SKees Cook 136c92316bfSLuis R. Rodriguez static void __test_release_all_firmware(void) 137c92316bfSLuis R. Rodriguez { 138c92316bfSLuis R. Rodriguez struct test_batched_req *req; 139c92316bfSLuis R. Rodriguez u8 i; 140c92316bfSLuis R. Rodriguez 141c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) 142c92316bfSLuis R. Rodriguez return; 143c92316bfSLuis R. Rodriguez 144c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 145c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 146c92316bfSLuis R. Rodriguez if (req->fw) 147c92316bfSLuis R. Rodriguez release_firmware(req->fw); 148c92316bfSLuis R. Rodriguez } 149c92316bfSLuis R. Rodriguez 150c92316bfSLuis R. Rodriguez vfree(test_fw_config->reqs); 151c92316bfSLuis R. Rodriguez test_fw_config->reqs = NULL; 152c92316bfSLuis R. Rodriguez } 153c92316bfSLuis R. Rodriguez 154c92316bfSLuis R. Rodriguez static void test_release_all_firmware(void) 155c92316bfSLuis R. Rodriguez { 156c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 157c92316bfSLuis R. Rodriguez __test_release_all_firmware(); 158c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 159c92316bfSLuis R. Rodriguez } 160c92316bfSLuis R. Rodriguez 161c92316bfSLuis R. Rodriguez 162c92316bfSLuis R. Rodriguez static void __test_firmware_config_free(void) 163c92316bfSLuis R. Rodriguez { 164c92316bfSLuis R. Rodriguez __test_release_all_firmware(); 165c92316bfSLuis R. Rodriguez kfree_const(test_fw_config->name); 166c92316bfSLuis R. Rodriguez test_fw_config->name = NULL; 167c92316bfSLuis R. Rodriguez } 168c92316bfSLuis R. Rodriguez 169c92316bfSLuis R. Rodriguez /* 170c92316bfSLuis R. Rodriguez * XXX: move to kstrncpy() once merged. 171c92316bfSLuis R. Rodriguez * 172c92316bfSLuis R. Rodriguez * Users should use kfree_const() when freeing these. 173c92316bfSLuis R. Rodriguez */ 174c92316bfSLuis R. Rodriguez static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp) 175c92316bfSLuis R. Rodriguez { 176c92316bfSLuis R. Rodriguez *dst = kstrndup(name, count, gfp); 177c92316bfSLuis R. Rodriguez if (!*dst) 178c92316bfSLuis R. Rodriguez return -ENOSPC; 179c92316bfSLuis R. Rodriguez return count; 180c92316bfSLuis R. Rodriguez } 181c92316bfSLuis R. Rodriguez 182c92316bfSLuis R. Rodriguez static int __test_firmware_config_init(void) 183c92316bfSLuis R. Rodriguez { 184c92316bfSLuis R. Rodriguez int ret; 185c92316bfSLuis R. Rodriguez 186c92316bfSLuis R. Rodriguez ret = __kstrncpy(&test_fw_config->name, TEST_FIRMWARE_NAME, 187c92316bfSLuis R. Rodriguez strlen(TEST_FIRMWARE_NAME), GFP_KERNEL); 188c92316bfSLuis R. Rodriguez if (ret < 0) 189c92316bfSLuis R. Rodriguez goto out; 190c92316bfSLuis R. Rodriguez 191c92316bfSLuis R. Rodriguez test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS; 192c92316bfSLuis R. Rodriguez test_fw_config->send_uevent = true; 1937feebfa4SScott Branden test_fw_config->into_buf = false; 1945d90e05cSScott Branden test_fw_config->buf_size = TEST_FIRMWARE_BUF_SIZE; 1955d90e05cSScott Branden test_fw_config->file_offset = 0; 1965d90e05cSScott Branden test_fw_config->partial = false; 197c92316bfSLuis R. Rodriguez test_fw_config->sync_direct = false; 198c92316bfSLuis R. Rodriguez test_fw_config->req_firmware = request_firmware; 199c92316bfSLuis R. Rodriguez test_fw_config->test_result = 0; 200c92316bfSLuis R. Rodriguez test_fw_config->reqs = NULL; 201c92316bfSLuis R. Rodriguez 202c92316bfSLuis R. Rodriguez return 0; 203c92316bfSLuis R. Rodriguez 204c92316bfSLuis R. Rodriguez out: 205c92316bfSLuis R. Rodriguez __test_firmware_config_free(); 206c92316bfSLuis R. Rodriguez return ret; 207c92316bfSLuis R. Rodriguez } 208c92316bfSLuis R. Rodriguez 209c92316bfSLuis R. Rodriguez static ssize_t reset_store(struct device *dev, 210c92316bfSLuis R. Rodriguez struct device_attribute *attr, 211c92316bfSLuis R. Rodriguez const char *buf, size_t count) 212c92316bfSLuis R. Rodriguez { 213c92316bfSLuis R. Rodriguez int ret; 214c92316bfSLuis R. Rodriguez 215c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 216c92316bfSLuis R. Rodriguez 217c92316bfSLuis R. Rodriguez __test_firmware_config_free(); 218c92316bfSLuis R. Rodriguez 219c92316bfSLuis R. Rodriguez ret = __test_firmware_config_init(); 220c92316bfSLuis R. Rodriguez if (ret < 0) { 221c92316bfSLuis R. Rodriguez ret = -ENOMEM; 222c92316bfSLuis R. Rodriguez pr_err("could not alloc settings for config trigger: %d\n", 223c92316bfSLuis R. Rodriguez ret); 224c92316bfSLuis R. Rodriguez goto out; 225c92316bfSLuis R. Rodriguez } 226c92316bfSLuis R. Rodriguez 227c92316bfSLuis R. Rodriguez pr_info("reset\n"); 228c92316bfSLuis R. Rodriguez ret = count; 229c92316bfSLuis R. Rodriguez 230c92316bfSLuis R. Rodriguez out: 231c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 232c92316bfSLuis R. Rodriguez 233c92316bfSLuis R. Rodriguez return ret; 234c92316bfSLuis R. Rodriguez } 235c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(reset); 236c92316bfSLuis R. Rodriguez 237c92316bfSLuis R. Rodriguez static ssize_t config_show(struct device *dev, 238c92316bfSLuis R. Rodriguez struct device_attribute *attr, 239c92316bfSLuis R. Rodriguez char *buf) 240c92316bfSLuis R. Rodriguez { 241c92316bfSLuis R. Rodriguez int len = 0; 242c92316bfSLuis R. Rodriguez 243c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 244c92316bfSLuis R. Rodriguez 245bd17cc5aSDan Carpenter len += scnprintf(buf, PAGE_SIZE - len, 246c92316bfSLuis R. Rodriguez "Custom trigger configuration for: %s\n", 247c92316bfSLuis R. Rodriguez dev_name(dev)); 248c92316bfSLuis R. Rodriguez 249c92316bfSLuis R. Rodriguez if (test_fw_config->name) 250bd17cc5aSDan Carpenter len += scnprintf(buf + len, PAGE_SIZE - len, 251c92316bfSLuis R. Rodriguez "name:\t%s\n", 252c92316bfSLuis R. Rodriguez test_fw_config->name); 253c92316bfSLuis R. Rodriguez else 254bd17cc5aSDan Carpenter len += scnprintf(buf + len, PAGE_SIZE - len, 255c92316bfSLuis R. Rodriguez "name:\tEMTPY\n"); 256c92316bfSLuis R. Rodriguez 257bd17cc5aSDan Carpenter len += scnprintf(buf + len, PAGE_SIZE - len, 258c92316bfSLuis R. Rodriguez "num_requests:\t%u\n", test_fw_config->num_requests); 259c92316bfSLuis R. Rodriguez 260bd17cc5aSDan Carpenter len += scnprintf(buf + len, PAGE_SIZE - len, 261c92316bfSLuis R. Rodriguez "send_uevent:\t\t%s\n", 262c92316bfSLuis R. Rodriguez test_fw_config->send_uevent ? 263c92316bfSLuis R. Rodriguez "FW_ACTION_HOTPLUG" : 264c92316bfSLuis R. Rodriguez "FW_ACTION_NOHOTPLUG"); 265bd17cc5aSDan Carpenter len += scnprintf(buf + len, PAGE_SIZE - len, 2667feebfa4SScott Branden "into_buf:\t\t%s\n", 2677feebfa4SScott Branden test_fw_config->into_buf ? "true" : "false"); 2687feebfa4SScott Branden len += scnprintf(buf + len, PAGE_SIZE - len, 2695d90e05cSScott Branden "buf_size:\t%zu\n", test_fw_config->buf_size); 2705d90e05cSScott Branden len += scnprintf(buf + len, PAGE_SIZE - len, 2715d90e05cSScott Branden "file_offset:\t%zu\n", test_fw_config->file_offset); 2725d90e05cSScott Branden len += scnprintf(buf + len, PAGE_SIZE - len, 2735d90e05cSScott Branden "partial:\t\t%s\n", 2745d90e05cSScott Branden test_fw_config->partial ? "true" : "false"); 2755d90e05cSScott Branden len += scnprintf(buf + len, PAGE_SIZE - len, 276c92316bfSLuis R. Rodriguez "sync_direct:\t\t%s\n", 277c92316bfSLuis R. Rodriguez test_fw_config->sync_direct ? "true" : "false"); 278bd17cc5aSDan Carpenter len += scnprintf(buf + len, PAGE_SIZE - len, 279c92316bfSLuis R. Rodriguez "read_fw_idx:\t%u\n", test_fw_config->read_fw_idx); 280c92316bfSLuis R. Rodriguez 281c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 282c92316bfSLuis R. Rodriguez 283c92316bfSLuis R. Rodriguez return len; 284c92316bfSLuis R. Rodriguez } 285c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(config); 286c92316bfSLuis R. Rodriguez 287c92316bfSLuis R. Rodriguez static ssize_t config_name_store(struct device *dev, 288c92316bfSLuis R. Rodriguez struct device_attribute *attr, 289c92316bfSLuis R. Rodriguez const char *buf, size_t count) 290c92316bfSLuis R. Rodriguez { 291c92316bfSLuis R. Rodriguez int ret; 292c92316bfSLuis R. Rodriguez 293c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 294c92316bfSLuis R. Rodriguez kfree_const(test_fw_config->name); 295c92316bfSLuis R. Rodriguez ret = __kstrncpy(&test_fw_config->name, buf, count, GFP_KERNEL); 296c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 297c92316bfSLuis R. Rodriguez 298c92316bfSLuis R. Rodriguez return ret; 299c92316bfSLuis R. Rodriguez } 300c92316bfSLuis R. Rodriguez 301c92316bfSLuis R. Rodriguez /* 302c92316bfSLuis R. Rodriguez * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE. 303c92316bfSLuis R. Rodriguez */ 304c92316bfSLuis R. Rodriguez static ssize_t config_test_show_str(char *dst, 305c92316bfSLuis R. Rodriguez char *src) 306c92316bfSLuis R. Rodriguez { 307c92316bfSLuis R. Rodriguez int len; 308c92316bfSLuis R. Rodriguez 309c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 310c92316bfSLuis R. Rodriguez len = snprintf(dst, PAGE_SIZE, "%s\n", src); 311c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 312c92316bfSLuis R. Rodriguez 313c92316bfSLuis R. Rodriguez return len; 314c92316bfSLuis R. Rodriguez } 315c92316bfSLuis R. Rodriguez 316c92316bfSLuis R. Rodriguez static int test_dev_config_update_bool(const char *buf, size_t size, 317c92316bfSLuis R. Rodriguez bool *cfg) 318c92316bfSLuis R. Rodriguez { 319c92316bfSLuis R. Rodriguez int ret; 320c92316bfSLuis R. Rodriguez 321c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 322c92316bfSLuis R. Rodriguez if (strtobool(buf, cfg) < 0) 323c92316bfSLuis R. Rodriguez ret = -EINVAL; 324c92316bfSLuis R. Rodriguez else 325c92316bfSLuis R. Rodriguez ret = size; 326c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 327c92316bfSLuis R. Rodriguez 328c92316bfSLuis R. Rodriguez return ret; 329c92316bfSLuis R. Rodriguez } 330c92316bfSLuis R. Rodriguez 33155623260SScott Branden static ssize_t test_dev_config_show_bool(char *buf, bool val) 332c92316bfSLuis R. Rodriguez { 333c92316bfSLuis R. Rodriguez return snprintf(buf, PAGE_SIZE, "%d\n", val); 334c92316bfSLuis R. Rodriguez } 335c92316bfSLuis R. Rodriguez 3365d90e05cSScott Branden static int test_dev_config_update_size_t(const char *buf, 3375d90e05cSScott Branden size_t size, 3385d90e05cSScott Branden size_t *cfg) 3395d90e05cSScott Branden { 3405d90e05cSScott Branden int ret; 3415d90e05cSScott Branden long new; 3425d90e05cSScott Branden 3435d90e05cSScott Branden ret = kstrtol(buf, 10, &new); 3445d90e05cSScott Branden if (ret) 3455d90e05cSScott Branden return ret; 3465d90e05cSScott Branden 3475d90e05cSScott Branden mutex_lock(&test_fw_mutex); 3485d90e05cSScott Branden *(size_t *)cfg = new; 3495d90e05cSScott Branden mutex_unlock(&test_fw_mutex); 3505d90e05cSScott Branden 3515d90e05cSScott Branden /* Always return full write size even if we didn't consume all */ 3525d90e05cSScott Branden return size; 3535d90e05cSScott Branden } 3545d90e05cSScott Branden 3555d90e05cSScott Branden static ssize_t test_dev_config_show_size_t(char *buf, size_t val) 3565d90e05cSScott Branden { 3575d90e05cSScott Branden return snprintf(buf, PAGE_SIZE, "%zu\n", val); 3585d90e05cSScott Branden } 3595d90e05cSScott Branden 36055623260SScott Branden static ssize_t test_dev_config_show_int(char *buf, int val) 361c92316bfSLuis R. Rodriguez { 362c92316bfSLuis R. Rodriguez return snprintf(buf, PAGE_SIZE, "%d\n", val); 363c92316bfSLuis R. Rodriguez } 364c92316bfSLuis R. Rodriguez 365c92316bfSLuis R. Rodriguez static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) 366c92316bfSLuis R. Rodriguez { 367*506dfc99SAlexey Dobriyan u8 val; 368c92316bfSLuis R. Rodriguez int ret; 369c92316bfSLuis R. Rodriguez 370*506dfc99SAlexey Dobriyan ret = kstrtou8(buf, 10, &val); 371c92316bfSLuis R. Rodriguez if (ret) 372c92316bfSLuis R. Rodriguez return ret; 373c92316bfSLuis R. Rodriguez 374c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 375*506dfc99SAlexey Dobriyan *(u8 *)cfg = val; 376c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 377c92316bfSLuis R. Rodriguez 378c92316bfSLuis R. Rodriguez /* Always return full write size even if we didn't consume all */ 379c92316bfSLuis R. Rodriguez return size; 380c92316bfSLuis R. Rodriguez } 381c92316bfSLuis R. Rodriguez 38255623260SScott Branden static ssize_t test_dev_config_show_u8(char *buf, u8 val) 383c92316bfSLuis R. Rodriguez { 384c92316bfSLuis R. Rodriguez return snprintf(buf, PAGE_SIZE, "%u\n", val); 385c92316bfSLuis R. Rodriguez } 386c92316bfSLuis R. Rodriguez 387c92316bfSLuis R. Rodriguez static ssize_t config_name_show(struct device *dev, 388c92316bfSLuis R. Rodriguez struct device_attribute *attr, 389c92316bfSLuis R. Rodriguez char *buf) 390c92316bfSLuis R. Rodriguez { 391c92316bfSLuis R. Rodriguez return config_test_show_str(buf, test_fw_config->name); 392c92316bfSLuis R. Rodriguez } 393b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_name); 394c92316bfSLuis R. Rodriguez 395c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_store(struct device *dev, 396c92316bfSLuis R. Rodriguez struct device_attribute *attr, 397c92316bfSLuis R. Rodriguez const char *buf, size_t count) 398c92316bfSLuis R. Rodriguez { 399c92316bfSLuis R. Rodriguez int rc; 400c92316bfSLuis R. Rodriguez 401c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 402c92316bfSLuis R. Rodriguez if (test_fw_config->reqs) { 403c92316bfSLuis R. Rodriguez pr_err("Must call release_all_firmware prior to changing config\n"); 404c92316bfSLuis R. Rodriguez rc = -EINVAL; 405a5e19233SWei Yongjun mutex_unlock(&test_fw_mutex); 406c92316bfSLuis R. Rodriguez goto out; 407c92316bfSLuis R. Rodriguez } 408c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 409c92316bfSLuis R. Rodriguez 410c92316bfSLuis R. Rodriguez rc = test_dev_config_update_u8(buf, count, 411c92316bfSLuis R. Rodriguez &test_fw_config->num_requests); 412c92316bfSLuis R. Rodriguez 413c92316bfSLuis R. Rodriguez out: 414c92316bfSLuis R. Rodriguez return rc; 415c92316bfSLuis R. Rodriguez } 416c92316bfSLuis R. Rodriguez 417c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_show(struct device *dev, 418c92316bfSLuis R. Rodriguez struct device_attribute *attr, 419c92316bfSLuis R. Rodriguez char *buf) 420c92316bfSLuis R. Rodriguez { 421c92316bfSLuis R. Rodriguez return test_dev_config_show_u8(buf, test_fw_config->num_requests); 422c92316bfSLuis R. Rodriguez } 423b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_num_requests); 424c92316bfSLuis R. Rodriguez 4257feebfa4SScott Branden static ssize_t config_into_buf_store(struct device *dev, 4267feebfa4SScott Branden struct device_attribute *attr, 4277feebfa4SScott Branden const char *buf, size_t count) 4287feebfa4SScott Branden { 4297feebfa4SScott Branden return test_dev_config_update_bool(buf, 4307feebfa4SScott Branden count, 4317feebfa4SScott Branden &test_fw_config->into_buf); 4327feebfa4SScott Branden } 4337feebfa4SScott Branden 4347feebfa4SScott Branden static ssize_t config_into_buf_show(struct device *dev, 4357feebfa4SScott Branden struct device_attribute *attr, 4367feebfa4SScott Branden char *buf) 4377feebfa4SScott Branden { 4387feebfa4SScott Branden return test_dev_config_show_bool(buf, test_fw_config->into_buf); 4397feebfa4SScott Branden } 4407feebfa4SScott Branden static DEVICE_ATTR_RW(config_into_buf); 4417feebfa4SScott Branden 4425d90e05cSScott Branden static ssize_t config_buf_size_store(struct device *dev, 4435d90e05cSScott Branden struct device_attribute *attr, 4445d90e05cSScott Branden const char *buf, size_t count) 4455d90e05cSScott Branden { 4465d90e05cSScott Branden int rc; 4475d90e05cSScott Branden 4485d90e05cSScott Branden mutex_lock(&test_fw_mutex); 4495d90e05cSScott Branden if (test_fw_config->reqs) { 4505d90e05cSScott Branden pr_err("Must call release_all_firmware prior to changing config\n"); 4515d90e05cSScott Branden rc = -EINVAL; 4525d90e05cSScott Branden mutex_unlock(&test_fw_mutex); 4535d90e05cSScott Branden goto out; 4545d90e05cSScott Branden } 4555d90e05cSScott Branden mutex_unlock(&test_fw_mutex); 4565d90e05cSScott Branden 4575d90e05cSScott Branden rc = test_dev_config_update_size_t(buf, count, 4585d90e05cSScott Branden &test_fw_config->buf_size); 4595d90e05cSScott Branden 4605d90e05cSScott Branden out: 4615d90e05cSScott Branden return rc; 4625d90e05cSScott Branden } 4635d90e05cSScott Branden 4645d90e05cSScott Branden static ssize_t config_buf_size_show(struct device *dev, 4655d90e05cSScott Branden struct device_attribute *attr, 4665d90e05cSScott Branden char *buf) 4675d90e05cSScott Branden { 4685d90e05cSScott Branden return test_dev_config_show_size_t(buf, test_fw_config->buf_size); 4695d90e05cSScott Branden } 4705d90e05cSScott Branden static DEVICE_ATTR_RW(config_buf_size); 4715d90e05cSScott Branden 4725d90e05cSScott Branden static ssize_t config_file_offset_store(struct device *dev, 4735d90e05cSScott Branden struct device_attribute *attr, 4745d90e05cSScott Branden const char *buf, size_t count) 4755d90e05cSScott Branden { 4765d90e05cSScott Branden int rc; 4775d90e05cSScott Branden 4785d90e05cSScott Branden mutex_lock(&test_fw_mutex); 4795d90e05cSScott Branden if (test_fw_config->reqs) { 4805d90e05cSScott Branden pr_err("Must call release_all_firmware prior to changing config\n"); 4815d90e05cSScott Branden rc = -EINVAL; 4825d90e05cSScott Branden mutex_unlock(&test_fw_mutex); 4835d90e05cSScott Branden goto out; 4845d90e05cSScott Branden } 4855d90e05cSScott Branden mutex_unlock(&test_fw_mutex); 4865d90e05cSScott Branden 4875d90e05cSScott Branden rc = test_dev_config_update_size_t(buf, count, 4885d90e05cSScott Branden &test_fw_config->file_offset); 4895d90e05cSScott Branden 4905d90e05cSScott Branden out: 4915d90e05cSScott Branden return rc; 4925d90e05cSScott Branden } 4935d90e05cSScott Branden 4945d90e05cSScott Branden static ssize_t config_file_offset_show(struct device *dev, 4955d90e05cSScott Branden struct device_attribute *attr, 4965d90e05cSScott Branden char *buf) 4975d90e05cSScott Branden { 4985d90e05cSScott Branden return test_dev_config_show_size_t(buf, test_fw_config->file_offset); 4995d90e05cSScott Branden } 5005d90e05cSScott Branden static DEVICE_ATTR_RW(config_file_offset); 5015d90e05cSScott Branden 5025d90e05cSScott Branden static ssize_t config_partial_store(struct device *dev, 5035d90e05cSScott Branden struct device_attribute *attr, 5045d90e05cSScott Branden const char *buf, size_t count) 5055d90e05cSScott Branden { 5065d90e05cSScott Branden return test_dev_config_update_bool(buf, 5075d90e05cSScott Branden count, 5085d90e05cSScott Branden &test_fw_config->partial); 5095d90e05cSScott Branden } 5105d90e05cSScott Branden 5115d90e05cSScott Branden static ssize_t config_partial_show(struct device *dev, 5125d90e05cSScott Branden struct device_attribute *attr, 5135d90e05cSScott Branden char *buf) 5145d90e05cSScott Branden { 5155d90e05cSScott Branden return test_dev_config_show_bool(buf, test_fw_config->partial); 5165d90e05cSScott Branden } 5175d90e05cSScott Branden static DEVICE_ATTR_RW(config_partial); 5185d90e05cSScott Branden 519c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_store(struct device *dev, 520c92316bfSLuis R. Rodriguez struct device_attribute *attr, 521c92316bfSLuis R. Rodriguez const char *buf, size_t count) 522c92316bfSLuis R. Rodriguez { 523c92316bfSLuis R. Rodriguez int rc = test_dev_config_update_bool(buf, count, 524c92316bfSLuis R. Rodriguez &test_fw_config->sync_direct); 525c92316bfSLuis R. Rodriguez 526c92316bfSLuis R. Rodriguez if (rc == count) 527c92316bfSLuis R. Rodriguez test_fw_config->req_firmware = test_fw_config->sync_direct ? 528c92316bfSLuis R. Rodriguez request_firmware_direct : 529c92316bfSLuis R. Rodriguez request_firmware; 530c92316bfSLuis R. Rodriguez return rc; 531c92316bfSLuis R. Rodriguez } 532c92316bfSLuis R. Rodriguez 533c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_show(struct device *dev, 534c92316bfSLuis R. Rodriguez struct device_attribute *attr, 535c92316bfSLuis R. Rodriguez char *buf) 536c92316bfSLuis R. Rodriguez { 537c92316bfSLuis R. Rodriguez return test_dev_config_show_bool(buf, test_fw_config->sync_direct); 538c92316bfSLuis R. Rodriguez } 539b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_sync_direct); 540c92316bfSLuis R. Rodriguez 541c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_store(struct device *dev, 542c92316bfSLuis R. Rodriguez struct device_attribute *attr, 543c92316bfSLuis R. Rodriguez const char *buf, size_t count) 544c92316bfSLuis R. Rodriguez { 545c92316bfSLuis R. Rodriguez return test_dev_config_update_bool(buf, count, 546c92316bfSLuis R. Rodriguez &test_fw_config->send_uevent); 547c92316bfSLuis R. Rodriguez } 548c92316bfSLuis R. Rodriguez 549c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_show(struct device *dev, 550c92316bfSLuis R. Rodriguez struct device_attribute *attr, 551c92316bfSLuis R. Rodriguez char *buf) 552c92316bfSLuis R. Rodriguez { 553c92316bfSLuis R. Rodriguez return test_dev_config_show_bool(buf, test_fw_config->send_uevent); 554c92316bfSLuis R. Rodriguez } 555b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_send_uevent); 556c92316bfSLuis R. Rodriguez 557c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_store(struct device *dev, 558c92316bfSLuis R. Rodriguez struct device_attribute *attr, 559c92316bfSLuis R. Rodriguez const char *buf, size_t count) 560c92316bfSLuis R. Rodriguez { 561c92316bfSLuis R. Rodriguez return test_dev_config_update_u8(buf, count, 562c92316bfSLuis R. Rodriguez &test_fw_config->read_fw_idx); 563c92316bfSLuis R. Rodriguez } 564c92316bfSLuis R. Rodriguez 565c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_show(struct device *dev, 566c92316bfSLuis R. Rodriguez struct device_attribute *attr, 567c92316bfSLuis R. Rodriguez char *buf) 568c92316bfSLuis R. Rodriguez { 569c92316bfSLuis R. Rodriguez return test_dev_config_show_u8(buf, test_fw_config->read_fw_idx); 570c92316bfSLuis R. Rodriguez } 571b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_read_fw_idx); 572c92316bfSLuis R. Rodriguez 573c92316bfSLuis R. Rodriguez 5740a8adf58SKees Cook static ssize_t trigger_request_store(struct device *dev, 5750a8adf58SKees Cook struct device_attribute *attr, 5760a8adf58SKees Cook const char *buf, size_t count) 5770a8adf58SKees Cook { 5780a8adf58SKees Cook int rc; 5790a8adf58SKees Cook char *name; 5800a8adf58SKees Cook 581be4a1326SBrian Norris name = kstrndup(buf, count, GFP_KERNEL); 5820a8adf58SKees Cook if (!name) 5830a8adf58SKees Cook return -ENOSPC; 5840a8adf58SKees Cook 5850a8adf58SKees Cook pr_info("loading '%s'\n", name); 5860a8adf58SKees Cook 5870a8adf58SKees Cook mutex_lock(&test_fw_mutex); 5880a8adf58SKees Cook release_firmware(test_firmware); 5890a8adf58SKees Cook test_firmware = NULL; 5900a8adf58SKees Cook rc = request_firmware(&test_firmware, name, dev); 59147e0bbb7SBrian Norris if (rc) { 5920a8adf58SKees Cook pr_info("load of '%s' failed: %d\n", name, rc); 59347e0bbb7SBrian Norris goto out; 59447e0bbb7SBrian Norris } 59547e0bbb7SBrian Norris pr_info("loaded: %zu\n", test_firmware->size); 59647e0bbb7SBrian Norris rc = count; 59747e0bbb7SBrian Norris 59847e0bbb7SBrian Norris out: 5990a8adf58SKees Cook mutex_unlock(&test_fw_mutex); 6000a8adf58SKees Cook 6010a8adf58SKees Cook kfree(name); 6020a8adf58SKees Cook 60347e0bbb7SBrian Norris return rc; 6040a8adf58SKees Cook } 6050a8adf58SKees Cook static DEVICE_ATTR_WO(trigger_request); 6060a8adf58SKees Cook 607548193cbSHans de Goede #ifdef CONFIG_EFI_EMBEDDED_FIRMWARE 608baaabecfSKees Cook extern struct list_head efi_embedded_fw_list; 609baaabecfSKees Cook extern bool efi_embedded_fw_checked; 610baaabecfSKees Cook 611548193cbSHans de Goede static ssize_t trigger_request_platform_store(struct device *dev, 612548193cbSHans de Goede struct device_attribute *attr, 613548193cbSHans de Goede const char *buf, size_t count) 614548193cbSHans de Goede { 615548193cbSHans de Goede static const u8 test_data[] = { 616548193cbSHans de Goede 0x55, 0xaa, 0x55, 0xaa, 0x01, 0x02, 0x03, 0x04, 617548193cbSHans de Goede 0x55, 0xaa, 0x55, 0xaa, 0x05, 0x06, 0x07, 0x08, 618548193cbSHans de Goede 0x55, 0xaa, 0x55, 0xaa, 0x10, 0x20, 0x30, 0x40, 619548193cbSHans de Goede 0x55, 0xaa, 0x55, 0xaa, 0x50, 0x60, 0x70, 0x80 620548193cbSHans de Goede }; 621548193cbSHans de Goede struct efi_embedded_fw efi_embedded_fw; 622548193cbSHans de Goede const struct firmware *firmware = NULL; 623baaabecfSKees Cook bool saved_efi_embedded_fw_checked; 624548193cbSHans de Goede char *name; 625548193cbSHans de Goede int rc; 626548193cbSHans de Goede 627548193cbSHans de Goede name = kstrndup(buf, count, GFP_KERNEL); 628548193cbSHans de Goede if (!name) 629548193cbSHans de Goede return -ENOSPC; 630548193cbSHans de Goede 631548193cbSHans de Goede pr_info("inserting test platform fw '%s'\n", name); 632548193cbSHans de Goede efi_embedded_fw.name = name; 633548193cbSHans de Goede efi_embedded_fw.data = (void *)test_data; 634548193cbSHans de Goede efi_embedded_fw.length = sizeof(test_data); 635548193cbSHans de Goede list_add(&efi_embedded_fw.list, &efi_embedded_fw_list); 636baaabecfSKees Cook saved_efi_embedded_fw_checked = efi_embedded_fw_checked; 637baaabecfSKees Cook efi_embedded_fw_checked = true; 638548193cbSHans de Goede 639548193cbSHans de Goede pr_info("loading '%s'\n", name); 640548193cbSHans de Goede rc = firmware_request_platform(&firmware, name, dev); 641548193cbSHans de Goede if (rc) { 642548193cbSHans de Goede pr_info("load of '%s' failed: %d\n", name, rc); 643548193cbSHans de Goede goto out; 644548193cbSHans de Goede } 645548193cbSHans de Goede if (firmware->size != sizeof(test_data) || 646548193cbSHans de Goede memcmp(firmware->data, test_data, sizeof(test_data)) != 0) { 647548193cbSHans de Goede pr_info("firmware contents mismatch for '%s'\n", name); 648548193cbSHans de Goede rc = -EINVAL; 649548193cbSHans de Goede goto out; 650548193cbSHans de Goede } 651548193cbSHans de Goede pr_info("loaded: %zu\n", firmware->size); 652548193cbSHans de Goede rc = count; 653548193cbSHans de Goede 654548193cbSHans de Goede out: 655baaabecfSKees Cook efi_embedded_fw_checked = saved_efi_embedded_fw_checked; 656548193cbSHans de Goede release_firmware(firmware); 657548193cbSHans de Goede list_del(&efi_embedded_fw.list); 658548193cbSHans de Goede kfree(name); 659548193cbSHans de Goede 660548193cbSHans de Goede return rc; 661548193cbSHans de Goede } 662548193cbSHans de Goede static DEVICE_ATTR_WO(trigger_request_platform); 663548193cbSHans de Goede #endif 664548193cbSHans de Goede 665eb910947SBrian Norris static DECLARE_COMPLETION(async_fw_done); 666eb910947SBrian Norris 667eb910947SBrian Norris static void trigger_async_request_cb(const struct firmware *fw, void *context) 668eb910947SBrian Norris { 669eb910947SBrian Norris test_firmware = fw; 670eb910947SBrian Norris complete(&async_fw_done); 671eb910947SBrian Norris } 672eb910947SBrian Norris 673eb910947SBrian Norris static ssize_t trigger_async_request_store(struct device *dev, 674eb910947SBrian Norris struct device_attribute *attr, 675eb910947SBrian Norris const char *buf, size_t count) 676eb910947SBrian Norris { 677eb910947SBrian Norris int rc; 678eb910947SBrian Norris char *name; 679eb910947SBrian Norris 680eb910947SBrian Norris name = kstrndup(buf, count, GFP_KERNEL); 681eb910947SBrian Norris if (!name) 682eb910947SBrian Norris return -ENOSPC; 683eb910947SBrian Norris 684eb910947SBrian Norris pr_info("loading '%s'\n", name); 685eb910947SBrian Norris 686eb910947SBrian Norris mutex_lock(&test_fw_mutex); 687eb910947SBrian Norris release_firmware(test_firmware); 688eb910947SBrian Norris test_firmware = NULL; 689eb910947SBrian Norris rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL, 690eb910947SBrian Norris NULL, trigger_async_request_cb); 691eb910947SBrian Norris if (rc) { 692eb910947SBrian Norris pr_info("async load of '%s' failed: %d\n", name, rc); 693eb910947SBrian Norris kfree(name); 694eb910947SBrian Norris goto out; 695eb910947SBrian Norris } 696eb910947SBrian Norris /* Free 'name' ASAP, to test for race conditions */ 697eb910947SBrian Norris kfree(name); 698eb910947SBrian Norris 699eb910947SBrian Norris wait_for_completion(&async_fw_done); 700eb910947SBrian Norris 701eb910947SBrian Norris if (test_firmware) { 702eb910947SBrian Norris pr_info("loaded: %zu\n", test_firmware->size); 703eb910947SBrian Norris rc = count; 704eb910947SBrian Norris } else { 705eb910947SBrian Norris pr_err("failed to async load firmware\n"); 7067feebfa4SScott Branden rc = -ENOMEM; 707eb910947SBrian Norris } 708eb910947SBrian Norris 709eb910947SBrian Norris out: 710eb910947SBrian Norris mutex_unlock(&test_fw_mutex); 711eb910947SBrian Norris 712eb910947SBrian Norris return rc; 713eb910947SBrian Norris } 714eb910947SBrian Norris static DEVICE_ATTR_WO(trigger_async_request); 715eb910947SBrian Norris 716061132d2SLuis R. Rodriguez static ssize_t trigger_custom_fallback_store(struct device *dev, 717061132d2SLuis R. Rodriguez struct device_attribute *attr, 718061132d2SLuis R. Rodriguez const char *buf, size_t count) 719061132d2SLuis R. Rodriguez { 720061132d2SLuis R. Rodriguez int rc; 721061132d2SLuis R. Rodriguez char *name; 722061132d2SLuis R. Rodriguez 723061132d2SLuis R. Rodriguez name = kstrndup(buf, count, GFP_KERNEL); 724061132d2SLuis R. Rodriguez if (!name) 725061132d2SLuis R. Rodriguez return -ENOSPC; 726061132d2SLuis R. Rodriguez 727061132d2SLuis R. Rodriguez pr_info("loading '%s' using custom fallback mechanism\n", name); 728061132d2SLuis R. Rodriguez 729061132d2SLuis R. Rodriguez mutex_lock(&test_fw_mutex); 730061132d2SLuis R. Rodriguez release_firmware(test_firmware); 731061132d2SLuis R. Rodriguez test_firmware = NULL; 732061132d2SLuis R. Rodriguez rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, name, 733061132d2SLuis R. Rodriguez dev, GFP_KERNEL, NULL, 734061132d2SLuis R. Rodriguez trigger_async_request_cb); 735061132d2SLuis R. Rodriguez if (rc) { 736061132d2SLuis R. Rodriguez pr_info("async load of '%s' failed: %d\n", name, rc); 737061132d2SLuis R. Rodriguez kfree(name); 738061132d2SLuis R. Rodriguez goto out; 739061132d2SLuis R. Rodriguez } 740061132d2SLuis R. Rodriguez /* Free 'name' ASAP, to test for race conditions */ 741061132d2SLuis R. Rodriguez kfree(name); 742061132d2SLuis R. Rodriguez 743061132d2SLuis R. Rodriguez wait_for_completion(&async_fw_done); 744061132d2SLuis R. Rodriguez 745061132d2SLuis R. Rodriguez if (test_firmware) { 746061132d2SLuis R. Rodriguez pr_info("loaded: %zu\n", test_firmware->size); 747061132d2SLuis R. Rodriguez rc = count; 748061132d2SLuis R. Rodriguez } else { 749061132d2SLuis R. Rodriguez pr_err("failed to async load firmware\n"); 750061132d2SLuis R. Rodriguez rc = -ENODEV; 751061132d2SLuis R. Rodriguez } 752061132d2SLuis R. Rodriguez 753061132d2SLuis R. Rodriguez out: 754061132d2SLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 755061132d2SLuis R. Rodriguez 756061132d2SLuis R. Rodriguez return rc; 757061132d2SLuis R. Rodriguez } 758061132d2SLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_custom_fallback); 759061132d2SLuis R. Rodriguez 760c92316bfSLuis R. Rodriguez static int test_fw_run_batch_request(void *data) 761c92316bfSLuis R. Rodriguez { 762c92316bfSLuis R. Rodriguez struct test_batched_req *req = data; 763c92316bfSLuis R. Rodriguez 764c92316bfSLuis R. Rodriguez if (!req) { 765c92316bfSLuis R. Rodriguez test_fw_config->test_result = -EINVAL; 766c92316bfSLuis R. Rodriguez return -EINVAL; 767c92316bfSLuis R. Rodriguez } 768c92316bfSLuis R. Rodriguez 7697feebfa4SScott Branden if (test_fw_config->into_buf) { 7707feebfa4SScott Branden void *test_buf; 7717feebfa4SScott Branden 7727feebfa4SScott Branden test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL); 7737feebfa4SScott Branden if (!test_buf) 7747feebfa4SScott Branden return -ENOSPC; 7757feebfa4SScott Branden 7765d90e05cSScott Branden if (test_fw_config->partial) 7775d90e05cSScott Branden req->rc = request_partial_firmware_into_buf 7785d90e05cSScott Branden (&req->fw, 7797feebfa4SScott Branden req->name, 7807feebfa4SScott Branden req->dev, 7817feebfa4SScott Branden test_buf, 7825d90e05cSScott Branden test_fw_config->buf_size, 7835d90e05cSScott Branden test_fw_config->file_offset); 7845d90e05cSScott Branden else 7855d90e05cSScott Branden req->rc = request_firmware_into_buf 7865d90e05cSScott Branden (&req->fw, 7875d90e05cSScott Branden req->name, 7885d90e05cSScott Branden req->dev, 7895d90e05cSScott Branden test_buf, 7905d90e05cSScott Branden test_fw_config->buf_size); 7917feebfa4SScott Branden if (!req->fw) 7927feebfa4SScott Branden kfree(test_buf); 7937feebfa4SScott Branden } else { 7947feebfa4SScott Branden req->rc = test_fw_config->req_firmware(&req->fw, 7957feebfa4SScott Branden req->name, 7967feebfa4SScott Branden req->dev); 7977feebfa4SScott Branden } 7987feebfa4SScott Branden 799c92316bfSLuis R. Rodriguez if (req->rc) { 800c92316bfSLuis R. Rodriguez pr_info("#%u: batched sync load failed: %d\n", 801c92316bfSLuis R. Rodriguez req->idx, req->rc); 802c92316bfSLuis R. Rodriguez if (!test_fw_config->test_result) 803c92316bfSLuis R. Rodriguez test_fw_config->test_result = req->rc; 804c92316bfSLuis R. Rodriguez } else if (req->fw) { 805c92316bfSLuis R. Rodriguez req->sent = true; 806c92316bfSLuis R. Rodriguez pr_info("#%u: batched sync loaded %zu\n", 807c92316bfSLuis R. Rodriguez req->idx, req->fw->size); 808c92316bfSLuis R. Rodriguez } 809c92316bfSLuis R. Rodriguez complete(&req->completion); 810c92316bfSLuis R. Rodriguez 811c92316bfSLuis R. Rodriguez req->task = NULL; 812c92316bfSLuis R. Rodriguez 813c92316bfSLuis R. Rodriguez return 0; 814c92316bfSLuis R. Rodriguez } 815c92316bfSLuis R. Rodriguez 816c92316bfSLuis R. Rodriguez /* 817c92316bfSLuis R. Rodriguez * We use a kthread as otherwise the kernel serializes all our sync requests 818c92316bfSLuis R. Rodriguez * and we would not be able to mimic batched requests on a sync call. Batched 819c92316bfSLuis R. Rodriguez * requests on a sync call can for instance happen on a device driver when 820c92316bfSLuis R. Rodriguez * multiple cards are used and firmware loading happens outside of probe. 821c92316bfSLuis R. Rodriguez */ 822c92316bfSLuis R. Rodriguez static ssize_t trigger_batched_requests_store(struct device *dev, 823c92316bfSLuis R. Rodriguez struct device_attribute *attr, 824c92316bfSLuis R. Rodriguez const char *buf, size_t count) 825c92316bfSLuis R. Rodriguez { 826c92316bfSLuis R. Rodriguez struct test_batched_req *req; 827c92316bfSLuis R. Rodriguez int rc; 828c92316bfSLuis R. Rodriguez u8 i; 829c92316bfSLuis R. Rodriguez 830c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 831c92316bfSLuis R. Rodriguez 832fad953ceSKees Cook test_fw_config->reqs = 833fad953ceSKees Cook vzalloc(array3_size(sizeof(struct test_batched_req), 834fad953ceSKees Cook test_fw_config->num_requests, 2)); 835c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) { 836c92316bfSLuis R. Rodriguez rc = -ENOMEM; 837c92316bfSLuis R. Rodriguez goto out_unlock; 838c92316bfSLuis R. Rodriguez } 839c92316bfSLuis R. Rodriguez 840c92316bfSLuis R. Rodriguez pr_info("batched sync firmware loading '%s' %u times\n", 841c92316bfSLuis R. Rodriguez test_fw_config->name, test_fw_config->num_requests); 842c92316bfSLuis R. Rodriguez 843c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 844c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 845c92316bfSLuis R. Rodriguez req->fw = NULL; 846c92316bfSLuis R. Rodriguez req->idx = i; 847c92316bfSLuis R. Rodriguez req->name = test_fw_config->name; 848c92316bfSLuis R. Rodriguez req->dev = dev; 849c92316bfSLuis R. Rodriguez init_completion(&req->completion); 850c92316bfSLuis R. Rodriguez req->task = kthread_run(test_fw_run_batch_request, req, 851c92316bfSLuis R. Rodriguez "%s-%u", KBUILD_MODNAME, req->idx); 852c92316bfSLuis R. Rodriguez if (!req->task || IS_ERR(req->task)) { 853c92316bfSLuis R. Rodriguez pr_err("Setting up thread %u failed\n", req->idx); 854c92316bfSLuis R. Rodriguez req->task = NULL; 855c92316bfSLuis R. Rodriguez rc = -ENOMEM; 856c92316bfSLuis R. Rodriguez goto out_bail; 857c92316bfSLuis R. Rodriguez } 858c92316bfSLuis R. Rodriguez } 859c92316bfSLuis R. Rodriguez 860c92316bfSLuis R. Rodriguez rc = count; 861c92316bfSLuis R. Rodriguez 862c92316bfSLuis R. Rodriguez /* 863c92316bfSLuis R. Rodriguez * We require an explicit release to enable more time and delay of 864c92316bfSLuis R. Rodriguez * calling release_firmware() to improve our chances of forcing a 865c92316bfSLuis R. Rodriguez * batched request. If we instead called release_firmware() right away 866c92316bfSLuis R. Rodriguez * then we might miss on an opportunity of having a successful firmware 867c92316bfSLuis R. Rodriguez * request pass on the opportunity to be come a batched request. 868c92316bfSLuis R. Rodriguez */ 869c92316bfSLuis R. Rodriguez 870c92316bfSLuis R. Rodriguez out_bail: 871c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 872c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 873c92316bfSLuis R. Rodriguez if (req->task || req->sent) 874c92316bfSLuis R. Rodriguez wait_for_completion(&req->completion); 875c92316bfSLuis R. Rodriguez } 876c92316bfSLuis R. Rodriguez 877c92316bfSLuis R. Rodriguez /* Override any worker error if we had a general setup error */ 878c92316bfSLuis R. Rodriguez if (rc < 0) 879c92316bfSLuis R. Rodriguez test_fw_config->test_result = rc; 880c92316bfSLuis R. Rodriguez 881c92316bfSLuis R. Rodriguez out_unlock: 882c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 883c92316bfSLuis R. Rodriguez 884c92316bfSLuis R. Rodriguez return rc; 885c92316bfSLuis R. Rodriguez } 886c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests); 887c92316bfSLuis R. Rodriguez 888c92316bfSLuis R. Rodriguez /* 889c92316bfSLuis R. Rodriguez * We wait for each callback to return with the lock held, no need to lock here 890c92316bfSLuis R. Rodriguez */ 891c92316bfSLuis R. Rodriguez static void trigger_batched_cb(const struct firmware *fw, void *context) 892c92316bfSLuis R. Rodriguez { 893c92316bfSLuis R. Rodriguez struct test_batched_req *req = context; 894c92316bfSLuis R. Rodriguez 895c92316bfSLuis R. Rodriguez if (!req) { 896c92316bfSLuis R. Rodriguez test_fw_config->test_result = -EINVAL; 897c92316bfSLuis R. Rodriguez return; 898c92316bfSLuis R. Rodriguez } 899c92316bfSLuis R. Rodriguez 900c92316bfSLuis R. Rodriguez /* forces *some* batched requests to queue up */ 901c92316bfSLuis R. Rodriguez if (!req->idx) 902c92316bfSLuis R. Rodriguez ssleep(2); 903c92316bfSLuis R. Rodriguez 904c92316bfSLuis R. Rodriguez req->fw = fw; 905c92316bfSLuis R. Rodriguez 906c92316bfSLuis R. Rodriguez /* 907c92316bfSLuis R. Rodriguez * Unfortunately the firmware API gives us nothing other than a null FW 908c92316bfSLuis R. Rodriguez * if the firmware was not found on async requests. Best we can do is 909c92316bfSLuis R. Rodriguez * just assume -ENOENT. A better API would pass the actual return 910c92316bfSLuis R. Rodriguez * value to the callback. 911c92316bfSLuis R. Rodriguez */ 912c92316bfSLuis R. Rodriguez if (!fw && !test_fw_config->test_result) 913c92316bfSLuis R. Rodriguez test_fw_config->test_result = -ENOENT; 914c92316bfSLuis R. Rodriguez 915c92316bfSLuis R. Rodriguez complete(&req->completion); 916c92316bfSLuis R. Rodriguez } 917c92316bfSLuis R. Rodriguez 918c92316bfSLuis R. Rodriguez static 919c92316bfSLuis R. Rodriguez ssize_t trigger_batched_requests_async_store(struct device *dev, 920c92316bfSLuis R. Rodriguez struct device_attribute *attr, 921c92316bfSLuis R. Rodriguez const char *buf, size_t count) 922c92316bfSLuis R. Rodriguez { 923c92316bfSLuis R. Rodriguez struct test_batched_req *req; 924c92316bfSLuis R. Rodriguez bool send_uevent; 925c92316bfSLuis R. Rodriguez int rc; 926c92316bfSLuis R. Rodriguez u8 i; 927c92316bfSLuis R. Rodriguez 928c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 929c92316bfSLuis R. Rodriguez 930fad953ceSKees Cook test_fw_config->reqs = 931fad953ceSKees Cook vzalloc(array3_size(sizeof(struct test_batched_req), 932fad953ceSKees Cook test_fw_config->num_requests, 2)); 933c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) { 934c92316bfSLuis R. Rodriguez rc = -ENOMEM; 935c92316bfSLuis R. Rodriguez goto out; 936c92316bfSLuis R. Rodriguez } 937c92316bfSLuis R. Rodriguez 938c92316bfSLuis R. Rodriguez pr_info("batched loading '%s' custom fallback mechanism %u times\n", 939c92316bfSLuis R. Rodriguez test_fw_config->name, test_fw_config->num_requests); 940c92316bfSLuis R. Rodriguez 941c92316bfSLuis R. Rodriguez send_uevent = test_fw_config->send_uevent ? FW_ACTION_HOTPLUG : 942c92316bfSLuis R. Rodriguez FW_ACTION_NOHOTPLUG; 943c92316bfSLuis R. Rodriguez 944c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 945c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 946c92316bfSLuis R. Rodriguez req->name = test_fw_config->name; 947c92316bfSLuis R. Rodriguez req->fw = NULL; 948c92316bfSLuis R. Rodriguez req->idx = i; 949c92316bfSLuis R. Rodriguez init_completion(&req->completion); 950c92316bfSLuis R. Rodriguez rc = request_firmware_nowait(THIS_MODULE, send_uevent, 951c92316bfSLuis R. Rodriguez req->name, 952c92316bfSLuis R. Rodriguez dev, GFP_KERNEL, req, 953c92316bfSLuis R. Rodriguez trigger_batched_cb); 954c92316bfSLuis R. Rodriguez if (rc) { 955c92316bfSLuis R. Rodriguez pr_info("#%u: batched async load failed setup: %d\n", 956c92316bfSLuis R. Rodriguez i, rc); 957c92316bfSLuis R. Rodriguez req->rc = rc; 958c92316bfSLuis R. Rodriguez goto out_bail; 959c92316bfSLuis R. Rodriguez } else 960c92316bfSLuis R. Rodriguez req->sent = true; 961c92316bfSLuis R. Rodriguez } 962c92316bfSLuis R. Rodriguez 963c92316bfSLuis R. Rodriguez rc = count; 964c92316bfSLuis R. Rodriguez 965c92316bfSLuis R. Rodriguez out_bail: 966c92316bfSLuis R. Rodriguez 967c92316bfSLuis R. Rodriguez /* 968c92316bfSLuis R. Rodriguez * We require an explicit release to enable more time and delay of 969c92316bfSLuis R. Rodriguez * calling release_firmware() to improve our chances of forcing a 970c92316bfSLuis R. Rodriguez * batched request. If we instead called release_firmware() right away 971c92316bfSLuis R. Rodriguez * then we might miss on an opportunity of having a successful firmware 972c92316bfSLuis R. Rodriguez * request pass on the opportunity to be come a batched request. 973c92316bfSLuis R. Rodriguez */ 974c92316bfSLuis R. Rodriguez 975c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 976c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 977c92316bfSLuis R. Rodriguez if (req->sent) 978c92316bfSLuis R. Rodriguez wait_for_completion(&req->completion); 979c92316bfSLuis R. Rodriguez } 980c92316bfSLuis R. Rodriguez 981c92316bfSLuis R. Rodriguez /* Override any worker error if we had a general setup error */ 982c92316bfSLuis R. Rodriguez if (rc < 0) 983c92316bfSLuis R. Rodriguez test_fw_config->test_result = rc; 984c92316bfSLuis R. Rodriguez 985c92316bfSLuis R. Rodriguez out: 986c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 987c92316bfSLuis R. Rodriguez 988c92316bfSLuis R. Rodriguez return rc; 989c92316bfSLuis R. Rodriguez } 990c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests_async); 991c92316bfSLuis R. Rodriguez 992c92316bfSLuis R. Rodriguez static ssize_t test_result_show(struct device *dev, 993c92316bfSLuis R. Rodriguez struct device_attribute *attr, 994c92316bfSLuis R. Rodriguez char *buf) 995c92316bfSLuis R. Rodriguez { 996c92316bfSLuis R. Rodriguez return test_dev_config_show_int(buf, test_fw_config->test_result); 997c92316bfSLuis R. Rodriguez } 998c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(test_result); 999c92316bfSLuis R. Rodriguez 1000c92316bfSLuis R. Rodriguez static ssize_t release_all_firmware_store(struct device *dev, 1001c92316bfSLuis R. Rodriguez struct device_attribute *attr, 1002c92316bfSLuis R. Rodriguez const char *buf, size_t count) 1003c92316bfSLuis R. Rodriguez { 1004c92316bfSLuis R. Rodriguez test_release_all_firmware(); 1005c92316bfSLuis R. Rodriguez return count; 1006c92316bfSLuis R. Rodriguez } 1007c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(release_all_firmware); 1008c92316bfSLuis R. Rodriguez 1009c92316bfSLuis R. Rodriguez static ssize_t read_firmware_show(struct device *dev, 1010c92316bfSLuis R. Rodriguez struct device_attribute *attr, 1011c92316bfSLuis R. Rodriguez char *buf) 1012c92316bfSLuis R. Rodriguez { 1013c92316bfSLuis R. Rodriguez struct test_batched_req *req; 1014c92316bfSLuis R. Rodriguez u8 idx; 1015c92316bfSLuis R. Rodriguez ssize_t rc = 0; 1016c92316bfSLuis R. Rodriguez 1017c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 1018c92316bfSLuis R. Rodriguez 1019c92316bfSLuis R. Rodriguez idx = test_fw_config->read_fw_idx; 1020c92316bfSLuis R. Rodriguez if (idx >= test_fw_config->num_requests) { 1021c92316bfSLuis R. Rodriguez rc = -ERANGE; 1022c92316bfSLuis R. Rodriguez goto out; 1023c92316bfSLuis R. Rodriguez } 1024c92316bfSLuis R. Rodriguez 1025c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) { 1026c92316bfSLuis R. Rodriguez rc = -EINVAL; 1027c92316bfSLuis R. Rodriguez goto out; 1028c92316bfSLuis R. Rodriguez } 1029c92316bfSLuis R. Rodriguez 1030c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[idx]; 1031c92316bfSLuis R. Rodriguez if (!req->fw) { 1032c92316bfSLuis R. Rodriguez pr_err("#%u: failed to async load firmware\n", idx); 1033c92316bfSLuis R. Rodriguez rc = -ENOENT; 1034c92316bfSLuis R. Rodriguez goto out; 1035c92316bfSLuis R. Rodriguez } 1036c92316bfSLuis R. Rodriguez 1037c92316bfSLuis R. Rodriguez pr_info("#%u: loaded %zu\n", idx, req->fw->size); 1038c92316bfSLuis R. Rodriguez 1039c92316bfSLuis R. Rodriguez if (req->fw->size > PAGE_SIZE) { 1040c92316bfSLuis R. Rodriguez pr_err("Testing interface must use PAGE_SIZE firmware for now\n"); 1041c92316bfSLuis R. Rodriguez rc = -EINVAL; 10428bb0a886SColin Ian King goto out; 1043c92316bfSLuis R. Rodriguez } 1044c92316bfSLuis R. Rodriguez memcpy(buf, req->fw->data, req->fw->size); 1045c92316bfSLuis R. Rodriguez 1046c92316bfSLuis R. Rodriguez rc = req->fw->size; 1047c92316bfSLuis R. Rodriguez out: 1048c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 1049c92316bfSLuis R. Rodriguez 1050c92316bfSLuis R. Rodriguez return rc; 1051c92316bfSLuis R. Rodriguez } 1052c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(read_firmware); 1053c92316bfSLuis R. Rodriguez 1054083a93b0SLuis R. Rodriguez #define TEST_FW_DEV_ATTR(name) &dev_attr_##name.attr 1055083a93b0SLuis R. Rodriguez 1056083a93b0SLuis R. Rodriguez static struct attribute *test_dev_attrs[] = { 1057c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(reset), 1058c92316bfSLuis R. Rodriguez 1059c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config), 1060c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_name), 1061c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_num_requests), 10627feebfa4SScott Branden TEST_FW_DEV_ATTR(config_into_buf), 10635d90e05cSScott Branden TEST_FW_DEV_ATTR(config_buf_size), 10645d90e05cSScott Branden TEST_FW_DEV_ATTR(config_file_offset), 10655d90e05cSScott Branden TEST_FW_DEV_ATTR(config_partial), 1066c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_sync_direct), 1067c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_send_uevent), 1068c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_read_fw_idx), 1069c92316bfSLuis R. Rodriguez 1070c92316bfSLuis R. Rodriguez /* These don't use the config at all - they could be ported! */ 1071083a93b0SLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_request), 1072083a93b0SLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_async_request), 1073061132d2SLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_custom_fallback), 1074548193cbSHans de Goede #ifdef CONFIG_EFI_EMBEDDED_FIRMWARE 1075548193cbSHans de Goede TEST_FW_DEV_ATTR(trigger_request_platform), 1076548193cbSHans de Goede #endif 1077c92316bfSLuis R. Rodriguez 1078c92316bfSLuis R. Rodriguez /* These use the config and can use the test_result */ 1079c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_batched_requests), 1080c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_batched_requests_async), 1081c92316bfSLuis R. Rodriguez 1082c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(release_all_firmware), 1083c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(test_result), 1084c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(read_firmware), 1085083a93b0SLuis R. Rodriguez NULL, 1086083a93b0SLuis R. Rodriguez }; 1087083a93b0SLuis R. Rodriguez 1088083a93b0SLuis R. Rodriguez ATTRIBUTE_GROUPS(test_dev); 1089083a93b0SLuis R. Rodriguez 109067fd553cSLuis R. Rodriguez static struct miscdevice test_fw_misc_device = { 109167fd553cSLuis R. Rodriguez .minor = MISC_DYNAMIC_MINOR, 109267fd553cSLuis R. Rodriguez .name = "test_firmware", 109367fd553cSLuis R. Rodriguez .fops = &test_fw_fops, 1094083a93b0SLuis R. Rodriguez .groups = test_dev_groups, 109567fd553cSLuis R. Rodriguez }; 109667fd553cSLuis R. Rodriguez 10970a8adf58SKees Cook static int __init test_firmware_init(void) 10980a8adf58SKees Cook { 10990a8adf58SKees Cook int rc; 11000a8adf58SKees Cook 1101c92316bfSLuis R. Rodriguez test_fw_config = kzalloc(sizeof(struct test_config), GFP_KERNEL); 1102c92316bfSLuis R. Rodriguez if (!test_fw_config) 1103c92316bfSLuis R. Rodriguez return -ENOMEM; 1104c92316bfSLuis R. Rodriguez 1105c92316bfSLuis R. Rodriguez rc = __test_firmware_config_init(); 1106d4fddac5SWenwen Wang if (rc) { 1107d4fddac5SWenwen Wang kfree(test_fw_config); 1108d4fddac5SWenwen Wang pr_err("could not init firmware test config: %d\n", rc); 1109c92316bfSLuis R. Rodriguez return rc; 1110d4fddac5SWenwen Wang } 1111c92316bfSLuis R. Rodriguez 11120a8adf58SKees Cook rc = misc_register(&test_fw_misc_device); 11130a8adf58SKees Cook if (rc) { 1114c92316bfSLuis R. Rodriguez kfree(test_fw_config); 11150a8adf58SKees Cook pr_err("could not register misc device: %d\n", rc); 11160a8adf58SKees Cook return rc; 11170a8adf58SKees Cook } 1118eb910947SBrian Norris 11190a8adf58SKees Cook pr_warn("interface ready\n"); 11200a8adf58SKees Cook 11210a8adf58SKees Cook return 0; 11220a8adf58SKees Cook } 11230a8adf58SKees Cook 11240a8adf58SKees Cook module_init(test_firmware_init); 11250a8adf58SKees Cook 11260a8adf58SKees Cook static void __exit test_firmware_exit(void) 11270a8adf58SKees Cook { 1128c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 11290a8adf58SKees Cook release_firmware(test_firmware); 11300a8adf58SKees Cook misc_deregister(&test_fw_misc_device); 1131c92316bfSLuis R. Rodriguez __test_firmware_config_free(); 1132c92316bfSLuis R. Rodriguez kfree(test_fw_config); 1133c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 1134c92316bfSLuis R. Rodriguez 11350a8adf58SKees Cook pr_warn("removed interface\n"); 11360a8adf58SKees Cook } 11370a8adf58SKees Cook 11380a8adf58SKees Cook module_exit(test_firmware_exit); 11390a8adf58SKees Cook 11400a8adf58SKees Cook MODULE_AUTHOR("Kees Cook <[email protected]>"); 11410a8adf58SKees Cook MODULE_LICENSE("GPL"); 1142