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> 21*7feebfa4SScott 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> 27c92316bfSLuis R. Rodriguez 28c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NAME "test-firmware.bin" 29c92316bfSLuis R. Rodriguez #define TEST_FIRMWARE_NUM_REQS 4 30*7feebfa4SScott Branden #define TEST_FIRMWARE_BUF_SIZE SZ_1K 310a8adf58SKees Cook 320a8adf58SKees Cook static DEFINE_MUTEX(test_fw_mutex); 330a8adf58SKees Cook static const struct firmware *test_firmware; 340a8adf58SKees Cook 35c92316bfSLuis R. Rodriguez struct test_batched_req { 36c92316bfSLuis R. Rodriguez u8 idx; 37c92316bfSLuis R. Rodriguez int rc; 38c92316bfSLuis R. Rodriguez bool sent; 39c92316bfSLuis R. Rodriguez const struct firmware *fw; 40c92316bfSLuis R. Rodriguez const char *name; 41c92316bfSLuis R. Rodriguez struct completion completion; 42c92316bfSLuis R. Rodriguez struct task_struct *task; 43c92316bfSLuis R. Rodriguez struct device *dev; 44c92316bfSLuis R. Rodriguez }; 45c92316bfSLuis R. Rodriguez 46c92316bfSLuis R. Rodriguez /** 47c92316bfSLuis R. Rodriguez * test_config - represents configuration for the test for different triggers 48c92316bfSLuis R. Rodriguez * 49c92316bfSLuis R. Rodriguez * @name: the name of the firmware file to look for 50*7feebfa4SScott Branden * @into_buf: when the into_buf is used if this is true 51*7feebfa4SScott Branden * request_firmware_into_buf() will be used instead. 52c92316bfSLuis R. Rodriguez * @sync_direct: when the sync trigger is used if this is true 53c92316bfSLuis R. Rodriguez * request_firmware_direct() will be used instead. 54c92316bfSLuis R. Rodriguez * @send_uevent: whether or not to send a uevent for async requests 55c92316bfSLuis R. Rodriguez * @num_requests: number of requests to try per test case. This is trigger 56c92316bfSLuis R. Rodriguez * specific. 57c92316bfSLuis R. Rodriguez * @reqs: stores all requests information 58c92316bfSLuis R. Rodriguez * @read_fw_idx: index of thread from which we want to read firmware results 59c92316bfSLuis R. Rodriguez * from through the read_fw trigger. 60c92316bfSLuis R. Rodriguez * @test_result: a test may use this to collect the result from the call 61c92316bfSLuis R. Rodriguez * of the request_firmware*() calls used in their tests. In order of 62c92316bfSLuis R. Rodriguez * priority we always keep first any setup error. If no setup errors were 63c92316bfSLuis R. Rodriguez * found then we move on to the first error encountered while running the 64c92316bfSLuis R. Rodriguez * API. Note that for async calls this typically will be a successful 65c92316bfSLuis R. Rodriguez * result (0) unless of course you've used bogus parameters, or the system 66c92316bfSLuis R. Rodriguez * is out of memory. In the async case the callback is expected to do a 67c92316bfSLuis R. Rodriguez * bit more homework to figure out what happened, unfortunately the only 68c92316bfSLuis R. Rodriguez * information passed today on error is the fact that no firmware was 69c92316bfSLuis R. Rodriguez * found so we can only assume -ENOENT on async calls if the firmware is 70c92316bfSLuis R. Rodriguez * NULL. 71c92316bfSLuis R. Rodriguez * 72c92316bfSLuis R. Rodriguez * Errors you can expect: 73c92316bfSLuis R. Rodriguez * 74c92316bfSLuis R. Rodriguez * API specific: 75c92316bfSLuis R. Rodriguez * 76c92316bfSLuis R. Rodriguez * 0: success for sync, for async it means request was sent 77c92316bfSLuis R. Rodriguez * -EINVAL: invalid parameters or request 78c92316bfSLuis R. Rodriguez * -ENOENT: files not found 79c92316bfSLuis R. Rodriguez * 80c92316bfSLuis R. Rodriguez * System environment: 81c92316bfSLuis R. Rodriguez * 82c92316bfSLuis R. Rodriguez * -ENOMEM: memory pressure on system 83c92316bfSLuis R. Rodriguez * -ENODEV: out of number of devices to test 84c92316bfSLuis R. Rodriguez * -EINVAL: an unexpected error has occurred 85c92316bfSLuis R. Rodriguez * @req_firmware: if @sync_direct is true this is set to 86c92316bfSLuis R. Rodriguez * request_firmware_direct(), otherwise request_firmware() 87c92316bfSLuis R. Rodriguez */ 88c92316bfSLuis R. Rodriguez struct test_config { 89c92316bfSLuis R. Rodriguez char *name; 90*7feebfa4SScott Branden bool into_buf; 91c92316bfSLuis R. Rodriguez bool sync_direct; 92c92316bfSLuis R. Rodriguez bool send_uevent; 93c92316bfSLuis R. Rodriguez u8 num_requests; 94c92316bfSLuis R. Rodriguez u8 read_fw_idx; 95c92316bfSLuis R. Rodriguez 96c92316bfSLuis R. Rodriguez /* 97c92316bfSLuis R. Rodriguez * These below don't belong her but we'll move them once we create 98c92316bfSLuis R. Rodriguez * a struct fw_test_device and stuff the misc_dev under there later. 99c92316bfSLuis R. Rodriguez */ 100c92316bfSLuis R. Rodriguez struct test_batched_req *reqs; 101c92316bfSLuis R. Rodriguez int test_result; 102c92316bfSLuis R. Rodriguez int (*req_firmware)(const struct firmware **fw, const char *name, 103c92316bfSLuis R. Rodriguez struct device *device); 104c92316bfSLuis R. Rodriguez }; 105c92316bfSLuis R. Rodriguez 10676f8ab1bSWei Yongjun static struct test_config *test_fw_config; 107c92316bfSLuis R. Rodriguez 1080a8adf58SKees Cook static ssize_t test_fw_misc_read(struct file *f, char __user *buf, 1090a8adf58SKees Cook size_t size, loff_t *offset) 1100a8adf58SKees Cook { 1110a8adf58SKees Cook ssize_t rc = 0; 1120a8adf58SKees Cook 1130a8adf58SKees Cook mutex_lock(&test_fw_mutex); 1140a8adf58SKees Cook if (test_firmware) 1150a8adf58SKees Cook rc = simple_read_from_buffer(buf, size, offset, 1160a8adf58SKees Cook test_firmware->data, 1170a8adf58SKees Cook test_firmware->size); 1180a8adf58SKees Cook mutex_unlock(&test_fw_mutex); 1190a8adf58SKees Cook return rc; 1200a8adf58SKees Cook } 1210a8adf58SKees Cook 1220a8adf58SKees Cook static const struct file_operations test_fw_fops = { 1230a8adf58SKees Cook .owner = THIS_MODULE, 1240a8adf58SKees Cook .read = test_fw_misc_read, 1250a8adf58SKees Cook }; 1260a8adf58SKees Cook 127c92316bfSLuis R. Rodriguez static void __test_release_all_firmware(void) 128c92316bfSLuis R. Rodriguez { 129c92316bfSLuis R. Rodriguez struct test_batched_req *req; 130c92316bfSLuis R. Rodriguez u8 i; 131c92316bfSLuis R. Rodriguez 132c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) 133c92316bfSLuis R. Rodriguez return; 134c92316bfSLuis R. Rodriguez 135c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 136c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 137c92316bfSLuis R. Rodriguez if (req->fw) 138c92316bfSLuis R. Rodriguez release_firmware(req->fw); 139c92316bfSLuis R. Rodriguez } 140c92316bfSLuis R. Rodriguez 141c92316bfSLuis R. Rodriguez vfree(test_fw_config->reqs); 142c92316bfSLuis R. Rodriguez test_fw_config->reqs = NULL; 143c92316bfSLuis R. Rodriguez } 144c92316bfSLuis R. Rodriguez 145c92316bfSLuis R. Rodriguez static void test_release_all_firmware(void) 146c92316bfSLuis R. Rodriguez { 147c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 148c92316bfSLuis R. Rodriguez __test_release_all_firmware(); 149c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 150c92316bfSLuis R. Rodriguez } 151c92316bfSLuis R. Rodriguez 152c92316bfSLuis R. Rodriguez 153c92316bfSLuis R. Rodriguez static void __test_firmware_config_free(void) 154c92316bfSLuis R. Rodriguez { 155c92316bfSLuis R. Rodriguez __test_release_all_firmware(); 156c92316bfSLuis R. Rodriguez kfree_const(test_fw_config->name); 157c92316bfSLuis R. Rodriguez test_fw_config->name = NULL; 158c92316bfSLuis R. Rodriguez } 159c92316bfSLuis R. Rodriguez 160c92316bfSLuis R. Rodriguez /* 161c92316bfSLuis R. Rodriguez * XXX: move to kstrncpy() once merged. 162c92316bfSLuis R. Rodriguez * 163c92316bfSLuis R. Rodriguez * Users should use kfree_const() when freeing these. 164c92316bfSLuis R. Rodriguez */ 165c92316bfSLuis R. Rodriguez static int __kstrncpy(char **dst, const char *name, size_t count, gfp_t gfp) 166c92316bfSLuis R. Rodriguez { 167c92316bfSLuis R. Rodriguez *dst = kstrndup(name, count, gfp); 168c92316bfSLuis R. Rodriguez if (!*dst) 169c92316bfSLuis R. Rodriguez return -ENOSPC; 170c92316bfSLuis R. Rodriguez return count; 171c92316bfSLuis R. Rodriguez } 172c92316bfSLuis R. Rodriguez 173c92316bfSLuis R. Rodriguez static int __test_firmware_config_init(void) 174c92316bfSLuis R. Rodriguez { 175c92316bfSLuis R. Rodriguez int ret; 176c92316bfSLuis R. Rodriguez 177c92316bfSLuis R. Rodriguez ret = __kstrncpy(&test_fw_config->name, TEST_FIRMWARE_NAME, 178c92316bfSLuis R. Rodriguez strlen(TEST_FIRMWARE_NAME), GFP_KERNEL); 179c92316bfSLuis R. Rodriguez if (ret < 0) 180c92316bfSLuis R. Rodriguez goto out; 181c92316bfSLuis R. Rodriguez 182c92316bfSLuis R. Rodriguez test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS; 183c92316bfSLuis R. Rodriguez test_fw_config->send_uevent = true; 184*7feebfa4SScott Branden test_fw_config->into_buf = false; 185c92316bfSLuis R. Rodriguez test_fw_config->sync_direct = false; 186c92316bfSLuis R. Rodriguez test_fw_config->req_firmware = request_firmware; 187c92316bfSLuis R. Rodriguez test_fw_config->test_result = 0; 188c92316bfSLuis R. Rodriguez test_fw_config->reqs = NULL; 189c92316bfSLuis R. Rodriguez 190c92316bfSLuis R. Rodriguez return 0; 191c92316bfSLuis R. Rodriguez 192c92316bfSLuis R. Rodriguez out: 193c92316bfSLuis R. Rodriguez __test_firmware_config_free(); 194c92316bfSLuis R. Rodriguez return ret; 195c92316bfSLuis R. Rodriguez } 196c92316bfSLuis R. Rodriguez 197c92316bfSLuis R. Rodriguez static ssize_t reset_store(struct device *dev, 198c92316bfSLuis R. Rodriguez struct device_attribute *attr, 199c92316bfSLuis R. Rodriguez const char *buf, size_t count) 200c92316bfSLuis R. Rodriguez { 201c92316bfSLuis R. Rodriguez int ret; 202c92316bfSLuis R. Rodriguez 203c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 204c92316bfSLuis R. Rodriguez 205c92316bfSLuis R. Rodriguez __test_firmware_config_free(); 206c92316bfSLuis R. Rodriguez 207c92316bfSLuis R. Rodriguez ret = __test_firmware_config_init(); 208c92316bfSLuis R. Rodriguez if (ret < 0) { 209c92316bfSLuis R. Rodriguez ret = -ENOMEM; 210c92316bfSLuis R. Rodriguez pr_err("could not alloc settings for config trigger: %d\n", 211c92316bfSLuis R. Rodriguez ret); 212c92316bfSLuis R. Rodriguez goto out; 213c92316bfSLuis R. Rodriguez } 214c92316bfSLuis R. Rodriguez 215c92316bfSLuis R. Rodriguez pr_info("reset\n"); 216c92316bfSLuis R. Rodriguez ret = count; 217c92316bfSLuis R. Rodriguez 218c92316bfSLuis R. Rodriguez out: 219c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 220c92316bfSLuis R. Rodriguez 221c92316bfSLuis R. Rodriguez return ret; 222c92316bfSLuis R. Rodriguez } 223c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(reset); 224c92316bfSLuis R. Rodriguez 225c92316bfSLuis R. Rodriguez static ssize_t config_show(struct device *dev, 226c92316bfSLuis R. Rodriguez struct device_attribute *attr, 227c92316bfSLuis R. Rodriguez char *buf) 228c92316bfSLuis R. Rodriguez { 229c92316bfSLuis R. Rodriguez int len = 0; 230c92316bfSLuis R. Rodriguez 231c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 232c92316bfSLuis R. Rodriguez 233bd17cc5aSDan Carpenter len += scnprintf(buf, PAGE_SIZE - len, 234c92316bfSLuis R. Rodriguez "Custom trigger configuration for: %s\n", 235c92316bfSLuis R. Rodriguez dev_name(dev)); 236c92316bfSLuis R. Rodriguez 237c92316bfSLuis R. Rodriguez if (test_fw_config->name) 238bd17cc5aSDan Carpenter len += scnprintf(buf+len, PAGE_SIZE - len, 239c92316bfSLuis R. Rodriguez "name:\t%s\n", 240c92316bfSLuis R. Rodriguez test_fw_config->name); 241c92316bfSLuis R. Rodriguez else 242bd17cc5aSDan Carpenter len += scnprintf(buf+len, PAGE_SIZE - len, 243c92316bfSLuis R. Rodriguez "name:\tEMTPY\n"); 244c92316bfSLuis R. Rodriguez 245bd17cc5aSDan Carpenter len += scnprintf(buf+len, PAGE_SIZE - len, 246c92316bfSLuis R. Rodriguez "num_requests:\t%u\n", test_fw_config->num_requests); 247c92316bfSLuis R. Rodriguez 248bd17cc5aSDan Carpenter len += scnprintf(buf+len, PAGE_SIZE - len, 249c92316bfSLuis R. Rodriguez "send_uevent:\t\t%s\n", 250c92316bfSLuis R. Rodriguez test_fw_config->send_uevent ? 251c92316bfSLuis R. Rodriguez "FW_ACTION_HOTPLUG" : 252c92316bfSLuis R. Rodriguez "FW_ACTION_NOHOTPLUG"); 253bd17cc5aSDan Carpenter len += scnprintf(buf+len, PAGE_SIZE - len, 254*7feebfa4SScott Branden "into_buf:\t\t%s\n", 255*7feebfa4SScott Branden test_fw_config->into_buf ? "true" : "false"); 256*7feebfa4SScott Branden len += scnprintf(buf+len, PAGE_SIZE - len, 257c92316bfSLuis R. Rodriguez "sync_direct:\t\t%s\n", 258c92316bfSLuis R. Rodriguez test_fw_config->sync_direct ? "true" : "false"); 259bd17cc5aSDan Carpenter len += scnprintf(buf+len, PAGE_SIZE - len, 260c92316bfSLuis R. Rodriguez "read_fw_idx:\t%u\n", test_fw_config->read_fw_idx); 261c92316bfSLuis R. Rodriguez 262c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 263c92316bfSLuis R. Rodriguez 264c92316bfSLuis R. Rodriguez return len; 265c92316bfSLuis R. Rodriguez } 266c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(config); 267c92316bfSLuis R. Rodriguez 268c92316bfSLuis R. Rodriguez static ssize_t config_name_store(struct device *dev, 269c92316bfSLuis R. Rodriguez struct device_attribute *attr, 270c92316bfSLuis R. Rodriguez const char *buf, size_t count) 271c92316bfSLuis R. Rodriguez { 272c92316bfSLuis R. Rodriguez int ret; 273c92316bfSLuis R. Rodriguez 274c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 275c92316bfSLuis R. Rodriguez kfree_const(test_fw_config->name); 276c92316bfSLuis R. Rodriguez ret = __kstrncpy(&test_fw_config->name, buf, count, GFP_KERNEL); 277c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 278c92316bfSLuis R. Rodriguez 279c92316bfSLuis R. Rodriguez return ret; 280c92316bfSLuis R. Rodriguez } 281c92316bfSLuis R. Rodriguez 282c92316bfSLuis R. Rodriguez /* 283c92316bfSLuis R. Rodriguez * As per sysfs_kf_seq_show() the buf is max PAGE_SIZE. 284c92316bfSLuis R. Rodriguez */ 285c92316bfSLuis R. Rodriguez static ssize_t config_test_show_str(char *dst, 286c92316bfSLuis R. Rodriguez char *src) 287c92316bfSLuis R. Rodriguez { 288c92316bfSLuis R. Rodriguez int len; 289c92316bfSLuis R. Rodriguez 290c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 291c92316bfSLuis R. Rodriguez len = snprintf(dst, PAGE_SIZE, "%s\n", src); 292c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 293c92316bfSLuis R. Rodriguez 294c92316bfSLuis R. Rodriguez return len; 295c92316bfSLuis R. Rodriguez } 296c92316bfSLuis R. Rodriguez 297c92316bfSLuis R. Rodriguez static int test_dev_config_update_bool(const char *buf, size_t size, 298c92316bfSLuis R. Rodriguez bool *cfg) 299c92316bfSLuis R. Rodriguez { 300c92316bfSLuis R. Rodriguez int ret; 301c92316bfSLuis R. Rodriguez 302c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 303c92316bfSLuis R. Rodriguez if (strtobool(buf, cfg) < 0) 304c92316bfSLuis R. Rodriguez ret = -EINVAL; 305c92316bfSLuis R. Rodriguez else 306c92316bfSLuis R. Rodriguez ret = size; 307c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 308c92316bfSLuis R. Rodriguez 309c92316bfSLuis R. Rodriguez return ret; 310c92316bfSLuis R. Rodriguez } 311c92316bfSLuis R. Rodriguez 312c92316bfSLuis R. Rodriguez static ssize_t 313c92316bfSLuis R. Rodriguez test_dev_config_show_bool(char *buf, 314c92316bfSLuis R. Rodriguez bool config) 315c92316bfSLuis R. Rodriguez { 316c92316bfSLuis R. Rodriguez bool val; 317c92316bfSLuis R. Rodriguez 318c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 319c92316bfSLuis R. Rodriguez val = config; 320c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 321c92316bfSLuis R. Rodriguez 322c92316bfSLuis R. Rodriguez return snprintf(buf, PAGE_SIZE, "%d\n", val); 323c92316bfSLuis R. Rodriguez } 324c92316bfSLuis R. Rodriguez 325c92316bfSLuis R. Rodriguez static ssize_t test_dev_config_show_int(char *buf, int cfg) 326c92316bfSLuis R. Rodriguez { 327c92316bfSLuis R. Rodriguez int val; 328c92316bfSLuis R. Rodriguez 329c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 330c92316bfSLuis R. Rodriguez val = cfg; 331c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 332c92316bfSLuis R. Rodriguez 333c92316bfSLuis R. Rodriguez return snprintf(buf, PAGE_SIZE, "%d\n", val); 334c92316bfSLuis R. Rodriguez } 335c92316bfSLuis R. Rodriguez 336c92316bfSLuis R. Rodriguez static int test_dev_config_update_u8(const char *buf, size_t size, u8 *cfg) 337c92316bfSLuis R. Rodriguez { 338c92316bfSLuis R. Rodriguez int ret; 339c92316bfSLuis R. Rodriguez long new; 340c92316bfSLuis R. Rodriguez 341c92316bfSLuis R. Rodriguez ret = kstrtol(buf, 10, &new); 342c92316bfSLuis R. Rodriguez if (ret) 343c92316bfSLuis R. Rodriguez return ret; 344c92316bfSLuis R. Rodriguez 345c92316bfSLuis R. Rodriguez if (new > U8_MAX) 346c92316bfSLuis R. Rodriguez return -EINVAL; 347c92316bfSLuis R. Rodriguez 348c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 349c92316bfSLuis R. Rodriguez *(u8 *)cfg = new; 350c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 351c92316bfSLuis R. Rodriguez 352c92316bfSLuis R. Rodriguez /* Always return full write size even if we didn't consume all */ 353c92316bfSLuis R. Rodriguez return size; 354c92316bfSLuis R. Rodriguez } 355c92316bfSLuis R. Rodriguez 356c92316bfSLuis R. Rodriguez static ssize_t test_dev_config_show_u8(char *buf, u8 cfg) 357c92316bfSLuis R. Rodriguez { 358c92316bfSLuis R. Rodriguez u8 val; 359c92316bfSLuis R. Rodriguez 360c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 361c92316bfSLuis R. Rodriguez val = cfg; 362c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 363c92316bfSLuis R. Rodriguez 364c92316bfSLuis R. Rodriguez return snprintf(buf, PAGE_SIZE, "%u\n", val); 365c92316bfSLuis R. Rodriguez } 366c92316bfSLuis R. Rodriguez 367c92316bfSLuis R. Rodriguez static ssize_t config_name_show(struct device *dev, 368c92316bfSLuis R. Rodriguez struct device_attribute *attr, 369c92316bfSLuis R. Rodriguez char *buf) 370c92316bfSLuis R. Rodriguez { 371c92316bfSLuis R. Rodriguez return config_test_show_str(buf, test_fw_config->name); 372c92316bfSLuis R. Rodriguez } 373b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_name); 374c92316bfSLuis R. Rodriguez 375c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_store(struct device *dev, 376c92316bfSLuis R. Rodriguez struct device_attribute *attr, 377c92316bfSLuis R. Rodriguez const char *buf, size_t count) 378c92316bfSLuis R. Rodriguez { 379c92316bfSLuis R. Rodriguez int rc; 380c92316bfSLuis R. Rodriguez 381c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 382c92316bfSLuis R. Rodriguez if (test_fw_config->reqs) { 383c92316bfSLuis R. Rodriguez pr_err("Must call release_all_firmware prior to changing config\n"); 384c92316bfSLuis R. Rodriguez rc = -EINVAL; 385a5e19233SWei Yongjun mutex_unlock(&test_fw_mutex); 386c92316bfSLuis R. Rodriguez goto out; 387c92316bfSLuis R. Rodriguez } 388c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 389c92316bfSLuis R. Rodriguez 390c92316bfSLuis R. Rodriguez rc = test_dev_config_update_u8(buf, count, 391c92316bfSLuis R. Rodriguez &test_fw_config->num_requests); 392c92316bfSLuis R. Rodriguez 393c92316bfSLuis R. Rodriguez out: 394c92316bfSLuis R. Rodriguez return rc; 395c92316bfSLuis R. Rodriguez } 396c92316bfSLuis R. Rodriguez 397c92316bfSLuis R. Rodriguez static ssize_t config_num_requests_show(struct device *dev, 398c92316bfSLuis R. Rodriguez struct device_attribute *attr, 399c92316bfSLuis R. Rodriguez char *buf) 400c92316bfSLuis R. Rodriguez { 401c92316bfSLuis R. Rodriguez return test_dev_config_show_u8(buf, test_fw_config->num_requests); 402c92316bfSLuis R. Rodriguez } 403b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_num_requests); 404c92316bfSLuis R. Rodriguez 405*7feebfa4SScott Branden static ssize_t config_into_buf_store(struct device *dev, 406*7feebfa4SScott Branden struct device_attribute *attr, 407*7feebfa4SScott Branden const char *buf, size_t count) 408*7feebfa4SScott Branden { 409*7feebfa4SScott Branden return test_dev_config_update_bool(buf, 410*7feebfa4SScott Branden count, 411*7feebfa4SScott Branden &test_fw_config->into_buf); 412*7feebfa4SScott Branden } 413*7feebfa4SScott Branden 414*7feebfa4SScott Branden static ssize_t config_into_buf_show(struct device *dev, 415*7feebfa4SScott Branden struct device_attribute *attr, 416*7feebfa4SScott Branden char *buf) 417*7feebfa4SScott Branden { 418*7feebfa4SScott Branden return test_dev_config_show_bool(buf, test_fw_config->into_buf); 419*7feebfa4SScott Branden } 420*7feebfa4SScott Branden static DEVICE_ATTR_RW(config_into_buf); 421*7feebfa4SScott Branden 422c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_store(struct device *dev, 423c92316bfSLuis R. Rodriguez struct device_attribute *attr, 424c92316bfSLuis R. Rodriguez const char *buf, size_t count) 425c92316bfSLuis R. Rodriguez { 426c92316bfSLuis R. Rodriguez int rc = test_dev_config_update_bool(buf, count, 427c92316bfSLuis R. Rodriguez &test_fw_config->sync_direct); 428c92316bfSLuis R. Rodriguez 429c92316bfSLuis R. Rodriguez if (rc == count) 430c92316bfSLuis R. Rodriguez test_fw_config->req_firmware = test_fw_config->sync_direct ? 431c92316bfSLuis R. Rodriguez request_firmware_direct : 432c92316bfSLuis R. Rodriguez request_firmware; 433c92316bfSLuis R. Rodriguez return rc; 434c92316bfSLuis R. Rodriguez } 435c92316bfSLuis R. Rodriguez 436c92316bfSLuis R. Rodriguez static ssize_t config_sync_direct_show(struct device *dev, 437c92316bfSLuis R. Rodriguez struct device_attribute *attr, 438c92316bfSLuis R. Rodriguez char *buf) 439c92316bfSLuis R. Rodriguez { 440c92316bfSLuis R. Rodriguez return test_dev_config_show_bool(buf, test_fw_config->sync_direct); 441c92316bfSLuis R. Rodriguez } 442b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_sync_direct); 443c92316bfSLuis R. Rodriguez 444c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_store(struct device *dev, 445c92316bfSLuis R. Rodriguez struct device_attribute *attr, 446c92316bfSLuis R. Rodriguez const char *buf, size_t count) 447c92316bfSLuis R. Rodriguez { 448c92316bfSLuis R. Rodriguez return test_dev_config_update_bool(buf, count, 449c92316bfSLuis R. Rodriguez &test_fw_config->send_uevent); 450c92316bfSLuis R. Rodriguez } 451c92316bfSLuis R. Rodriguez 452c92316bfSLuis R. Rodriguez static ssize_t config_send_uevent_show(struct device *dev, 453c92316bfSLuis R. Rodriguez struct device_attribute *attr, 454c92316bfSLuis R. Rodriguez char *buf) 455c92316bfSLuis R. Rodriguez { 456c92316bfSLuis R. Rodriguez return test_dev_config_show_bool(buf, test_fw_config->send_uevent); 457c92316bfSLuis R. Rodriguez } 458b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_send_uevent); 459c92316bfSLuis R. Rodriguez 460c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_store(struct device *dev, 461c92316bfSLuis R. Rodriguez struct device_attribute *attr, 462c92316bfSLuis R. Rodriguez const char *buf, size_t count) 463c92316bfSLuis R. Rodriguez { 464c92316bfSLuis R. Rodriguez return test_dev_config_update_u8(buf, count, 465c92316bfSLuis R. Rodriguez &test_fw_config->read_fw_idx); 466c92316bfSLuis R. Rodriguez } 467c92316bfSLuis R. Rodriguez 468c92316bfSLuis R. Rodriguez static ssize_t config_read_fw_idx_show(struct device *dev, 469c92316bfSLuis R. Rodriguez struct device_attribute *attr, 470c92316bfSLuis R. Rodriguez char *buf) 471c92316bfSLuis R. Rodriguez { 472c92316bfSLuis R. Rodriguez return test_dev_config_show_u8(buf, test_fw_config->read_fw_idx); 473c92316bfSLuis R. Rodriguez } 474b6b996b6SJoe Perches static DEVICE_ATTR_RW(config_read_fw_idx); 475c92316bfSLuis R. Rodriguez 476c92316bfSLuis R. Rodriguez 4770a8adf58SKees Cook static ssize_t trigger_request_store(struct device *dev, 4780a8adf58SKees Cook struct device_attribute *attr, 4790a8adf58SKees Cook const char *buf, size_t count) 4800a8adf58SKees Cook { 4810a8adf58SKees Cook int rc; 4820a8adf58SKees Cook char *name; 4830a8adf58SKees Cook 484be4a1326SBrian Norris name = kstrndup(buf, count, GFP_KERNEL); 4850a8adf58SKees Cook if (!name) 4860a8adf58SKees Cook return -ENOSPC; 4870a8adf58SKees Cook 4880a8adf58SKees Cook pr_info("loading '%s'\n", name); 4890a8adf58SKees Cook 4900a8adf58SKees Cook mutex_lock(&test_fw_mutex); 4910a8adf58SKees Cook release_firmware(test_firmware); 4920a8adf58SKees Cook test_firmware = NULL; 4930a8adf58SKees Cook rc = request_firmware(&test_firmware, name, dev); 49447e0bbb7SBrian Norris if (rc) { 4950a8adf58SKees Cook pr_info("load of '%s' failed: %d\n", name, rc); 49647e0bbb7SBrian Norris goto out; 49747e0bbb7SBrian Norris } 49847e0bbb7SBrian Norris pr_info("loaded: %zu\n", test_firmware->size); 49947e0bbb7SBrian Norris rc = count; 50047e0bbb7SBrian Norris 50147e0bbb7SBrian Norris out: 5020a8adf58SKees Cook mutex_unlock(&test_fw_mutex); 5030a8adf58SKees Cook 5040a8adf58SKees Cook kfree(name); 5050a8adf58SKees Cook 50647e0bbb7SBrian Norris return rc; 5070a8adf58SKees Cook } 5080a8adf58SKees Cook static DEVICE_ATTR_WO(trigger_request); 5090a8adf58SKees Cook 510eb910947SBrian Norris static DECLARE_COMPLETION(async_fw_done); 511eb910947SBrian Norris 512eb910947SBrian Norris static void trigger_async_request_cb(const struct firmware *fw, void *context) 513eb910947SBrian Norris { 514eb910947SBrian Norris test_firmware = fw; 515eb910947SBrian Norris complete(&async_fw_done); 516eb910947SBrian Norris } 517eb910947SBrian Norris 518eb910947SBrian Norris static ssize_t trigger_async_request_store(struct device *dev, 519eb910947SBrian Norris struct device_attribute *attr, 520eb910947SBrian Norris const char *buf, size_t count) 521eb910947SBrian Norris { 522eb910947SBrian Norris int rc; 523eb910947SBrian Norris char *name; 524eb910947SBrian Norris 525eb910947SBrian Norris name = kstrndup(buf, count, GFP_KERNEL); 526eb910947SBrian Norris if (!name) 527eb910947SBrian Norris return -ENOSPC; 528eb910947SBrian Norris 529eb910947SBrian Norris pr_info("loading '%s'\n", name); 530eb910947SBrian Norris 531eb910947SBrian Norris mutex_lock(&test_fw_mutex); 532eb910947SBrian Norris release_firmware(test_firmware); 533eb910947SBrian Norris test_firmware = NULL; 534eb910947SBrian Norris rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL, 535eb910947SBrian Norris NULL, trigger_async_request_cb); 536eb910947SBrian Norris if (rc) { 537eb910947SBrian Norris pr_info("async load of '%s' failed: %d\n", name, rc); 538eb910947SBrian Norris kfree(name); 539eb910947SBrian Norris goto out; 540eb910947SBrian Norris } 541eb910947SBrian Norris /* Free 'name' ASAP, to test for race conditions */ 542eb910947SBrian Norris kfree(name); 543eb910947SBrian Norris 544eb910947SBrian Norris wait_for_completion(&async_fw_done); 545eb910947SBrian Norris 546eb910947SBrian Norris if (test_firmware) { 547eb910947SBrian Norris pr_info("loaded: %zu\n", test_firmware->size); 548eb910947SBrian Norris rc = count; 549eb910947SBrian Norris } else { 550eb910947SBrian Norris pr_err("failed to async load firmware\n"); 551*7feebfa4SScott Branden rc = -ENOMEM; 552eb910947SBrian Norris } 553eb910947SBrian Norris 554eb910947SBrian Norris out: 555eb910947SBrian Norris mutex_unlock(&test_fw_mutex); 556eb910947SBrian Norris 557eb910947SBrian Norris return rc; 558eb910947SBrian Norris } 559eb910947SBrian Norris static DEVICE_ATTR_WO(trigger_async_request); 560eb910947SBrian Norris 561061132d2SLuis R. Rodriguez static ssize_t trigger_custom_fallback_store(struct device *dev, 562061132d2SLuis R. Rodriguez struct device_attribute *attr, 563061132d2SLuis R. Rodriguez const char *buf, size_t count) 564061132d2SLuis R. Rodriguez { 565061132d2SLuis R. Rodriguez int rc; 566061132d2SLuis R. Rodriguez char *name; 567061132d2SLuis R. Rodriguez 568061132d2SLuis R. Rodriguez name = kstrndup(buf, count, GFP_KERNEL); 569061132d2SLuis R. Rodriguez if (!name) 570061132d2SLuis R. Rodriguez return -ENOSPC; 571061132d2SLuis R. Rodriguez 572061132d2SLuis R. Rodriguez pr_info("loading '%s' using custom fallback mechanism\n", name); 573061132d2SLuis R. Rodriguez 574061132d2SLuis R. Rodriguez mutex_lock(&test_fw_mutex); 575061132d2SLuis R. Rodriguez release_firmware(test_firmware); 576061132d2SLuis R. Rodriguez test_firmware = NULL; 577061132d2SLuis R. Rodriguez rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, name, 578061132d2SLuis R. Rodriguez dev, GFP_KERNEL, NULL, 579061132d2SLuis R. Rodriguez trigger_async_request_cb); 580061132d2SLuis R. Rodriguez if (rc) { 581061132d2SLuis R. Rodriguez pr_info("async load of '%s' failed: %d\n", name, rc); 582061132d2SLuis R. Rodriguez kfree(name); 583061132d2SLuis R. Rodriguez goto out; 584061132d2SLuis R. Rodriguez } 585061132d2SLuis R. Rodriguez /* Free 'name' ASAP, to test for race conditions */ 586061132d2SLuis R. Rodriguez kfree(name); 587061132d2SLuis R. Rodriguez 588061132d2SLuis R. Rodriguez wait_for_completion(&async_fw_done); 589061132d2SLuis R. Rodriguez 590061132d2SLuis R. Rodriguez if (test_firmware) { 591061132d2SLuis R. Rodriguez pr_info("loaded: %zu\n", test_firmware->size); 592061132d2SLuis R. Rodriguez rc = count; 593061132d2SLuis R. Rodriguez } else { 594061132d2SLuis R. Rodriguez pr_err("failed to async load firmware\n"); 595061132d2SLuis R. Rodriguez rc = -ENODEV; 596061132d2SLuis R. Rodriguez } 597061132d2SLuis R. Rodriguez 598061132d2SLuis R. Rodriguez out: 599061132d2SLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 600061132d2SLuis R. Rodriguez 601061132d2SLuis R. Rodriguez return rc; 602061132d2SLuis R. Rodriguez } 603061132d2SLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_custom_fallback); 604061132d2SLuis R. Rodriguez 605c92316bfSLuis R. Rodriguez static int test_fw_run_batch_request(void *data) 606c92316bfSLuis R. Rodriguez { 607c92316bfSLuis R. Rodriguez struct test_batched_req *req = data; 608c92316bfSLuis R. Rodriguez 609c92316bfSLuis R. Rodriguez if (!req) { 610c92316bfSLuis R. Rodriguez test_fw_config->test_result = -EINVAL; 611c92316bfSLuis R. Rodriguez return -EINVAL; 612c92316bfSLuis R. Rodriguez } 613c92316bfSLuis R. Rodriguez 614*7feebfa4SScott Branden if (test_fw_config->into_buf) { 615*7feebfa4SScott Branden void *test_buf; 616*7feebfa4SScott Branden 617*7feebfa4SScott Branden test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL); 618*7feebfa4SScott Branden if (!test_buf) 619*7feebfa4SScott Branden return -ENOSPC; 620*7feebfa4SScott Branden 621*7feebfa4SScott Branden req->rc = request_firmware_into_buf(&req->fw, 622*7feebfa4SScott Branden req->name, 623*7feebfa4SScott Branden req->dev, 624*7feebfa4SScott Branden test_buf, 625*7feebfa4SScott Branden TEST_FIRMWARE_BUF_SIZE); 626*7feebfa4SScott Branden if (!req->fw) 627*7feebfa4SScott Branden kfree(test_buf); 628*7feebfa4SScott Branden } else { 629*7feebfa4SScott Branden req->rc = test_fw_config->req_firmware(&req->fw, 630*7feebfa4SScott Branden req->name, 631*7feebfa4SScott Branden req->dev); 632*7feebfa4SScott Branden } 633*7feebfa4SScott Branden 634c92316bfSLuis R. Rodriguez if (req->rc) { 635c92316bfSLuis R. Rodriguez pr_info("#%u: batched sync load failed: %d\n", 636c92316bfSLuis R. Rodriguez req->idx, req->rc); 637c92316bfSLuis R. Rodriguez if (!test_fw_config->test_result) 638c92316bfSLuis R. Rodriguez test_fw_config->test_result = req->rc; 639c92316bfSLuis R. Rodriguez } else if (req->fw) { 640c92316bfSLuis R. Rodriguez req->sent = true; 641c92316bfSLuis R. Rodriguez pr_info("#%u: batched sync loaded %zu\n", 642c92316bfSLuis R. Rodriguez req->idx, req->fw->size); 643c92316bfSLuis R. Rodriguez } 644c92316bfSLuis R. Rodriguez complete(&req->completion); 645c92316bfSLuis R. Rodriguez 646c92316bfSLuis R. Rodriguez req->task = NULL; 647c92316bfSLuis R. Rodriguez 648c92316bfSLuis R. Rodriguez return 0; 649c92316bfSLuis R. Rodriguez } 650c92316bfSLuis R. Rodriguez 651c92316bfSLuis R. Rodriguez /* 652c92316bfSLuis R. Rodriguez * We use a kthread as otherwise the kernel serializes all our sync requests 653c92316bfSLuis R. Rodriguez * and we would not be able to mimic batched requests on a sync call. Batched 654c92316bfSLuis R. Rodriguez * requests on a sync call can for instance happen on a device driver when 655c92316bfSLuis R. Rodriguez * multiple cards are used and firmware loading happens outside of probe. 656c92316bfSLuis R. Rodriguez */ 657c92316bfSLuis R. Rodriguez static ssize_t trigger_batched_requests_store(struct device *dev, 658c92316bfSLuis R. Rodriguez struct device_attribute *attr, 659c92316bfSLuis R. Rodriguez const char *buf, size_t count) 660c92316bfSLuis R. Rodriguez { 661c92316bfSLuis R. Rodriguez struct test_batched_req *req; 662c92316bfSLuis R. Rodriguez int rc; 663c92316bfSLuis R. Rodriguez u8 i; 664c92316bfSLuis R. Rodriguez 665c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 666c92316bfSLuis R. Rodriguez 667fad953ceSKees Cook test_fw_config->reqs = 668fad953ceSKees Cook vzalloc(array3_size(sizeof(struct test_batched_req), 669fad953ceSKees Cook test_fw_config->num_requests, 2)); 670c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) { 671c92316bfSLuis R. Rodriguez rc = -ENOMEM; 672c92316bfSLuis R. Rodriguez goto out_unlock; 673c92316bfSLuis R. Rodriguez } 674c92316bfSLuis R. Rodriguez 675c92316bfSLuis R. Rodriguez pr_info("batched sync firmware loading '%s' %u times\n", 676c92316bfSLuis R. Rodriguez test_fw_config->name, test_fw_config->num_requests); 677c92316bfSLuis R. Rodriguez 678c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 679c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 680c92316bfSLuis R. Rodriguez req->fw = NULL; 681c92316bfSLuis R. Rodriguez req->idx = i; 682c92316bfSLuis R. Rodriguez req->name = test_fw_config->name; 683c92316bfSLuis R. Rodriguez req->dev = dev; 684c92316bfSLuis R. Rodriguez init_completion(&req->completion); 685c92316bfSLuis R. Rodriguez req->task = kthread_run(test_fw_run_batch_request, req, 686c92316bfSLuis R. Rodriguez "%s-%u", KBUILD_MODNAME, req->idx); 687c92316bfSLuis R. Rodriguez if (!req->task || IS_ERR(req->task)) { 688c92316bfSLuis R. Rodriguez pr_err("Setting up thread %u failed\n", req->idx); 689c92316bfSLuis R. Rodriguez req->task = NULL; 690c92316bfSLuis R. Rodriguez rc = -ENOMEM; 691c92316bfSLuis R. Rodriguez goto out_bail; 692c92316bfSLuis R. Rodriguez } 693c92316bfSLuis R. Rodriguez } 694c92316bfSLuis R. Rodriguez 695c92316bfSLuis R. Rodriguez rc = count; 696c92316bfSLuis R. Rodriguez 697c92316bfSLuis R. Rodriguez /* 698c92316bfSLuis R. Rodriguez * We require an explicit release to enable more time and delay of 699c92316bfSLuis R. Rodriguez * calling release_firmware() to improve our chances of forcing a 700c92316bfSLuis R. Rodriguez * batched request. If we instead called release_firmware() right away 701c92316bfSLuis R. Rodriguez * then we might miss on an opportunity of having a successful firmware 702c92316bfSLuis R. Rodriguez * request pass on the opportunity to be come a batched request. 703c92316bfSLuis R. Rodriguez */ 704c92316bfSLuis R. Rodriguez 705c92316bfSLuis R. Rodriguez out_bail: 706c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 707c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 708c92316bfSLuis R. Rodriguez if (req->task || req->sent) 709c92316bfSLuis R. Rodriguez wait_for_completion(&req->completion); 710c92316bfSLuis R. Rodriguez } 711c92316bfSLuis R. Rodriguez 712c92316bfSLuis R. Rodriguez /* Override any worker error if we had a general setup error */ 713c92316bfSLuis R. Rodriguez if (rc < 0) 714c92316bfSLuis R. Rodriguez test_fw_config->test_result = rc; 715c92316bfSLuis R. Rodriguez 716c92316bfSLuis R. Rodriguez out_unlock: 717c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 718c92316bfSLuis R. Rodriguez 719c92316bfSLuis R. Rodriguez return rc; 720c92316bfSLuis R. Rodriguez } 721c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests); 722c92316bfSLuis R. Rodriguez 723c92316bfSLuis R. Rodriguez /* 724c92316bfSLuis R. Rodriguez * We wait for each callback to return with the lock held, no need to lock here 725c92316bfSLuis R. Rodriguez */ 726c92316bfSLuis R. Rodriguez static void trigger_batched_cb(const struct firmware *fw, void *context) 727c92316bfSLuis R. Rodriguez { 728c92316bfSLuis R. Rodriguez struct test_batched_req *req = context; 729c92316bfSLuis R. Rodriguez 730c92316bfSLuis R. Rodriguez if (!req) { 731c92316bfSLuis R. Rodriguez test_fw_config->test_result = -EINVAL; 732c92316bfSLuis R. Rodriguez return; 733c92316bfSLuis R. Rodriguez } 734c92316bfSLuis R. Rodriguez 735c92316bfSLuis R. Rodriguez /* forces *some* batched requests to queue up */ 736c92316bfSLuis R. Rodriguez if (!req->idx) 737c92316bfSLuis R. Rodriguez ssleep(2); 738c92316bfSLuis R. Rodriguez 739c92316bfSLuis R. Rodriguez req->fw = fw; 740c92316bfSLuis R. Rodriguez 741c92316bfSLuis R. Rodriguez /* 742c92316bfSLuis R. Rodriguez * Unfortunately the firmware API gives us nothing other than a null FW 743c92316bfSLuis R. Rodriguez * if the firmware was not found on async requests. Best we can do is 744c92316bfSLuis R. Rodriguez * just assume -ENOENT. A better API would pass the actual return 745c92316bfSLuis R. Rodriguez * value to the callback. 746c92316bfSLuis R. Rodriguez */ 747c92316bfSLuis R. Rodriguez if (!fw && !test_fw_config->test_result) 748c92316bfSLuis R. Rodriguez test_fw_config->test_result = -ENOENT; 749c92316bfSLuis R. Rodriguez 750c92316bfSLuis R. Rodriguez complete(&req->completion); 751c92316bfSLuis R. Rodriguez } 752c92316bfSLuis R. Rodriguez 753c92316bfSLuis R. Rodriguez static 754c92316bfSLuis R. Rodriguez ssize_t trigger_batched_requests_async_store(struct device *dev, 755c92316bfSLuis R. Rodriguez struct device_attribute *attr, 756c92316bfSLuis R. Rodriguez const char *buf, size_t count) 757c92316bfSLuis R. Rodriguez { 758c92316bfSLuis R. Rodriguez struct test_batched_req *req; 759c92316bfSLuis R. Rodriguez bool send_uevent; 760c92316bfSLuis R. Rodriguez int rc; 761c92316bfSLuis R. Rodriguez u8 i; 762c92316bfSLuis R. Rodriguez 763c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 764c92316bfSLuis R. Rodriguez 765fad953ceSKees Cook test_fw_config->reqs = 766fad953ceSKees Cook vzalloc(array3_size(sizeof(struct test_batched_req), 767fad953ceSKees Cook test_fw_config->num_requests, 2)); 768c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) { 769c92316bfSLuis R. Rodriguez rc = -ENOMEM; 770c92316bfSLuis R. Rodriguez goto out; 771c92316bfSLuis R. Rodriguez } 772c92316bfSLuis R. Rodriguez 773c92316bfSLuis R. Rodriguez pr_info("batched loading '%s' custom fallback mechanism %u times\n", 774c92316bfSLuis R. Rodriguez test_fw_config->name, test_fw_config->num_requests); 775c92316bfSLuis R. Rodriguez 776c92316bfSLuis R. Rodriguez send_uevent = test_fw_config->send_uevent ? FW_ACTION_HOTPLUG : 777c92316bfSLuis R. Rodriguez FW_ACTION_NOHOTPLUG; 778c92316bfSLuis R. Rodriguez 779c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 780c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 781c92316bfSLuis R. Rodriguez req->name = test_fw_config->name; 782c92316bfSLuis R. Rodriguez req->fw = NULL; 783c92316bfSLuis R. Rodriguez req->idx = i; 784c92316bfSLuis R. Rodriguez init_completion(&req->completion); 785c92316bfSLuis R. Rodriguez rc = request_firmware_nowait(THIS_MODULE, send_uevent, 786c92316bfSLuis R. Rodriguez req->name, 787c92316bfSLuis R. Rodriguez dev, GFP_KERNEL, req, 788c92316bfSLuis R. Rodriguez trigger_batched_cb); 789c92316bfSLuis R. Rodriguez if (rc) { 790c92316bfSLuis R. Rodriguez pr_info("#%u: batched async load failed setup: %d\n", 791c92316bfSLuis R. Rodriguez i, rc); 792c92316bfSLuis R. Rodriguez req->rc = rc; 793c92316bfSLuis R. Rodriguez goto out_bail; 794c92316bfSLuis R. Rodriguez } else 795c92316bfSLuis R. Rodriguez req->sent = true; 796c92316bfSLuis R. Rodriguez } 797c92316bfSLuis R. Rodriguez 798c92316bfSLuis R. Rodriguez rc = count; 799c92316bfSLuis R. Rodriguez 800c92316bfSLuis R. Rodriguez out_bail: 801c92316bfSLuis R. Rodriguez 802c92316bfSLuis R. Rodriguez /* 803c92316bfSLuis R. Rodriguez * We require an explicit release to enable more time and delay of 804c92316bfSLuis R. Rodriguez * calling release_firmware() to improve our chances of forcing a 805c92316bfSLuis R. Rodriguez * batched request. If we instead called release_firmware() right away 806c92316bfSLuis R. Rodriguez * then we might miss on an opportunity of having a successful firmware 807c92316bfSLuis R. Rodriguez * request pass on the opportunity to be come a batched request. 808c92316bfSLuis R. Rodriguez */ 809c92316bfSLuis R. Rodriguez 810c92316bfSLuis R. Rodriguez for (i = 0; i < test_fw_config->num_requests; i++) { 811c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[i]; 812c92316bfSLuis R. Rodriguez if (req->sent) 813c92316bfSLuis R. Rodriguez wait_for_completion(&req->completion); 814c92316bfSLuis R. Rodriguez } 815c92316bfSLuis R. Rodriguez 816c92316bfSLuis R. Rodriguez /* Override any worker error if we had a general setup error */ 817c92316bfSLuis R. Rodriguez if (rc < 0) 818c92316bfSLuis R. Rodriguez test_fw_config->test_result = rc; 819c92316bfSLuis R. Rodriguez 820c92316bfSLuis R. Rodriguez out: 821c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 822c92316bfSLuis R. Rodriguez 823c92316bfSLuis R. Rodriguez return rc; 824c92316bfSLuis R. Rodriguez } 825c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(trigger_batched_requests_async); 826c92316bfSLuis R. Rodriguez 827c92316bfSLuis R. Rodriguez static ssize_t test_result_show(struct device *dev, 828c92316bfSLuis R. Rodriguez struct device_attribute *attr, 829c92316bfSLuis R. Rodriguez char *buf) 830c92316bfSLuis R. Rodriguez { 831c92316bfSLuis R. Rodriguez return test_dev_config_show_int(buf, test_fw_config->test_result); 832c92316bfSLuis R. Rodriguez } 833c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(test_result); 834c92316bfSLuis R. Rodriguez 835c92316bfSLuis R. Rodriguez static ssize_t release_all_firmware_store(struct device *dev, 836c92316bfSLuis R. Rodriguez struct device_attribute *attr, 837c92316bfSLuis R. Rodriguez const char *buf, size_t count) 838c92316bfSLuis R. Rodriguez { 839c92316bfSLuis R. Rodriguez test_release_all_firmware(); 840c92316bfSLuis R. Rodriguez return count; 841c92316bfSLuis R. Rodriguez } 842c92316bfSLuis R. Rodriguez static DEVICE_ATTR_WO(release_all_firmware); 843c92316bfSLuis R. Rodriguez 844c92316bfSLuis R. Rodriguez static ssize_t read_firmware_show(struct device *dev, 845c92316bfSLuis R. Rodriguez struct device_attribute *attr, 846c92316bfSLuis R. Rodriguez char *buf) 847c92316bfSLuis R. Rodriguez { 848c92316bfSLuis R. Rodriguez struct test_batched_req *req; 849c92316bfSLuis R. Rodriguez u8 idx; 850c92316bfSLuis R. Rodriguez ssize_t rc = 0; 851c92316bfSLuis R. Rodriguez 852c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 853c92316bfSLuis R. Rodriguez 854c92316bfSLuis R. Rodriguez idx = test_fw_config->read_fw_idx; 855c92316bfSLuis R. Rodriguez if (idx >= test_fw_config->num_requests) { 856c92316bfSLuis R. Rodriguez rc = -ERANGE; 857c92316bfSLuis R. Rodriguez goto out; 858c92316bfSLuis R. Rodriguez } 859c92316bfSLuis R. Rodriguez 860c92316bfSLuis R. Rodriguez if (!test_fw_config->reqs) { 861c92316bfSLuis R. Rodriguez rc = -EINVAL; 862c92316bfSLuis R. Rodriguez goto out; 863c92316bfSLuis R. Rodriguez } 864c92316bfSLuis R. Rodriguez 865c92316bfSLuis R. Rodriguez req = &test_fw_config->reqs[idx]; 866c92316bfSLuis R. Rodriguez if (!req->fw) { 867c92316bfSLuis R. Rodriguez pr_err("#%u: failed to async load firmware\n", idx); 868c92316bfSLuis R. Rodriguez rc = -ENOENT; 869c92316bfSLuis R. Rodriguez goto out; 870c92316bfSLuis R. Rodriguez } 871c92316bfSLuis R. Rodriguez 872c92316bfSLuis R. Rodriguez pr_info("#%u: loaded %zu\n", idx, req->fw->size); 873c92316bfSLuis R. Rodriguez 874c92316bfSLuis R. Rodriguez if (req->fw->size > PAGE_SIZE) { 875c92316bfSLuis R. Rodriguez pr_err("Testing interface must use PAGE_SIZE firmware for now\n"); 876c92316bfSLuis R. Rodriguez rc = -EINVAL; 8778bb0a886SColin Ian King goto out; 878c92316bfSLuis R. Rodriguez } 879c92316bfSLuis R. Rodriguez memcpy(buf, req->fw->data, req->fw->size); 880c92316bfSLuis R. Rodriguez 881c92316bfSLuis R. Rodriguez rc = req->fw->size; 882c92316bfSLuis R. Rodriguez out: 883c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 884c92316bfSLuis R. Rodriguez 885c92316bfSLuis R. Rodriguez return rc; 886c92316bfSLuis R. Rodriguez } 887c92316bfSLuis R. Rodriguez static DEVICE_ATTR_RO(read_firmware); 888c92316bfSLuis R. Rodriguez 889083a93b0SLuis R. Rodriguez #define TEST_FW_DEV_ATTR(name) &dev_attr_##name.attr 890083a93b0SLuis R. Rodriguez 891083a93b0SLuis R. Rodriguez static struct attribute *test_dev_attrs[] = { 892c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(reset), 893c92316bfSLuis R. Rodriguez 894c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config), 895c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_name), 896c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_num_requests), 897*7feebfa4SScott Branden TEST_FW_DEV_ATTR(config_into_buf), 898c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_sync_direct), 899c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_send_uevent), 900c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(config_read_fw_idx), 901c92316bfSLuis R. Rodriguez 902c92316bfSLuis R. Rodriguez /* These don't use the config at all - they could be ported! */ 903083a93b0SLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_request), 904083a93b0SLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_async_request), 905061132d2SLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_custom_fallback), 906c92316bfSLuis R. Rodriguez 907c92316bfSLuis R. Rodriguez /* These use the config and can use the test_result */ 908c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_batched_requests), 909c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(trigger_batched_requests_async), 910c92316bfSLuis R. Rodriguez 911c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(release_all_firmware), 912c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(test_result), 913c92316bfSLuis R. Rodriguez TEST_FW_DEV_ATTR(read_firmware), 914083a93b0SLuis R. Rodriguez NULL, 915083a93b0SLuis R. Rodriguez }; 916083a93b0SLuis R. Rodriguez 917083a93b0SLuis R. Rodriguez ATTRIBUTE_GROUPS(test_dev); 918083a93b0SLuis R. Rodriguez 91967fd553cSLuis R. Rodriguez static struct miscdevice test_fw_misc_device = { 92067fd553cSLuis R. Rodriguez .minor = MISC_DYNAMIC_MINOR, 92167fd553cSLuis R. Rodriguez .name = "test_firmware", 92267fd553cSLuis R. Rodriguez .fops = &test_fw_fops, 923083a93b0SLuis R. Rodriguez .groups = test_dev_groups, 92467fd553cSLuis R. Rodriguez }; 92567fd553cSLuis R. Rodriguez 9260a8adf58SKees Cook static int __init test_firmware_init(void) 9270a8adf58SKees Cook { 9280a8adf58SKees Cook int rc; 9290a8adf58SKees Cook 930c92316bfSLuis R. Rodriguez test_fw_config = kzalloc(sizeof(struct test_config), GFP_KERNEL); 931c92316bfSLuis R. Rodriguez if (!test_fw_config) 932c92316bfSLuis R. Rodriguez return -ENOMEM; 933c92316bfSLuis R. Rodriguez 934c92316bfSLuis R. Rodriguez rc = __test_firmware_config_init(); 935d4fddac5SWenwen Wang if (rc) { 936d4fddac5SWenwen Wang kfree(test_fw_config); 937d4fddac5SWenwen Wang pr_err("could not init firmware test config: %d\n", rc); 938c92316bfSLuis R. Rodriguez return rc; 939d4fddac5SWenwen Wang } 940c92316bfSLuis R. Rodriguez 9410a8adf58SKees Cook rc = misc_register(&test_fw_misc_device); 9420a8adf58SKees Cook if (rc) { 943c92316bfSLuis R. Rodriguez kfree(test_fw_config); 9440a8adf58SKees Cook pr_err("could not register misc device: %d\n", rc); 9450a8adf58SKees Cook return rc; 9460a8adf58SKees Cook } 947eb910947SBrian Norris 9480a8adf58SKees Cook pr_warn("interface ready\n"); 9490a8adf58SKees Cook 9500a8adf58SKees Cook return 0; 9510a8adf58SKees Cook } 9520a8adf58SKees Cook 9530a8adf58SKees Cook module_init(test_firmware_init); 9540a8adf58SKees Cook 9550a8adf58SKees Cook static void __exit test_firmware_exit(void) 9560a8adf58SKees Cook { 957c92316bfSLuis R. Rodriguez mutex_lock(&test_fw_mutex); 9580a8adf58SKees Cook release_firmware(test_firmware); 9590a8adf58SKees Cook misc_deregister(&test_fw_misc_device); 960c92316bfSLuis R. Rodriguez __test_firmware_config_free(); 961c92316bfSLuis R. Rodriguez kfree(test_fw_config); 962c92316bfSLuis R. Rodriguez mutex_unlock(&test_fw_mutex); 963c92316bfSLuis R. Rodriguez 9640a8adf58SKees Cook pr_warn("removed interface\n"); 9650a8adf58SKees Cook } 9660a8adf58SKees Cook 9670a8adf58SKees Cook module_exit(test_firmware_exit); 9680a8adf58SKees Cook 9690a8adf58SKees Cook MODULE_AUTHOR("Kees Cook <[email protected]>"); 9700a8adf58SKees Cook MODULE_LICENSE("GPL"); 971