]>
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 | ||
67fd553c LR |
129 | static struct miscdevice test_fw_misc_device = { |
130 | .minor = MISC_DYNAMIC_MINOR, | |
131 | .name = "test_firmware", | |
132 | .fops = &test_fw_fops, | |
133 | }; | |
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"); |