xref: /linux-6.15/lib/test_firmware.c (revision 0a8adf58)
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