1*0a8adf58SKees Cook /* 2*0a8adf58SKees Cook * This module provides an interface to trigger and test firmware loading. 3*0a8adf58SKees Cook * 4*0a8adf58SKees Cook * It is designed to be used for basic evaluation of the firmware loading 5*0a8adf58SKees Cook * subsystem (for example when validating firmware verification). It lacks 6*0a8adf58SKees Cook * any extra dependencies, and will not normally be loaded by the system 7*0a8adf58SKees Cook * unless explicitly requested by name. 8*0a8adf58SKees Cook */ 9*0a8adf58SKees Cook 10*0a8adf58SKees Cook #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 11*0a8adf58SKees Cook 12*0a8adf58SKees Cook #include <linux/init.h> 13*0a8adf58SKees Cook #include <linux/module.h> 14*0a8adf58SKees Cook #include <linux/printk.h> 15*0a8adf58SKees Cook #include <linux/firmware.h> 16*0a8adf58SKees Cook #include <linux/device.h> 17*0a8adf58SKees Cook #include <linux/fs.h> 18*0a8adf58SKees Cook #include <linux/miscdevice.h> 19*0a8adf58SKees Cook #include <linux/slab.h> 20*0a8adf58SKees Cook #include <linux/uaccess.h> 21*0a8adf58SKees Cook 22*0a8adf58SKees Cook static DEFINE_MUTEX(test_fw_mutex); 23*0a8adf58SKees Cook static const struct firmware *test_firmware; 24*0a8adf58SKees Cook 25*0a8adf58SKees Cook static ssize_t test_fw_misc_read(struct file *f, char __user *buf, 26*0a8adf58SKees Cook size_t size, loff_t *offset) 27*0a8adf58SKees Cook { 28*0a8adf58SKees Cook ssize_t rc = 0; 29*0a8adf58SKees Cook 30*0a8adf58SKees Cook mutex_lock(&test_fw_mutex); 31*0a8adf58SKees Cook if (test_firmware) 32*0a8adf58SKees Cook rc = simple_read_from_buffer(buf, size, offset, 33*0a8adf58SKees Cook test_firmware->data, 34*0a8adf58SKees Cook test_firmware->size); 35*0a8adf58SKees Cook mutex_unlock(&test_fw_mutex); 36*0a8adf58SKees Cook return rc; 37*0a8adf58SKees Cook } 38*0a8adf58SKees Cook 39*0a8adf58SKees Cook static const struct file_operations test_fw_fops = { 40*0a8adf58SKees Cook .owner = THIS_MODULE, 41*0a8adf58SKees Cook .read = test_fw_misc_read, 42*0a8adf58SKees Cook }; 43*0a8adf58SKees Cook 44*0a8adf58SKees Cook static struct miscdevice test_fw_misc_device = { 45*0a8adf58SKees Cook .minor = MISC_DYNAMIC_MINOR, 46*0a8adf58SKees Cook .name = "test_firmware", 47*0a8adf58SKees Cook .fops = &test_fw_fops, 48*0a8adf58SKees Cook }; 49*0a8adf58SKees Cook 50*0a8adf58SKees Cook static ssize_t trigger_request_store(struct device *dev, 51*0a8adf58SKees Cook struct device_attribute *attr, 52*0a8adf58SKees Cook const char *buf, size_t count) 53*0a8adf58SKees Cook { 54*0a8adf58SKees Cook int rc; 55*0a8adf58SKees Cook char *name; 56*0a8adf58SKees Cook 57*0a8adf58SKees Cook name = kzalloc(count + 1, GFP_KERNEL); 58*0a8adf58SKees Cook if (!name) 59*0a8adf58SKees Cook return -ENOSPC; 60*0a8adf58SKees Cook memcpy(name, buf, count); 61*0a8adf58SKees Cook 62*0a8adf58SKees Cook pr_info("loading '%s'\n", name); 63*0a8adf58SKees Cook 64*0a8adf58SKees Cook mutex_lock(&test_fw_mutex); 65*0a8adf58SKees Cook release_firmware(test_firmware); 66*0a8adf58SKees Cook test_firmware = NULL; 67*0a8adf58SKees Cook rc = request_firmware(&test_firmware, name, dev); 68*0a8adf58SKees Cook if (rc) 69*0a8adf58SKees Cook pr_info("load of '%s' failed: %d\n", name, rc); 70*0a8adf58SKees Cook pr_info("loaded: %zu\n", test_firmware ? test_firmware->size : 0); 71*0a8adf58SKees Cook mutex_unlock(&test_fw_mutex); 72*0a8adf58SKees Cook 73*0a8adf58SKees Cook kfree(name); 74*0a8adf58SKees Cook 75*0a8adf58SKees Cook return count; 76*0a8adf58SKees Cook } 77*0a8adf58SKees Cook static DEVICE_ATTR_WO(trigger_request); 78*0a8adf58SKees Cook 79*0a8adf58SKees Cook static int __init test_firmware_init(void) 80*0a8adf58SKees Cook { 81*0a8adf58SKees Cook int rc; 82*0a8adf58SKees Cook 83*0a8adf58SKees Cook rc = misc_register(&test_fw_misc_device); 84*0a8adf58SKees Cook if (rc) { 85*0a8adf58SKees Cook pr_err("could not register misc device: %d\n", rc); 86*0a8adf58SKees Cook return rc; 87*0a8adf58SKees Cook } 88*0a8adf58SKees Cook rc = device_create_file(test_fw_misc_device.this_device, 89*0a8adf58SKees Cook &dev_attr_trigger_request); 90*0a8adf58SKees Cook if (rc) { 91*0a8adf58SKees Cook pr_err("could not create sysfs interface: %d\n", rc); 92*0a8adf58SKees Cook goto dereg; 93*0a8adf58SKees Cook } 94*0a8adf58SKees Cook 95*0a8adf58SKees Cook pr_warn("interface ready\n"); 96*0a8adf58SKees Cook 97*0a8adf58SKees Cook return 0; 98*0a8adf58SKees Cook dereg: 99*0a8adf58SKees Cook misc_deregister(&test_fw_misc_device); 100*0a8adf58SKees Cook return rc; 101*0a8adf58SKees Cook } 102*0a8adf58SKees Cook 103*0a8adf58SKees Cook module_init(test_firmware_init); 104*0a8adf58SKees Cook 105*0a8adf58SKees Cook static void __exit test_firmware_exit(void) 106*0a8adf58SKees Cook { 107*0a8adf58SKees Cook release_firmware(test_firmware); 108*0a8adf58SKees Cook device_remove_file(test_fw_misc_device.this_device, 109*0a8adf58SKees Cook &dev_attr_trigger_request); 110*0a8adf58SKees Cook misc_deregister(&test_fw_misc_device); 111*0a8adf58SKees Cook pr_warn("removed interface\n"); 112*0a8adf58SKees Cook } 113*0a8adf58SKees Cook 114*0a8adf58SKees Cook module_exit(test_firmware_exit); 115*0a8adf58SKees Cook 116*0a8adf58SKees Cook MODULE_AUTHOR("Kees Cook <[email protected]>"); 117*0a8adf58SKees Cook MODULE_LICENSE("GPL"); 118