diff options
Diffstat (limited to 'drivers/firmware')
-rw-r--r-- | drivers/firmware/Kconfig | 11 | ||||
-rw-r--r-- | drivers/firmware/Makefile | 1 | ||||
-rw-r--r-- | drivers/firmware/dmi-sysfs.c | 396 |
3 files changed, 408 insertions, 0 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index e710424b59ea..3c56afc5eb1b 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig | |||
@@ -113,6 +113,17 @@ config DMIID | |||
113 | information from userspace through /sys/class/dmi/id/ or if you want | 113 | information from userspace through /sys/class/dmi/id/ or if you want |
114 | DMI-based module auto-loading. | 114 | DMI-based module auto-loading. |
115 | 115 | ||
116 | config DMI_SYSFS | ||
117 | tristate "DMI table support in sysfs" | ||
118 | depends on SYSFS && DMI | ||
119 | default n | ||
120 | help | ||
121 | Say Y or M here to enable the exporting of the raw DMI table | ||
122 | data via sysfs. This is useful for consuming the data without | ||
123 | requiring any access to /dev/mem at all. Tables are found | ||
124 | under /sys/firmware/dmi when this option is enabled and | ||
125 | loaded. | ||
126 | |||
116 | config ISCSI_IBFT_FIND | 127 | config ISCSI_IBFT_FIND |
117 | bool "iSCSI Boot Firmware Table Attributes" | 128 | bool "iSCSI Boot Firmware Table Attributes" |
118 | depends on X86 | 129 | depends on X86 |
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 1c3c17343dbe..20c17fca1232 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile | |||
@@ -2,6 +2,7 @@ | |||
2 | # Makefile for the linux kernel. | 2 | # Makefile for the linux kernel. |
3 | # | 3 | # |
4 | obj-$(CONFIG_DMI) += dmi_scan.o | 4 | obj-$(CONFIG_DMI) += dmi_scan.o |
5 | obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o | ||
5 | obj-$(CONFIG_EDD) += edd.o | 6 | obj-$(CONFIG_EDD) += edd.o |
6 | obj-$(CONFIG_EFI_VARS) += efivars.o | 7 | obj-$(CONFIG_EFI_VARS) += efivars.o |
7 | obj-$(CONFIG_EFI_PCDP) += pcdp.o | 8 | obj-$(CONFIG_EFI_PCDP) += pcdp.o |
diff --git a/drivers/firmware/dmi-sysfs.c b/drivers/firmware/dmi-sysfs.c new file mode 100644 index 000000000000..2d8a04a95085 --- /dev/null +++ b/drivers/firmware/dmi-sysfs.c | |||
@@ -0,0 +1,396 @@ | |||
1 | /* | ||
2 | * dmi-sysfs.c | ||
3 | * | ||
4 | * This module exports the DMI tables read-only to userspace through the | ||
5 | * sysfs file system. | ||
6 | * | ||
7 | * Data is currently found below | ||
8 | * /sys/firmware/dmi/... | ||
9 | * | ||
10 | * DMI attributes are presented in attribute files with names | ||
11 | * formatted using %d-%d, so that the first integer indicates the | ||
12 | * structure type (0-255), and the second field is the instance of that | ||
13 | * entry. | ||
14 | * | ||
15 | * Copyright 2011 Google, Inc. | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/types.h> | ||
22 | #include <linux/kobject.h> | ||
23 | #include <linux/dmi.h> | ||
24 | #include <linux/capability.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/list.h> | ||
27 | #include <linux/io.h> | ||
28 | |||
29 | #define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider | ||
30 | the top entry type is only 8 bits */ | ||
31 | |||
32 | struct dmi_sysfs_entry { | ||
33 | struct dmi_header dh; | ||
34 | struct kobject kobj; | ||
35 | int instance; | ||
36 | int position; | ||
37 | struct list_head list; | ||
38 | }; | ||
39 | |||
40 | /* | ||
41 | * Global list of dmi_sysfs_entry. Even though this should only be | ||
42 | * manipulated at setup and teardown, the lazy nature of the kobject | ||
43 | * system means we get lazy removes. | ||
44 | */ | ||
45 | static LIST_HEAD(entry_list); | ||
46 | static DEFINE_SPINLOCK(entry_list_lock); | ||
47 | |||
48 | /* dmi_sysfs_attribute - Top level attribute. used by all entries. */ | ||
49 | struct dmi_sysfs_attribute { | ||
50 | struct attribute attr; | ||
51 | ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf); | ||
52 | }; | ||
53 | |||
54 | #define DMI_SYSFS_ATTR(_entry, _name) \ | ||
55 | struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \ | ||
56 | .attr = {.name = __stringify(_name), .mode = 0400}, \ | ||
57 | .show = dmi_sysfs_##_entry##_##_name, \ | ||
58 | } | ||
59 | |||
60 | /* | ||
61 | * dmi_sysfs_mapped_attribute - Attribute where we require the entry be | ||
62 | * mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops. | ||
63 | */ | ||
64 | struct dmi_sysfs_mapped_attribute { | ||
65 | struct attribute attr; | ||
66 | ssize_t (*show)(struct dmi_sysfs_entry *entry, | ||
67 | const struct dmi_header *dh, | ||
68 | char *buf); | ||
69 | }; | ||
70 | |||
71 | #define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \ | ||
72 | struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \ | ||
73 | .attr = {.name = __stringify(_name), .mode = 0400}, \ | ||
74 | .show = dmi_sysfs_##_entry##_##_name, \ | ||
75 | } | ||
76 | |||
77 | /************************************************* | ||
78 | * Generic DMI entry support. | ||
79 | *************************************************/ | ||
80 | |||
81 | static struct dmi_sysfs_entry *to_entry(struct kobject *kobj) | ||
82 | { | ||
83 | return container_of(kobj, struct dmi_sysfs_entry, kobj); | ||
84 | } | ||
85 | |||
86 | static struct dmi_sysfs_attribute *to_attr(struct attribute *attr) | ||
87 | { | ||
88 | return container_of(attr, struct dmi_sysfs_attribute, attr); | ||
89 | } | ||
90 | |||
91 | static ssize_t dmi_sysfs_attr_show(struct kobject *kobj, | ||
92 | struct attribute *_attr, char *buf) | ||
93 | { | ||
94 | struct dmi_sysfs_entry *entry = to_entry(kobj); | ||
95 | struct dmi_sysfs_attribute *attr = to_attr(_attr); | ||
96 | |||
97 | /* DMI stuff is only ever admin visible */ | ||
98 | if (!capable(CAP_SYS_ADMIN)) | ||
99 | return -EACCES; | ||
100 | |||
101 | return attr->show(entry, buf); | ||
102 | } | ||
103 | |||
104 | static const struct sysfs_ops dmi_sysfs_attr_ops = { | ||
105 | .show = dmi_sysfs_attr_show, | ||
106 | }; | ||
107 | |||
108 | typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *, | ||
109 | const struct dmi_header *dh, void *); | ||
110 | |||
111 | struct find_dmi_data { | ||
112 | struct dmi_sysfs_entry *entry; | ||
113 | dmi_callback callback; | ||
114 | void *private; | ||
115 | int instance_countdown; | ||
116 | ssize_t ret; | ||
117 | }; | ||
118 | |||
119 | static void find_dmi_entry_helper(const struct dmi_header *dh, | ||
120 | void *_data) | ||
121 | { | ||
122 | struct find_dmi_data *data = _data; | ||
123 | struct dmi_sysfs_entry *entry = data->entry; | ||
124 | |||
125 | /* Is this the entry we want? */ | ||
126 | if (dh->type != entry->dh.type) | ||
127 | return; | ||
128 | |||
129 | if (data->instance_countdown != 0) { | ||
130 | /* try the next instance? */ | ||
131 | data->instance_countdown--; | ||
132 | return; | ||
133 | } | ||
134 | |||
135 | /* | ||
136 | * Don't ever revisit the instance. Short circuit later | ||
137 | * instances by letting the instance_countdown run negative | ||
138 | */ | ||
139 | data->instance_countdown--; | ||
140 | |||
141 | /* Found the entry */ | ||
142 | data->ret = data->callback(entry, dh, data->private); | ||
143 | } | ||
144 | |||
145 | /* State for passing the read parameters through dmi_find_entry() */ | ||
146 | struct dmi_read_state { | ||
147 | char *buf; | ||
148 | loff_t pos; | ||
149 | size_t count; | ||
150 | }; | ||
151 | |||
152 | static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry, | ||
153 | dmi_callback callback, void *private) | ||
154 | { | ||
155 | struct find_dmi_data data = { | ||
156 | .entry = entry, | ||
157 | .callback = callback, | ||
158 | .private = private, | ||
159 | .instance_countdown = entry->instance, | ||
160 | .ret = -EIO, /* To signal the entry disappeared */ | ||
161 | }; | ||
162 | int ret; | ||
163 | |||
164 | ret = dmi_walk(find_dmi_entry_helper, &data); | ||
165 | /* This shouldn't happen, but just in case. */ | ||
166 | if (ret) | ||
167 | return -EINVAL; | ||
168 | return data.ret; | ||
169 | } | ||
170 | |||
171 | /* | ||
172 | * Calculate and return the byte length of the dmi entry identified by | ||
173 | * dh. This includes both the formatted portion as well as the | ||
174 | * unformatted string space, including the two trailing nul characters. | ||
175 | */ | ||
176 | static size_t dmi_entry_length(const struct dmi_header *dh) | ||
177 | { | ||
178 | const char *p = (const char *)dh; | ||
179 | |||
180 | p += dh->length; | ||
181 | |||
182 | while (p[0] || p[1]) | ||
183 | p++; | ||
184 | |||
185 | return 2 + p - (const char *)dh; | ||
186 | } | ||
187 | |||
188 | /************************************************* | ||
189 | * Generic DMI entry support. | ||
190 | *************************************************/ | ||
191 | |||
192 | static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf) | ||
193 | { | ||
194 | return sprintf(buf, "%d\n", entry->dh.length); | ||
195 | } | ||
196 | |||
197 | static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf) | ||
198 | { | ||
199 | return sprintf(buf, "%d\n", entry->dh.handle); | ||
200 | } | ||
201 | |||
202 | static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf) | ||
203 | { | ||
204 | return sprintf(buf, "%d\n", entry->dh.type); | ||
205 | } | ||
206 | |||
207 | static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry, | ||
208 | char *buf) | ||
209 | { | ||
210 | return sprintf(buf, "%d\n", entry->instance); | ||
211 | } | ||
212 | |||
213 | static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry, | ||
214 | char *buf) | ||
215 | { | ||
216 | return sprintf(buf, "%d\n", entry->position); | ||
217 | } | ||
218 | |||
219 | static DMI_SYSFS_ATTR(entry, length); | ||
220 | static DMI_SYSFS_ATTR(entry, handle); | ||
221 | static DMI_SYSFS_ATTR(entry, type); | ||
222 | static DMI_SYSFS_ATTR(entry, instance); | ||
223 | static DMI_SYSFS_ATTR(entry, position); | ||
224 | |||
225 | static struct attribute *dmi_sysfs_entry_attrs[] = { | ||
226 | &dmi_sysfs_attr_entry_length.attr, | ||
227 | &dmi_sysfs_attr_entry_handle.attr, | ||
228 | &dmi_sysfs_attr_entry_type.attr, | ||
229 | &dmi_sysfs_attr_entry_instance.attr, | ||
230 | &dmi_sysfs_attr_entry_position.attr, | ||
231 | NULL, | ||
232 | }; | ||
233 | |||
234 | static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry, | ||
235 | const struct dmi_header *dh, | ||
236 | void *_state) | ||
237 | { | ||
238 | struct dmi_read_state *state = _state; | ||
239 | size_t entry_length; | ||
240 | |||
241 | entry_length = dmi_entry_length(dh); | ||
242 | |||
243 | return memory_read_from_buffer(state->buf, state->count, | ||
244 | &state->pos, dh, entry_length); | ||
245 | } | ||
246 | |||
247 | static ssize_t dmi_entry_raw_read(struct file *filp, | ||
248 | struct kobject *kobj, | ||
249 | struct bin_attribute *bin_attr, | ||
250 | char *buf, loff_t pos, size_t count) | ||
251 | { | ||
252 | struct dmi_sysfs_entry *entry = to_entry(kobj); | ||
253 | struct dmi_read_state state = { | ||
254 | .buf = buf, | ||
255 | .pos = pos, | ||
256 | .count = count, | ||
257 | }; | ||
258 | |||
259 | return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state); | ||
260 | } | ||
261 | |||
262 | static const struct bin_attribute dmi_entry_raw_attr = { | ||
263 | .attr = {.name = "raw", .mode = 0400}, | ||
264 | .read = dmi_entry_raw_read, | ||
265 | }; | ||
266 | |||
267 | static void dmi_sysfs_entry_release(struct kobject *kobj) | ||
268 | { | ||
269 | struct dmi_sysfs_entry *entry = to_entry(kobj); | ||
270 | sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr); | ||
271 | spin_lock(&entry_list_lock); | ||
272 | list_del(&entry->list); | ||
273 | spin_unlock(&entry_list_lock); | ||
274 | kfree(entry); | ||
275 | } | ||
276 | |||
277 | static struct kobj_type dmi_sysfs_entry_ktype = { | ||
278 | .release = dmi_sysfs_entry_release, | ||
279 | .sysfs_ops = &dmi_sysfs_attr_ops, | ||
280 | .default_attrs = dmi_sysfs_entry_attrs, | ||
281 | }; | ||
282 | |||
283 | static struct kobject *dmi_kobj; | ||
284 | static struct kset *dmi_kset; | ||
285 | |||
286 | /* Global count of all instances seen. Only for setup */ | ||
287 | static int __initdata instance_counts[MAX_ENTRY_TYPE + 1]; | ||
288 | |||
289 | /* Global positional count of all entries seen. Only for setup */ | ||
290 | static int __initdata position_count; | ||
291 | |||
292 | static void __init dmi_sysfs_register_handle(const struct dmi_header *dh, | ||
293 | void *_ret) | ||
294 | { | ||
295 | struct dmi_sysfs_entry *entry; | ||
296 | int *ret = _ret; | ||
297 | |||
298 | /* If a previous entry saw an error, short circuit */ | ||
299 | if (*ret) | ||
300 | return; | ||
301 | |||
302 | /* Allocate and register a new entry into the entries set */ | ||
303 | entry = kzalloc(sizeof(*entry), GFP_KERNEL); | ||
304 | if (!entry) { | ||
305 | *ret = -ENOMEM; | ||
306 | return; | ||
307 | } | ||
308 | |||
309 | /* Set the key */ | ||
310 | entry->dh = *dh; | ||
311 | entry->instance = instance_counts[dh->type]++; | ||
312 | entry->position = position_count++; | ||
313 | |||
314 | entry->kobj.kset = dmi_kset; | ||
315 | *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL, | ||
316 | "%d-%d", dh->type, entry->instance); | ||
317 | |||
318 | if (*ret) { | ||
319 | kfree(entry); | ||
320 | return; | ||
321 | } | ||
322 | |||
323 | /* Thread on the global list for cleanup */ | ||
324 | spin_lock(&entry_list_lock); | ||
325 | list_add_tail(&entry->list, &entry_list); | ||
326 | spin_unlock(&entry_list_lock); | ||
327 | |||
328 | /* Create the raw binary file to access the entry */ | ||
329 | *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr); | ||
330 | if (*ret) | ||
331 | goto out_err; | ||
332 | |||
333 | return; | ||
334 | out_err: | ||
335 | kobject_put(&entry->kobj); | ||
336 | return; | ||
337 | } | ||
338 | |||
339 | static void cleanup_entry_list(void) | ||
340 | { | ||
341 | struct dmi_sysfs_entry *entry, *next; | ||
342 | |||
343 | /* No locks, we are on our way out */ | ||
344 | list_for_each_entry_safe(entry, next, &entry_list, list) { | ||
345 | kobject_put(&entry->kobj); | ||
346 | } | ||
347 | } | ||
348 | |||
349 | static int __init dmi_sysfs_init(void) | ||
350 | { | ||
351 | int error = -ENOMEM; | ||
352 | int val; | ||
353 | |||
354 | /* Set up our directory */ | ||
355 | dmi_kobj = kobject_create_and_add("dmi", firmware_kobj); | ||
356 | if (!dmi_kobj) | ||
357 | goto err; | ||
358 | |||
359 | dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj); | ||
360 | if (!dmi_kset) | ||
361 | goto err; | ||
362 | |||
363 | val = 0; | ||
364 | error = dmi_walk(dmi_sysfs_register_handle, &val); | ||
365 | if (error) | ||
366 | goto err; | ||
367 | if (val) { | ||
368 | error = val; | ||
369 | goto err; | ||
370 | } | ||
371 | |||
372 | pr_debug("dmi-sysfs: loaded.\n"); | ||
373 | |||
374 | return 0; | ||
375 | err: | ||
376 | cleanup_entry_list(); | ||
377 | kset_unregister(dmi_kset); | ||
378 | kobject_put(dmi_kobj); | ||
379 | return error; | ||
380 | } | ||
381 | |||
382 | /* clean up everything. */ | ||
383 | static void __exit dmi_sysfs_exit(void) | ||
384 | { | ||
385 | pr_debug("dmi-sysfs: unloading.\n"); | ||
386 | cleanup_entry_list(); | ||
387 | kset_unregister(dmi_kset); | ||
388 | kobject_put(dmi_kobj); | ||
389 | } | ||
390 | |||
391 | module_init(dmi_sysfs_init); | ||
392 | module_exit(dmi_sysfs_exit); | ||
393 | |||
394 | MODULE_AUTHOR("Mike Waychison <mikew@google.com>"); | ||
395 | MODULE_DESCRIPTION("DMI sysfs support"); | ||
396 | MODULE_LICENSE("GPL"); | ||