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(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); 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(find_dmi_entry_helper, &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(to_entry(kobj->parent), 226 dmi_entry_attr_show_helper, &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 header; 247 u16 area_length; 248 u16 header_start_offset; 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 header_format; 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 typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel, 314 loff_t offset); 315 316 static DEFINE_MUTEX(io_port_lock); 317 318 static u8 read_sel_8bit_indexed_io(const struct dmi_system_event_log *sel, 319 loff_t offset) 320 { 321 u8 ret; 322 323 mutex_lock(&io_port_lock); 324 outb((u8)offset, sel->io.index_addr); 325 ret = inb(sel->io.data_addr); 326 mutex_unlock(&io_port_lock); 327 return ret; 328 } 329 330 static u8 read_sel_2x8bit_indexed_io(const struct dmi_system_event_log *sel, 331 loff_t offset) 332 { 333 u8 ret; 334 335 mutex_lock(&io_port_lock); 336 outb((u8)offset, sel->io.index_addr); 337 outb((u8)(offset >> 8), sel->io.index_addr + 1); 338 ret = inb(sel->io.data_addr); 339 mutex_unlock(&io_port_lock); 340 return ret; 341 } 342 343 static u8 read_sel_16bit_indexed_io(const struct dmi_system_event_log *sel, 344 loff_t offset) 345 { 346 u8 ret; 347 348 mutex_lock(&io_port_lock); 349 outw((u16)offset, sel->io.index_addr); 350 ret = inb(sel->io.data_addr); 351 mutex_unlock(&io_port_lock); 352 return ret; 353 } 354 355 static sel_io_reader sel_io_readers[] = { 356 [DMI_SEL_ACCESS_METHOD_IO8] = read_sel_8bit_indexed_io, 357 [DMI_SEL_ACCESS_METHOD_IO2x8] = read_sel_2x8bit_indexed_io, 358 [DMI_SEL_ACCESS_METHOD_IO16] = read_sel_16bit_indexed_io, 359 }; 360 361 static ssize_t dmi_sel_raw_read_io(struct dmi_sysfs_entry *entry, 362 const struct dmi_system_event_log *sel, 363 char *buf, loff_t pos, size_t count) 364 { 365 ssize_t wrote = 0; 366 367 sel_io_reader io_reader = sel_io_readers[sel->access_method]; 368 369 while (count && pos < sel->area_length) { 370 count--; 371 *(buf++) = io_reader(sel, pos++); 372 wrote++; 373 } 374 375 return wrote; 376 } 377 378 static ssize_t dmi_sel_raw_read_phys32(struct dmi_sysfs_entry *entry, 379 const struct dmi_system_event_log *sel, 380 char *buf, loff_t pos, size_t count) 381 { 382 u8 __iomem *mapped; 383 ssize_t wrote = 0; 384 385 mapped = dmi_remap(sel->access_method_address, sel->area_length); 386 if (!mapped) 387 return -EIO; 388 389 while (count && pos < sel->area_length) { 390 count--; 391 *(buf++) = readb(mapped + pos++); 392 wrote++; 393 } 394 395 dmi_unmap(mapped); 396 return wrote; 397 } 398 399 static ssize_t dmi_sel_raw_read_helper(struct dmi_sysfs_entry *entry, 400 const struct dmi_header *dh, 401 void *_state) 402 { 403 struct dmi_read_state *state = _state; 404 struct dmi_system_event_log sel; 405 406 if (sizeof(sel) > dmi_entry_length(dh)) 407 return -EIO; 408 409 memcpy(&sel, dh, sizeof(sel)); 410 411 switch (sel.access_method) { 412 case DMI_SEL_ACCESS_METHOD_IO8: 413 case DMI_SEL_ACCESS_METHOD_IO2x8: 414 case DMI_SEL_ACCESS_METHOD_IO16: 415 return dmi_sel_raw_read_io(entry, &sel, state->buf, 416 state->pos, state->count); 417 case DMI_SEL_ACCESS_METHOD_PHYS32: 418 return dmi_sel_raw_read_phys32(entry, &sel, state->buf, 419 state->pos, state->count); 420 case DMI_SEL_ACCESS_METHOD_GPNV: 421 pr_info_ratelimited("dmi-sysfs: GPNV support missing.\n"); 422 return -EIO; 423 default: 424 pr_info_ratelimited("dmi-sysfs: Unknown access method %02x\n", 425 sel.access_method); 426 return -EIO; 427 } 428 } 429 430 static ssize_t dmi_sel_raw_read(struct file *filp, struct kobject *kobj, 431 struct bin_attribute *bin_attr, 432 char *buf, loff_t pos, size_t count) 433 { 434 struct dmi_sysfs_entry *entry = to_entry(kobj->parent); 435 struct dmi_read_state state = { 436 .buf = buf, 437 .pos = pos, 438 .count = count, 439 }; 440 441 return find_dmi_entry(entry, dmi_sel_raw_read_helper, &state); 442 } 443 444 static struct bin_attribute dmi_sel_raw_attr = { 445 .attr = {.name = "raw_event_log", .mode = 0400}, 446 .read = dmi_sel_raw_read, 447 }; 448 449 static int dmi_system_event_log(struct dmi_sysfs_entry *entry) 450 { 451 int ret; 452 453 entry->child = kzalloc(sizeof(*entry->child), GFP_KERNEL); 454 if (!entry->child) 455 return -ENOMEM; 456 ret = kobject_init_and_add(entry->child, 457 &dmi_system_event_log_ktype, 458 &entry->kobj, 459 "system_event_log"); 460 if (ret) 461 goto out_free; 462 463 ret = sysfs_create_bin_file(entry->child, &dmi_sel_raw_attr); 464 if (ret) 465 goto out_del; 466 467 return 0; 468 469 out_del: 470 kobject_del(entry->child); 471 out_free: 472 kfree(entry->child); 473 return ret; 474 } 475 476 /************************************************* 477 * Generic DMI entry support. 478 *************************************************/ 479 480 static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf) 481 { 482 return sprintf(buf, "%d\n", entry->dh.length); 483 } 484 485 static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf) 486 { 487 return sprintf(buf, "%d\n", entry->dh.handle); 488 } 489 490 static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf) 491 { 492 return sprintf(buf, "%d\n", entry->dh.type); 493 } 494 495 static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry, 496 char *buf) 497 { 498 return sprintf(buf, "%d\n", entry->instance); 499 } 500 501 static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry, 502 char *buf) 503 { 504 return sprintf(buf, "%d\n", entry->position); 505 } 506 507 static DMI_SYSFS_ATTR(entry, length); 508 static DMI_SYSFS_ATTR(entry, handle); 509 static DMI_SYSFS_ATTR(entry, type); 510 static DMI_SYSFS_ATTR(entry, instance); 511 static DMI_SYSFS_ATTR(entry, position); 512 513 static struct attribute *dmi_sysfs_entry_attrs[] = { 514 &dmi_sysfs_attr_entry_length.attr, 515 &dmi_sysfs_attr_entry_handle.attr, 516 &dmi_sysfs_attr_entry_type.attr, 517 &dmi_sysfs_attr_entry_instance.attr, 518 &dmi_sysfs_attr_entry_position.attr, 519 NULL, 520 }; 521 ATTRIBUTE_GROUPS(dmi_sysfs_entry); 522 523 static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry, 524 const struct dmi_header *dh, 525 void *_state) 526 { 527 struct dmi_read_state *state = _state; 528 size_t entry_length; 529 530 entry_length = dmi_entry_length(dh); 531 532 return memory_read_from_buffer(state->buf, state->count, 533 &state->pos, dh, entry_length); 534 } 535 536 static ssize_t dmi_entry_raw_read(struct file *filp, 537 struct kobject *kobj, 538 struct bin_attribute *bin_attr, 539 char *buf, loff_t pos, size_t count) 540 { 541 struct dmi_sysfs_entry *entry = to_entry(kobj); 542 struct dmi_read_state state = { 543 .buf = buf, 544 .pos = pos, 545 .count = count, 546 }; 547 548 return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state); 549 } 550 551 static const struct bin_attribute dmi_entry_raw_attr = { 552 .attr = {.name = "raw", .mode = 0400}, 553 .read = dmi_entry_raw_read, 554 }; 555 556 static void dmi_sysfs_entry_release(struct kobject *kobj) 557 { 558 struct dmi_sysfs_entry *entry = to_entry(kobj); 559 560 spin_lock(&entry_list_lock); 561 list_del(&entry->list); 562 spin_unlock(&entry_list_lock); 563 kfree(entry); 564 } 565 566 static const struct kobj_type dmi_sysfs_entry_ktype = { 567 .release = dmi_sysfs_entry_release, 568 .sysfs_ops = &dmi_sysfs_attr_ops, 569 .default_groups = dmi_sysfs_entry_groups, 570 }; 571 572 static struct kset *dmi_kset; 573 574 /* Global count of all instances seen. Only for setup */ 575 static int __initdata instance_counts[MAX_ENTRY_TYPE + 1]; 576 577 /* Global positional count of all entries seen. Only for setup */ 578 static int __initdata position_count; 579 580 static void __init dmi_sysfs_register_handle(const struct dmi_header *dh, 581 void *_ret) 582 { 583 struct dmi_sysfs_entry *entry; 584 int *ret = _ret; 585 586 /* If a previous entry saw an error, short circuit */ 587 if (*ret) 588 return; 589 590 /* Allocate and register a new entry into the entries set */ 591 entry = kzalloc(sizeof(*entry), GFP_KERNEL); 592 if (!entry) { 593 *ret = -ENOMEM; 594 return; 595 } 596 597 /* Set the key */ 598 memcpy(&entry->dh, dh, sizeof(*dh)); 599 entry->instance = instance_counts[dh->type]++; 600 entry->position = position_count++; 601 602 entry->kobj.kset = dmi_kset; 603 *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL, 604 "%d-%d", dh->type, entry->instance); 605 606 /* Thread on the global list for cleanup */ 607 spin_lock(&entry_list_lock); 608 list_add_tail(&entry->list, &entry_list); 609 spin_unlock(&entry_list_lock); 610 611 if (*ret) { 612 kobject_put(&entry->kobj); 613 return; 614 } 615 616 /* Handle specializations by type */ 617 switch (dh->type) { 618 case DMI_ENTRY_SYSTEM_EVENT_LOG: 619 *ret = dmi_system_event_log(entry); 620 break; 621 default: 622 /* No specialization */ 623 break; 624 } 625 if (*ret) 626 goto out_err; 627 628 /* Create the raw binary file to access the entry */ 629 *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr); 630 if (*ret) 631 goto out_err; 632 633 return; 634 out_err: 635 kobject_put(entry->child); 636 kobject_put(&entry->kobj); 637 return; 638 } 639 640 static void cleanup_entry_list(void) 641 { 642 struct dmi_sysfs_entry *entry, *next; 643 644 /* No locks, we are on our way out */ 645 list_for_each_entry_safe(entry, next, &entry_list, list) { 646 kobject_put(entry->child); 647 kobject_put(&entry->kobj); 648 } 649 } 650 651 static int __init dmi_sysfs_init(void) 652 { 653 int error; 654 int val; 655 656 if (!dmi_kobj) { 657 pr_debug("dmi-sysfs: dmi entry is absent.\n"); 658 error = -ENODATA; 659 goto err; 660 } 661 662 dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj); 663 if (!dmi_kset) { 664 error = -ENOMEM; 665 goto err; 666 } 667 668 val = 0; 669 error = dmi_walk(dmi_sysfs_register_handle, &val); 670 if (error) 671 goto err; 672 if (val) { 673 error = val; 674 goto err; 675 } 676 677 pr_debug("dmi-sysfs: loaded.\n"); 678 679 return 0; 680 err: 681 cleanup_entry_list(); 682 kset_unregister(dmi_kset); 683 return error; 684 } 685 686 /* clean up everything. */ 687 static void __exit dmi_sysfs_exit(void) 688 { 689 pr_debug("dmi-sysfs: unloading.\n"); 690 cleanup_entry_list(); 691 kset_unregister(dmi_kset); 692 } 693 694 module_init(dmi_sysfs_init); 695 module_exit(dmi_sysfs_exit); 696 697 MODULE_AUTHOR("Mike Waychison <mikew@google.com>"); 698 MODULE_DESCRIPTION("DMI sysfs support"); 699 MODULE_LICENSE("GPL"); 700