Commit | Line | Data |
---|---|---|
0a8adf58 KC |
1 | /* |
2 | * This module provides an interface to trigger and test firmware loading. | |
3 | * | |
4 | * It is designed to be used for basic evaluation of the firmware loading | |
5 | * subsystem (for example when validating firmware verification). It lacks | |
6 | * any extra dependencies, and will not normally be loaded by the system | |
7 | * unless explicitly requested by name. | |
8 | */ | |
9 | ||
10 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
11 | ||
12 | #include <linux/init.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/printk.h> | |
eb910947 | 15 | #include <linux/completion.h> |
0a8adf58 KC |
16 | #include <linux/firmware.h> |
17 | #include <linux/device.h> | |
18 | #include <linux/fs.h> | |
19 | #include <linux/miscdevice.h> | |
20 | #include <linux/slab.h> | |
21 | #include <linux/uaccess.h> | |
22 | ||
23 | static DEFINE_MUTEX(test_fw_mutex); | |
24 | static const struct firmware *test_firmware; | |
25 | ||
26 | static ssize_t test_fw_misc_read(struct file *f, char __user *buf, | |
27 | size_t size, loff_t *offset) | |
28 | { | |
29 | ssize_t rc = 0; | |
30 | ||
31 | mutex_lock(&test_fw_mutex); | |
32 | if (test_firmware) | |
33 | rc = simple_read_from_buffer(buf, size, offset, | |
34 | test_firmware->data, | |
35 | test_firmware->size); | |
36 | mutex_unlock(&test_fw_mutex); | |
37 | return rc; | |
38 | } | |
39 | ||
40 | static const struct file_operations test_fw_fops = { | |
41 | .owner = THIS_MODULE, | |
42 | .read = test_fw_misc_read, | |
43 | }; | |
44 | ||
45 | static struct miscdevice test_fw_misc_device = { | |
46 | .minor = MISC_DYNAMIC_MINOR, | |
47 | .name = "test_firmware", | |
48 | .fops = &test_fw_fops, | |
49 | }; | |
50 | ||
51 | static ssize_t trigger_request_store(struct device *dev, | |
52 | struct device_attribute *attr, | |
53 | const char *buf, size_t count) | |
54 | { | |
55 | int rc; | |
56 | char *name; | |
57 | ||
be4a1326 | 58 | name = kstrndup(buf, count, GFP_KERNEL); |
0a8adf58 KC |
59 | if (!name) |
60 | return -ENOSPC; | |
0a8adf58 KC |
61 | |
62 | pr_info("loading '%s'\n", name); | |
63 | ||
64 | mutex_lock(&test_fw_mutex); | |
65 | release_firmware(test_firmware); | |
66 | test_firmware = NULL; | |
67 | rc = request_firmware(&test_firmware, name, dev); | |
47e0bbb7 | 68 | if (rc) { |
0a8adf58 | 69 | pr_info("load of '%s' failed: %d\n", name, rc); |
47e0bbb7 BN |
70 | goto out; |
71 | } | |
72 | pr_info("loaded: %zu\n", test_firmware->size); | |
73 | rc = count; | |
74 | ||
75 | out: | |
0a8adf58 KC |
76 | mutex_unlock(&test_fw_mutex); |
77 | ||
78 | kfree(name); | |
79 | ||
47e0bbb7 | 80 | return rc; |
0a8adf58 KC |
81 | } |
82 | static DEVICE_ATTR_WO(trigger_request); | |
83 | ||
eb910947 BN |
84 | static DECLARE_COMPLETION(async_fw_done); |
85 | ||
86 | static void trigger_async_request_cb(const struct firmware *fw, void *context) | |
87 | { | |
88 | test_firmware = fw; | |
89 | complete(&async_fw_done); | |
90 | } | |
91 | ||
92 | static ssize_t trigger_async_request_store(struct device *dev, | |
93 | struct device_attribute *attr, | |
94 | const char *buf, size_t count) | |
95 | { | |
96 | int rc; | |
97 | char *name; | |
98 | ||
99 | name = kstrndup(buf, count, GFP_KERNEL); | |
100 | if (!name) | |
101 | return -ENOSPC; | |
102 | ||
103 | pr_info("loading '%s'\n", name); | |
104 | ||
105 | mutex_lock(&test_fw_mutex); | |
106 | release_firmware(test_firmware); | |
107 | test_firmware = NULL; | |
108 | rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL, | |
109 | NULL, trigger_async_request_cb); | |
110 | if (rc) { | |
111 | pr_info("async load of '%s' failed: %d\n", name, rc); | |
112 | kfree(name); | |
113 | goto out; | |
114 | } | |
115 | /* Free 'name' ASAP, to test for race conditions */ | |
116 | kfree(name); | |
117 | ||
118 | wait_for_completion(&async_fw_done); | |
119 | ||
120 | if (test_firmware) { | |
121 | pr_info("loaded: %zu\n", test_firmware->size); | |
122 | rc = count; | |
123 | } else { | |
124 | pr_err("failed to async load firmware\n"); | |
125 | rc = -ENODEV; | |
126 | } | |
127 | ||
128 | out: | |
129 | mutex_unlock(&test_fw_mutex); | |
130 | ||
131 | return rc; | |
132 | } | |
133 | static DEVICE_ATTR_WO(trigger_async_request); | |
134 | ||
0a8adf58 KC |
135 | static int __init test_firmware_init(void) |
136 | { | |
137 | int rc; | |
138 | ||
139 | rc = misc_register(&test_fw_misc_device); | |
140 | if (rc) { | |
141 | pr_err("could not register misc device: %d\n", rc); | |
142 | return rc; | |
143 | } | |
144 | rc = device_create_file(test_fw_misc_device.this_device, | |
145 | &dev_attr_trigger_request); | |
146 | if (rc) { | |
147 | pr_err("could not create sysfs interface: %d\n", rc); | |
148 | goto dereg; | |
149 | } | |
150 | ||
eb910947 BN |
151 | rc = device_create_file(test_fw_misc_device.this_device, |
152 | &dev_attr_trigger_async_request); | |
153 | if (rc) { | |
154 | pr_err("could not create async sysfs interface: %d\n", rc); | |
155 | goto remove_file; | |
156 | } | |
157 | ||
0a8adf58 KC |
158 | pr_warn("interface ready\n"); |
159 | ||
160 | return 0; | |
eb910947 BN |
161 | |
162 | remove_file: | |
163 | device_remove_file(test_fw_misc_device.this_device, | |
164 | &dev_attr_trigger_async_request); | |
0a8adf58 KC |
165 | dereg: |
166 | misc_deregister(&test_fw_misc_device); | |
167 | return rc; | |
168 | } | |
169 | ||
170 | module_init(test_firmware_init); | |
171 | ||
172 | static void __exit test_firmware_exit(void) | |
173 | { | |
174 | release_firmware(test_firmware); | |
eb910947 BN |
175 | device_remove_file(test_fw_misc_device.this_device, |
176 | &dev_attr_trigger_async_request); | |
0a8adf58 KC |
177 | device_remove_file(test_fw_misc_device.this_device, |
178 | &dev_attr_trigger_request); | |
179 | misc_deregister(&test_fw_misc_device); | |
180 | pr_warn("removed interface\n"); | |
181 | } | |
182 | ||
183 | module_exit(test_firmware_exit); | |
184 | ||
185 | MODULE_AUTHOR("Kees Cook <keescook@chromium.org>"); | |
186 | MODULE_LICENSE("GPL"); |