Blame view

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