1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * dmi-sysfs.c |
4 | * |
5 | * This module exports the DMI tables read-only to userspace through the |
6 | * sysfs file system. |
7 | * |
8 | * Data is currently found below |
9 | * /sys/firmware/dmi/... |
10 | * |
11 | * DMI attributes are presented in attribute files with names |
12 | * formatted using %d-%d, so that the first integer indicates the |
13 | * structure type (0-255), and the second field is the instance of that |
14 | * entry. |
15 | * |
16 | * Copyright 2011 Google, Inc. |
17 | */ |
18 | |
19 | #include <linux/kernel.h> |
20 | #include <linux/init.h> |
21 | #include <linux/module.h> |
22 | #include <linux/types.h> |
23 | #include <linux/kobject.h> |
24 | #include <linux/dmi.h> |
25 | #include <linux/capability.h> |
26 | #include <linux/slab.h> |
27 | #include <linux/list.h> |
28 | #include <linux/io.h> |
29 | #include <asm/dmi.h> |
30 | |
31 | #define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider |
32 | the top entry type is only 8 bits */ |
33 | |
34 | struct dmi_sysfs_entry { |
35 | struct dmi_header dh; |
36 | struct kobject kobj; |
37 | int instance; |
38 | int position; |
39 | struct list_head list; |
40 | struct kobject *child; |
41 | }; |
42 | |
43 | /* |
44 | * Global list of dmi_sysfs_entry. Even though this should only be |
45 | * manipulated at setup and teardown, the lazy nature of the kobject |
46 | * system means we get lazy removes. |
47 | */ |
48 | static LIST_HEAD(entry_list); |
49 | static DEFINE_SPINLOCK(entry_list_lock); |
50 | |
51 | /* dmi_sysfs_attribute - Top level attribute. used by all entries. */ |
52 | struct dmi_sysfs_attribute { |
53 | struct attribute attr; |
54 | ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf); |
55 | }; |
56 | |
57 | #define DMI_SYSFS_ATTR(_entry, _name) \ |
58 | struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \ |
59 | .attr = {.name = __stringify(_name), .mode = 0400}, \ |
60 | .show = dmi_sysfs_##_entry##_##_name, \ |
61 | } |
62 | |
63 | /* |
64 | * dmi_sysfs_mapped_attribute - Attribute where we require the entry be |
65 | * mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops. |
66 | */ |
67 | struct dmi_sysfs_mapped_attribute { |
68 | struct attribute attr; |
69 | ssize_t (*show)(struct dmi_sysfs_entry *entry, |
70 | const struct dmi_header *dh, |
71 | char *buf); |
72 | }; |
73 | |
74 | #define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \ |
75 | struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \ |
76 | .attr = {.name = __stringify(_name), .mode = 0400}, \ |
77 | .show = dmi_sysfs_##_entry##_##_name, \ |
78 | } |
79 | |
80 | /************************************************* |
81 | * Generic DMI entry support. |
82 | *************************************************/ |
83 | static void dmi_entry_free(struct kobject *kobj) |
84 | { |
85 | kfree(objp: kobj); |
86 | } |
87 | |
88 | static struct dmi_sysfs_entry *to_entry(struct kobject *kobj) |
89 | { |
90 | return container_of(kobj, struct dmi_sysfs_entry, kobj); |
91 | } |
92 | |
93 | static struct dmi_sysfs_attribute *to_attr(struct attribute *attr) |
94 | { |
95 | return container_of(attr, struct dmi_sysfs_attribute, attr); |
96 | } |
97 | |
98 | static ssize_t dmi_sysfs_attr_show(struct kobject *kobj, |
99 | struct attribute *_attr, char *buf) |
100 | { |
101 | struct dmi_sysfs_entry *entry = to_entry(kobj); |
102 | struct dmi_sysfs_attribute *attr = to_attr(attr: _attr); |
103 | |
104 | /* DMI stuff is only ever admin visible */ |
105 | if (!capable(CAP_SYS_ADMIN)) |
106 | return -EACCES; |
107 | |
108 | return attr->show(entry, buf); |
109 | } |
110 | |
111 | static const struct sysfs_ops dmi_sysfs_attr_ops = { |
112 | .show = dmi_sysfs_attr_show, |
113 | }; |
114 | |
115 | typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *, |
116 | const struct dmi_header *dh, void *); |
117 | |
118 | struct find_dmi_data { |
119 | struct dmi_sysfs_entry *entry; |
120 | dmi_callback callback; |
121 | void *private; |
122 | int instance_countdown; |
123 | ssize_t ret; |
124 | }; |
125 | |
126 | static void find_dmi_entry_helper(const struct dmi_header *dh, |
127 | void *_data) |
128 | { |
129 | struct find_dmi_data *data = _data; |
130 | struct dmi_sysfs_entry *entry = data->entry; |
131 | |
132 | /* Is this the entry we want? */ |
133 | if (dh->type != entry->dh.type) |
134 | return; |
135 | |
136 | if (data->instance_countdown != 0) { |
137 | /* try the next instance? */ |
138 | data->instance_countdown--; |
139 | return; |
140 | } |
141 | |
142 | /* |
143 | * Don't ever revisit the instance. Short circuit later |
144 | * instances by letting the instance_countdown run negative |
145 | */ |
146 | data->instance_countdown--; |
147 | |
148 | /* Found the entry */ |
149 | data->ret = data->callback(entry, dh, data->private); |
150 | } |
151 | |
152 | /* State for passing the read parameters through dmi_find_entry() */ |
153 | struct dmi_read_state { |
154 | char *buf; |
155 | loff_t pos; |
156 | size_t count; |
157 | }; |
158 | |
159 | static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry, |
160 | dmi_callback callback, void *private) |
161 | { |
162 | struct find_dmi_data data = { |
163 | .entry = entry, |
164 | .callback = callback, |
165 | .private = private, |
166 | .instance_countdown = entry->instance, |
167 | .ret = -EIO, /* To signal the entry disappeared */ |
168 | }; |
169 | int ret; |
170 | |
171 | ret = dmi_walk(decode: find_dmi_entry_helper, private_data: &data); |
172 | /* This shouldn't happen, but just in case. */ |
173 | if (ret) |
174 | return -EINVAL; |
175 | return data.ret; |
176 | } |
177 | |
178 | /* |
179 | * Calculate and return the byte length of the dmi entry identified by |
180 | * dh. This includes both the formatted portion as well as the |
181 | * unformatted string space, including the two trailing nul characters. |
182 | */ |
183 | static size_t dmi_entry_length(const struct dmi_header *dh) |
184 | { |
185 | const char *p = (const char *)dh; |
186 | |
187 | p += dh->length; |
188 | |
189 | while (p[0] || p[1]) |
190 | p++; |
191 | |
192 | return 2 + p - (const char *)dh; |
193 | } |
194 | |
195 | /************************************************* |
196 | * Support bits for specialized DMI entry support |
197 | *************************************************/ |
198 | struct dmi_entry_attr_show_data { |
199 | struct attribute *attr; |
200 | char *buf; |
201 | }; |
202 | |
203 | static ssize_t dmi_entry_attr_show_helper(struct dmi_sysfs_entry *entry, |
204 | const struct dmi_header *dh, |
205 | void *_data) |
206 | { |
207 | struct dmi_entry_attr_show_data *data = _data; |
208 | struct dmi_sysfs_mapped_attribute *attr; |
209 | |
210 | attr = container_of(data->attr, |
211 | struct dmi_sysfs_mapped_attribute, attr); |
212 | return attr->show(entry, dh, data->buf); |
213 | } |
214 | |
215 | static ssize_t dmi_entry_attr_show(struct kobject *kobj, |
216 | struct attribute *attr, |
217 | char *buf) |
218 | { |
219 | struct dmi_entry_attr_show_data data = { |
220 | .attr = attr, |
221 | .buf = buf, |
222 | }; |
223 | /* Find the entry according to our parent and call the |
224 | * normalized show method hanging off of the attribute */ |
225 | return find_dmi_entry(entry: to_entry(kobj: kobj->parent), |
226 | callback: dmi_entry_attr_show_helper, private: &data); |
227 | } |
228 | |
229 | static const struct sysfs_ops dmi_sysfs_specialize_attr_ops = { |
230 | .show = dmi_entry_attr_show, |
231 | }; |
232 | |
233 | /************************************************* |
234 | * Specialized DMI entry support. |
235 | *************************************************/ |
236 | |
237 | /*** Type 15 - System Event Table ***/ |
238 | |
239 | #define DMI_SEL_ACCESS_METHOD_IO8 0x00 |
240 | #define DMI_SEL_ACCESS_METHOD_IO2x8 0x01 |
241 | #define DMI_SEL_ACCESS_METHOD_IO16 0x02 |
242 | #define DMI_SEL_ACCESS_METHOD_PHYS32 0x03 |
243 | #define DMI_SEL_ACCESS_METHOD_GPNV 0x04 |
244 | |
245 | struct dmi_system_event_log { |
246 | struct dmi_header ; |
247 | u16 area_length; |
248 | u16 ; |
249 | u16 data_start_offset; |
250 | u8 access_method; |
251 | u8 status; |
252 | u32 change_token; |
253 | union { |
254 | struct { |
255 | u16 index_addr; |
256 | u16 data_addr; |
257 | } io; |
258 | u32 phys_addr32; |
259 | u16 gpnv_handle; |
260 | u32 access_method_address; |
261 | }; |
262 | u8 ; |
263 | u8 type_descriptors_supported_count; |
264 | u8 per_log_type_descriptor_length; |
265 | u8 supported_log_type_descriptos[]; |
266 | } __packed; |
267 | |
268 | #define DMI_SYSFS_SEL_FIELD(_field) \ |
269 | static ssize_t dmi_sysfs_sel_##_field(struct dmi_sysfs_entry *entry, \ |
270 | const struct dmi_header *dh, \ |
271 | char *buf) \ |
272 | { \ |
273 | struct dmi_system_event_log sel; \ |
274 | if (sizeof(sel) > dmi_entry_length(dh)) \ |
275 | return -EIO; \ |
276 | memcpy(&sel, dh, sizeof(sel)); \ |
277 | return sprintf(buf, "%u\n", sel._field); \ |
278 | } \ |
279 | static DMI_SYSFS_MAPPED_ATTR(sel, _field) |
280 | |
281 | DMI_SYSFS_SEL_FIELD(area_length); |
282 | DMI_SYSFS_SEL_FIELD(header_start_offset); |
283 | DMI_SYSFS_SEL_FIELD(data_start_offset); |
284 | DMI_SYSFS_SEL_FIELD(access_method); |
285 | DMI_SYSFS_SEL_FIELD(status); |
286 | DMI_SYSFS_SEL_FIELD(change_token); |
287 | DMI_SYSFS_SEL_FIELD(access_method_address); |
288 | DMI_SYSFS_SEL_FIELD(header_format); |
289 | DMI_SYSFS_SEL_FIELD(type_descriptors_supported_count); |
290 | DMI_SYSFS_SEL_FIELD(per_log_type_descriptor_length); |
291 | |
292 | static struct attribute *dmi_sysfs_sel_attrs[] = { |
293 | &dmi_sysfs_attr_sel_area_length.attr, |
294 | &dmi_sysfs_attr_sel_header_start_offset.attr, |
295 | &dmi_sysfs_attr_sel_data_start_offset.attr, |
296 | &dmi_sysfs_attr_sel_access_method.attr, |
297 | &dmi_sysfs_attr_sel_status.attr, |
298 | &dmi_sysfs_attr_sel_change_token.attr, |
299 | &dmi_sysfs_attr_sel_access_method_address.attr, |
300 | &dmi_sysfs_attr_sel_header_format.attr, |
301 | &dmi_sysfs_attr_sel_type_descriptors_supported_count.attr, |
302 | &dmi_sysfs_attr_sel_per_log_type_descriptor_length.attr, |
303 | NULL, |
304 | }; |
305 | ATTRIBUTE_GROUPS(dmi_sysfs_sel); |
306 | |
307 | static const struct kobj_type dmi_system_event_log_ktype = { |
308 | .release = dmi_entry_free, |
309 | .sysfs_ops = &dmi_sysfs_specialize_attr_ops, |
310 | .default_groups = dmi_sysfs_sel_groups, |
311 | }; |
312 | |
313 | #ifdef CONFIG_HAS_IOPORT |
314 | typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel, |
315 | loff_t offset); |
316 | |
317 | static DEFINE_MUTEX(io_port_lock); |
318 | |
319 | static u8 read_sel_8bit_indexed_io(const struct dmi_system_event_log *sel, |
320 | loff_t offset) |
321 | { |
322 | u8 ret; |
323 | |
324 | mutex_lock(&io_port_lock); |
325 | outb(value: (u8)offset, port: sel->io.index_addr); |
326 | ret = inb(port: sel->io.data_addr); |
327 | mutex_unlock(lock: &io_port_lock); |
328 | return ret; |
329 | } |
330 | |
331 | static u8 read_sel_2x8bit_indexed_io(const struct dmi_system_event_log *sel, |
332 | loff_t offset) |
333 | { |
334 | u8 ret; |
335 | |
336 | mutex_lock(&io_port_lock); |
337 | outb(value: (u8)offset, port: sel->io.index_addr); |
338 | outb(value: (u8)(offset >> 8), port: sel->io.index_addr + 1); |
339 | ret = inb(port: sel->io.data_addr); |
340 | mutex_unlock(lock: &io_port_lock); |
341 | return ret; |
342 | } |
343 | |
344 | static u8 read_sel_16bit_indexed_io(const struct dmi_system_event_log *sel, |
345 | loff_t offset) |
346 | { |
347 | u8 ret; |
348 | |
349 | mutex_lock(&io_port_lock); |
350 | outw(value: (u16)offset, port: sel->io.index_addr); |
351 | ret = inb(port: sel->io.data_addr); |
352 | mutex_unlock(lock: &io_port_lock); |
353 | return ret; |
354 | } |
355 | |
356 | static sel_io_reader sel_io_readers[] = { |
357 | [DMI_SEL_ACCESS_METHOD_IO8] = read_sel_8bit_indexed_io, |
358 | [DMI_SEL_ACCESS_METHOD_IO2x8] = read_sel_2x8bit_indexed_io, |
359 | [DMI_SEL_ACCESS_METHOD_IO16] = read_sel_16bit_indexed_io, |
360 | }; |
361 | |
362 | static ssize_t dmi_sel_raw_read_io(struct dmi_sysfs_entry *entry, |
363 | const struct dmi_system_event_log *sel, |
364 | char *buf, loff_t pos, size_t count) |
365 | { |
366 | ssize_t wrote = 0; |
367 | |
368 | sel_io_reader io_reader = sel_io_readers[sel->access_method]; |
369 | |
370 | while (count && pos < sel->area_length) { |
371 | count--; |
372 | *(buf++) = io_reader(sel, pos++); |
373 | wrote++; |
374 | } |
375 | |
376 | return wrote; |
377 | } |
378 | #endif |
379 | |
380 | static ssize_t dmi_sel_raw_read_phys32(struct dmi_sysfs_entry *entry, |
381 | const struct dmi_system_event_log *sel, |
382 | char *buf, loff_t pos, size_t count) |
383 | { |
384 | u8 __iomem *mapped; |
385 | ssize_t wrote = 0; |
386 | |
387 | mapped = dmi_remap(sel->access_method_address, sel->area_length); |
388 | if (!mapped) |
389 | return -EIO; |
390 | |
391 | while (count && pos < sel->area_length) { |
392 | count--; |
393 | *(buf++) = readb(addr: mapped + pos++); |
394 | wrote++; |
395 | } |
396 | |
397 | dmi_unmap(mapped); |
398 | return wrote; |
399 | } |
400 | |
401 | static ssize_t dmi_sel_raw_read_helper(struct dmi_sysfs_entry *entry, |
402 | const struct dmi_header *dh, |
403 | void *_state) |
404 | { |
405 | struct dmi_read_state *state = _state; |
406 | struct dmi_system_event_log sel; |
407 | |
408 | if (sizeof(sel) > dmi_entry_length(dh)) |
409 | return -EIO; |
410 | |
411 | memcpy(&sel, dh, sizeof(sel)); |
412 | |
413 | switch (sel.access_method) { |
414 | #ifdef CONFIG_HAS_IOPORT |
415 | case DMI_SEL_ACCESS_METHOD_IO8: |
416 | case DMI_SEL_ACCESS_METHOD_IO2x8: |
417 | case DMI_SEL_ACCESS_METHOD_IO16: |
418 | return dmi_sel_raw_read_io(entry, sel: &sel, buf: state->buf, |
419 | pos: state->pos, count: state->count); |
420 | #endif |
421 | case DMI_SEL_ACCESS_METHOD_PHYS32: |
422 | return dmi_sel_raw_read_phys32(entry, sel: &sel, buf: state->buf, |
423 | pos: state->pos, count: state->count); |
424 | case DMI_SEL_ACCESS_METHOD_GPNV: |
425 | pr_info_ratelimited("dmi-sysfs: GPNV support missing.\n" ); |
426 | return -EIO; |
427 | default: |
428 | pr_info_ratelimited("dmi-sysfs: Unknown access method %02x\n" , |
429 | sel.access_method); |
430 | return -EIO; |
431 | } |
432 | } |
433 | |
434 | static ssize_t dmi_sel_raw_read(struct file *filp, struct kobject *kobj, |
435 | struct bin_attribute *bin_attr, |
436 | char *buf, loff_t pos, size_t count) |
437 | { |
438 | struct dmi_sysfs_entry *entry = to_entry(kobj: kobj->parent); |
439 | struct dmi_read_state state = { |
440 | .buf = buf, |
441 | .pos = pos, |
442 | .count = count, |
443 | }; |
444 | |
445 | return find_dmi_entry(entry, callback: dmi_sel_raw_read_helper, private: &state); |
446 | } |
447 | |
448 | static struct bin_attribute dmi_sel_raw_attr = { |
449 | .attr = {.name = "raw_event_log" , .mode = 0400}, |
450 | .read = dmi_sel_raw_read, |
451 | }; |
452 | |
453 | static int dmi_system_event_log(struct dmi_sysfs_entry *entry) |
454 | { |
455 | int ret; |
456 | |
457 | entry->child = kzalloc(size: sizeof(*entry->child), GFP_KERNEL); |
458 | if (!entry->child) |
459 | return -ENOMEM; |
460 | ret = kobject_init_and_add(kobj: entry->child, |
461 | ktype: &dmi_system_event_log_ktype, |
462 | parent: &entry->kobj, |
463 | fmt: "system_event_log" ); |
464 | if (ret) |
465 | goto out_free; |
466 | |
467 | ret = sysfs_create_bin_file(kobj: entry->child, attr: &dmi_sel_raw_attr); |
468 | if (ret) |
469 | goto out_del; |
470 | |
471 | return 0; |
472 | |
473 | out_del: |
474 | kobject_del(kobj: entry->child); |
475 | out_free: |
476 | kfree(objp: entry->child); |
477 | return ret; |
478 | } |
479 | |
480 | /************************************************* |
481 | * Generic DMI entry support. |
482 | *************************************************/ |
483 | |
484 | static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf) |
485 | { |
486 | return sprintf(buf, fmt: "%d\n" , entry->dh.length); |
487 | } |
488 | |
489 | static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf) |
490 | { |
491 | return sprintf(buf, fmt: "%d\n" , entry->dh.handle); |
492 | } |
493 | |
494 | static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf) |
495 | { |
496 | return sprintf(buf, fmt: "%d\n" , entry->dh.type); |
497 | } |
498 | |
499 | static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry, |
500 | char *buf) |
501 | { |
502 | return sprintf(buf, fmt: "%d\n" , entry->instance); |
503 | } |
504 | |
505 | static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry, |
506 | char *buf) |
507 | { |
508 | return sprintf(buf, fmt: "%d\n" , entry->position); |
509 | } |
510 | |
511 | static DMI_SYSFS_ATTR(entry, length); |
512 | static DMI_SYSFS_ATTR(entry, handle); |
513 | static DMI_SYSFS_ATTR(entry, type); |
514 | static DMI_SYSFS_ATTR(entry, instance); |
515 | static DMI_SYSFS_ATTR(entry, position); |
516 | |
517 | static struct attribute *dmi_sysfs_entry_attrs[] = { |
518 | &dmi_sysfs_attr_entry_length.attr, |
519 | &dmi_sysfs_attr_entry_handle.attr, |
520 | &dmi_sysfs_attr_entry_type.attr, |
521 | &dmi_sysfs_attr_entry_instance.attr, |
522 | &dmi_sysfs_attr_entry_position.attr, |
523 | NULL, |
524 | }; |
525 | ATTRIBUTE_GROUPS(dmi_sysfs_entry); |
526 | |
527 | static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry, |
528 | const struct dmi_header *dh, |
529 | void *_state) |
530 | { |
531 | struct dmi_read_state *state = _state; |
532 | size_t entry_length; |
533 | |
534 | entry_length = dmi_entry_length(dh); |
535 | |
536 | return memory_read_from_buffer(to: state->buf, count: state->count, |
537 | ppos: &state->pos, from: dh, available: entry_length); |
538 | } |
539 | |
540 | static ssize_t dmi_entry_raw_read(struct file *filp, |
541 | struct kobject *kobj, |
542 | struct bin_attribute *bin_attr, |
543 | char *buf, loff_t pos, size_t count) |
544 | { |
545 | struct dmi_sysfs_entry *entry = to_entry(kobj); |
546 | struct dmi_read_state state = { |
547 | .buf = buf, |
548 | .pos = pos, |
549 | .count = count, |
550 | }; |
551 | |
552 | return find_dmi_entry(entry, callback: dmi_entry_raw_read_helper, private: &state); |
553 | } |
554 | |
555 | static const struct bin_attribute dmi_entry_raw_attr = { |
556 | .attr = {.name = "raw" , .mode = 0400}, |
557 | .read = dmi_entry_raw_read, |
558 | }; |
559 | |
560 | static void dmi_sysfs_entry_release(struct kobject *kobj) |
561 | { |
562 | struct dmi_sysfs_entry *entry = to_entry(kobj); |
563 | |
564 | spin_lock(lock: &entry_list_lock); |
565 | list_del(entry: &entry->list); |
566 | spin_unlock(lock: &entry_list_lock); |
567 | kfree(objp: entry); |
568 | } |
569 | |
570 | static const struct kobj_type dmi_sysfs_entry_ktype = { |
571 | .release = dmi_sysfs_entry_release, |
572 | .sysfs_ops = &dmi_sysfs_attr_ops, |
573 | .default_groups = dmi_sysfs_entry_groups, |
574 | }; |
575 | |
576 | static struct kset *dmi_kset; |
577 | |
578 | /* Global count of all instances seen. Only for setup */ |
579 | static int __initdata instance_counts[MAX_ENTRY_TYPE + 1]; |
580 | |
581 | /* Global positional count of all entries seen. Only for setup */ |
582 | static int __initdata position_count; |
583 | |
584 | static void __init dmi_sysfs_register_handle(const struct dmi_header *dh, |
585 | void *_ret) |
586 | { |
587 | struct dmi_sysfs_entry *entry; |
588 | int *ret = _ret; |
589 | |
590 | /* If a previous entry saw an error, short circuit */ |
591 | if (*ret) |
592 | return; |
593 | |
594 | /* Allocate and register a new entry into the entries set */ |
595 | entry = kzalloc(size: sizeof(*entry), GFP_KERNEL); |
596 | if (!entry) { |
597 | *ret = -ENOMEM; |
598 | return; |
599 | } |
600 | |
601 | /* Set the key */ |
602 | memcpy(&entry->dh, dh, sizeof(*dh)); |
603 | entry->instance = instance_counts[dh->type]++; |
604 | entry->position = position_count++; |
605 | |
606 | entry->kobj.kset = dmi_kset; |
607 | *ret = kobject_init_and_add(kobj: &entry->kobj, ktype: &dmi_sysfs_entry_ktype, NULL, |
608 | fmt: "%d-%d" , dh->type, entry->instance); |
609 | |
610 | /* Thread on the global list for cleanup */ |
611 | spin_lock(lock: &entry_list_lock); |
612 | list_add_tail(new: &entry->list, head: &entry_list); |
613 | spin_unlock(lock: &entry_list_lock); |
614 | |
615 | if (*ret) { |
616 | kobject_put(kobj: &entry->kobj); |
617 | return; |
618 | } |
619 | |
620 | /* Handle specializations by type */ |
621 | switch (dh->type) { |
622 | case DMI_ENTRY_SYSTEM_EVENT_LOG: |
623 | *ret = dmi_system_event_log(entry); |
624 | break; |
625 | default: |
626 | /* No specialization */ |
627 | break; |
628 | } |
629 | if (*ret) |
630 | goto out_err; |
631 | |
632 | /* Create the raw binary file to access the entry */ |
633 | *ret = sysfs_create_bin_file(kobj: &entry->kobj, attr: &dmi_entry_raw_attr); |
634 | if (*ret) |
635 | goto out_err; |
636 | |
637 | return; |
638 | out_err: |
639 | kobject_put(kobj: entry->child); |
640 | kobject_put(kobj: &entry->kobj); |
641 | return; |
642 | } |
643 | |
644 | static void cleanup_entry_list(void) |
645 | { |
646 | struct dmi_sysfs_entry *entry, *next; |
647 | |
648 | /* No locks, we are on our way out */ |
649 | list_for_each_entry_safe(entry, next, &entry_list, list) { |
650 | kobject_put(kobj: entry->child); |
651 | kobject_put(kobj: &entry->kobj); |
652 | } |
653 | } |
654 | |
655 | static int __init dmi_sysfs_init(void) |
656 | { |
657 | int error; |
658 | int val; |
659 | |
660 | if (!dmi_kobj) { |
661 | pr_debug("dmi-sysfs: dmi entry is absent.\n" ); |
662 | error = -ENODATA; |
663 | goto err; |
664 | } |
665 | |
666 | dmi_kset = kset_create_and_add(name: "entries" , NULL, parent_kobj: dmi_kobj); |
667 | if (!dmi_kset) { |
668 | error = -ENOMEM; |
669 | goto err; |
670 | } |
671 | |
672 | val = 0; |
673 | error = dmi_walk(decode: dmi_sysfs_register_handle, private_data: &val); |
674 | if (error) |
675 | goto err; |
676 | if (val) { |
677 | error = val; |
678 | goto err; |
679 | } |
680 | |
681 | pr_debug("dmi-sysfs: loaded.\n" ); |
682 | |
683 | return 0; |
684 | err: |
685 | cleanup_entry_list(); |
686 | kset_unregister(kset: dmi_kset); |
687 | return error; |
688 | } |
689 | |
690 | /* clean up everything. */ |
691 | static void __exit dmi_sysfs_exit(void) |
692 | { |
693 | pr_debug("dmi-sysfs: unloading.\n" ); |
694 | cleanup_entry_list(); |
695 | kset_unregister(kset: dmi_kset); |
696 | } |
697 | |
698 | module_init(dmi_sysfs_init); |
699 | module_exit(dmi_sysfs_exit); |
700 | |
701 | MODULE_AUTHOR("Mike Waychison <mikew@google.com>" ); |
702 | MODULE_DESCRIPTION("DMI sysfs support" ); |
703 | MODULE_LICENSE("GPL" ); |
704 | |