]>
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 | ||
0a8adf58 KC |
45 | static ssize_t trigger_request_store(struct device *dev, |
46 | struct device_attribute *attr, | |
47 | const char *buf, size_t count) | |
48 | { | |
49 | int rc; | |
50 | char *name; | |
51 | ||
be4a1326 | 52 | name = kstrndup(buf, count, GFP_KERNEL); |
0a8adf58 KC |
53 | if (!name) |
54 | return -ENOSPC; | |
0a8adf58 KC |
55 | |
56 | pr_info("loading '%s'\n", name); | |
57 | ||
58 | mutex_lock(&test_fw_mutex); | |
59 | release_firmware(test_firmware); | |
60 | test_firmware = NULL; | |
61 | rc = request_firmware(&test_firmware, name, dev); | |
47e0bbb7 | 62 | if (rc) { |
0a8adf58 | 63 | pr_info("load of '%s' failed: %d\n", name, rc); |
47e0bbb7 BN |
64 | goto out; |
65 | } | |
66 | pr_info("loaded: %zu\n", test_firmware->size); | |
67 | rc = count; | |
68 | ||
69 | out: | |
0a8adf58 KC |
70 | mutex_unlock(&test_fw_mutex); |
71 | ||
72 | kfree(name); | |
73 | ||
47e0bbb7 | 74 | return rc; |
0a8adf58 KC |
75 | } |
76 | static DEVICE_ATTR_WO(trigger_request); | |
77 | ||
eb910947 BN |
78 | static DECLARE_COMPLETION(async_fw_done); |
79 | ||
80 | static void trigger_async_request_cb(const struct firmware *fw, void *context) | |
81 | { | |
82 | test_firmware = fw; | |
83 | complete(&async_fw_done); | |
84 | } | |
85 | ||
86 | static ssize_t trigger_async_request_store(struct device *dev, | |
87 | struct device_attribute *attr, | |
88 | const char *buf, size_t count) | |
89 | { | |
90 | int rc; | |
91 | char *name; | |
92 | ||
93 | name = kstrndup(buf, count, GFP_KERNEL); | |
94 | if (!name) | |
95 | return -ENOSPC; | |
96 | ||
97 | pr_info("loading '%s'\n", name); | |
98 | ||
99 | mutex_lock(&test_fw_mutex); | |
100 | release_firmware(test_firmware); | |
101 | test_firmware = NULL; | |
102 | rc = request_firmware_nowait(THIS_MODULE, 1, name, dev, GFP_KERNEL, | |
103 | NULL, trigger_async_request_cb); | |
104 | if (rc) { | |
105 | pr_info("async load of '%s' failed: %d\n", name, rc); | |
106 | kfree(name); | |
107 | goto out; | |
108 | } | |
109 | /* Free 'name' ASAP, to test for race conditions */ | |
110 | kfree(name); | |
111 | ||
112 | wait_for_completion(&async_fw_done); | |
113 | ||
114 | if (test_firmware) { | |
115 | pr_info("loaded: %zu\n", test_firmware->size); | |
116 | rc = count; | |
117 | } else { | |
118 | pr_err("failed to async load firmware\n"); | |
119 | rc = -ENODEV; | |
120 | } | |
121 | ||
122 | out: | |
123 | mutex_unlock(&test_fw_mutex); | |
124 | ||
125 | return rc; | |
126 | } | |
127 | static DEVICE_ATTR_WO(trigger_async_request); | |
128 | ||
061132d2 LR |
129 | static ssize_t trigger_custom_fallback_store(struct device *dev, |
130 | struct device_attribute *attr, | |
131 | const char *buf, size_t count) | |
132 | { | |
133 | int rc; | |
134 | char *name; | |
135 | ||
136 | name = kstrndup(buf, count, GFP_KERNEL); | |
137 | if (!name) | |
138 | return -ENOSPC; | |
139 | ||
140 | pr_info("loading '%s' using custom fallback mechanism\n", name); | |
141 | ||
142 | mutex_lock(&test_fw_mutex); | |
143 | release_firmware(test_firmware); | |
144 | test_firmware = NULL; | |
145 | rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, name, | |
146 | dev, GFP_KERNEL, NULL, | |
147 | trigger_async_request_cb); | |
148 | if (rc) { | |
149 | pr_info("async load of '%s' failed: %d\n", name, rc); | |
150 | kfree(name); | |
151 | goto out; | |
152 | } | |
153 | /* Free 'name' ASAP, to test for race conditions */ | |
154 | kfree(name); | |
155 | ||
156 | wait_for_completion(&async_fw_done); | |
157 | ||
158 | if (test_firmware) { | |
159 | pr_info("loaded: %zu\n", test_firmware->size); | |
160 | rc = count; | |
161 | } else { | |
162 | pr_err("failed to async load firmware\n"); | |
163 | rc = -ENODEV; | |
164 | } | |
165 | ||
166 | out: | |
167 | mutex_unlock(&test_fw_mutex); | |
168 | ||
169 | return rc; | |
170 | } | |
171 | static DEVICE_ATTR_WO(trigger_custom_fallback); | |
172 | ||
083a93b0 LR |
173 | #define TEST_FW_DEV_ATTR(name) &dev_attr_##name.attr |
174 | ||
175 | static struct attribute *test_dev_attrs[] = { | |
176 | TEST_FW_DEV_ATTR(trigger_request), | |
177 | TEST_FW_DEV_ATTR(trigger_async_request), | |
061132d2 | 178 | TEST_FW_DEV_ATTR(trigger_custom_fallback), |
083a93b0 LR |
179 | NULL, |
180 | }; | |
181 | ||
182 | ATTRIBUTE_GROUPS(test_dev); | |
183 | ||
67fd553c LR |
184 | static struct miscdevice test_fw_misc_device = { |
185 | .minor = MISC_DYNAMIC_MINOR, | |
186 | .name = "test_firmware", | |
187 | .fops = &test_fw_fops, | |
083a93b0 | 188 | .groups = test_dev_groups, |
67fd553c LR |
189 | }; |
190 | ||
0a8adf58 KC |
191 | static int __init test_firmware_init(void) |
192 | { | |
193 | int rc; | |
194 | ||
195 | rc = misc_register(&test_fw_misc_device); | |
196 | if (rc) { | |
197 | pr_err("could not register misc device: %d\n", rc); | |
198 | return rc; | |
199 | } | |
eb910947 | 200 | |
0a8adf58 KC |
201 | pr_warn("interface ready\n"); |
202 | ||
203 | return 0; | |
0a8adf58 KC |
204 | } |
205 | ||
206 | module_init(test_firmware_init); | |
207 | ||
208 | static void __exit test_firmware_exit(void) | |
209 | { | |
210 | release_firmware(test_firmware); | |
0a8adf58 KC |
211 | misc_deregister(&test_fw_misc_device); |
212 | pr_warn("removed interface\n"); | |
213 | } | |
214 | ||
215 | module_exit(test_firmware_exit); | |
216 | ||
217 | MODULE_AUTHOR("Kees Cook <[email protected]>"); | |
218 | MODULE_LICENSE("GPL"); |