blob: a3e8ec3fb1c50c59192abd8402601960e78d4001
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> |
15 | #include <linux/completion.h> |
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 | |
58 | name = kstrndup(buf, count, GFP_KERNEL); |
59 | if (!name) |
60 | return -ENOSPC; |
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); |
68 | if (rc) { |
69 | pr_info("load of '%s' failed: %d\n", name, rc); |
70 | goto out; |
71 | } |
72 | pr_info("loaded: %zu\n", test_firmware->size); |
73 | rc = count; |
74 | |
75 | out: |
76 | mutex_unlock(&test_fw_mutex); |
77 | |
78 | kfree(name); |
79 | |
80 | return rc; |
81 | } |
82 | static DEVICE_ATTR_WO(trigger_request); |
83 | |
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 | |
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 | |
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 | |
158 | pr_warn("interface ready\n"); |
159 | |
160 | return 0; |
161 | |
162 | remove_file: |
163 | device_remove_file(test_fw_misc_device.this_device, |
164 | &dev_attr_trigger_async_request); |
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); |
175 | device_remove_file(test_fw_misc_device.this_device, |
176 | &dev_attr_trigger_async_request); |
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"); |
187 |