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 34struct 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 */ 48static LIST_HEAD(entry_list); 49static DEFINE_SPINLOCK(entry_list_lock); 50 51/* dmi_sysfs_attribute - Top level attribute. used by all entries. */ 52struct 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) \ 58struct 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 */ 67struct 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) \ 75struct 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 *************************************************/ 83static void dmi_entry_free(struct kobject *kobj) 84{ 85 kfree(kobj); 86} 87 88static struct dmi_sysfs_entry *to_entry(struct kobject *kobj) 89{ 90 return container_of(kobj, struct dmi_sysfs_entry, kobj); 91} 92 93static struct dmi_sysfs_attribute *to_attr(struct attribute *attr) 94{ 95 return container_of(attr, struct dmi_sysfs_attribute, attr); 96} 97 98static 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 111static const struct sysfs_ops dmi_sysfs_attr_ops = { 112 .show = dmi_sysfs_attr_show, 113}; 114 115typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *, 116 const struct dmi_header *dh, void *); 117 118struct 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 126static 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() */ 153struct dmi_read_state { 154 char *buf; 155 loff_t pos; 156 size_t count; 157}; 158 159static 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 */ 183static 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 *************************************************/ 198struct dmi_entry_attr_show_data { 199 struct attribute *attr; 200 char *buf; 201}; 202 203static 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 215static 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 229static 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 245struct 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) \ 269static 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} \ 279static DMI_SYSFS_MAPPED_ATTR(sel, _field) 280 281DMI_SYSFS_SEL_FIELD(area_length); 282DMI_SYSFS_SEL_FIELD(header_start_offset); 283DMI_SYSFS_SEL_FIELD(data_start_offset); 284DMI_SYSFS_SEL_FIELD(access_method); 285DMI_SYSFS_SEL_FIELD(status); 286DMI_SYSFS_SEL_FIELD(change_token); 287DMI_SYSFS_SEL_FIELD(access_method_address); 288DMI_SYSFS_SEL_FIELD(header_format); 289DMI_SYSFS_SEL_FIELD(type_descriptors_supported_count); 290DMI_SYSFS_SEL_FIELD(per_log_type_descriptor_length); 291 292static 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}; 305ATTRIBUTE_GROUPS(dmi_sysfs_sel); 306 307static 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 314typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel, 315 loff_t offset); 316 317static DEFINE_MUTEX(io_port_lock); 318 319static 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((u8)offset, sel->io.index_addr); 326 ret = inb(sel->io.data_addr); 327 mutex_unlock(&io_port_lock); 328 return ret; 329} 330 331static 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((u8)offset, sel->io.index_addr); 338 outb((u8)(offset >> 8), sel->io.index_addr + 1); 339 ret = inb(sel->io.data_addr); 340 mutex_unlock(&io_port_lock); 341 return ret; 342} 343 344static 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((u16)offset, sel->io.index_addr); 351 ret = inb(sel->io.data_addr); 352 mutex_unlock(&io_port_lock); 353 return ret; 354} 355 356static 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 362static 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 380static 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(mapped + pos++); 394 wrote++; 395 } 396 397 dmi_unmap(mapped); 398 return wrote; 399} 400 401static 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, state->buf, 419 state->pos, state->count); 420#endif 421 case DMI_SEL_ACCESS_METHOD_PHYS32: 422 return dmi_sel_raw_read_phys32(entry, &sel, state->buf, 423 state->pos, 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 434static 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->parent); 439 struct dmi_read_state state = { 440 .buf = buf, 441 .pos = pos, 442 .count = count, 443 }; 444 445 return find_dmi_entry(entry, dmi_sel_raw_read_helper, &state); 446} 447 448static struct bin_attribute dmi_sel_raw_attr = { 449 .attr = {.name = "raw_event_log", .mode = 0400}, 450 .read = dmi_sel_raw_read, 451}; 452 453static int dmi_system_event_log(struct dmi_sysfs_entry *entry) 454{ 455 int ret; 456 457 entry->child = kzalloc(sizeof(*entry->child), GFP_KERNEL); 458 if (!entry->child) 459 return -ENOMEM; 460 ret = kobject_init_and_add(entry->child, 461 &dmi_system_event_log_ktype, 462 &entry->kobj, 463 "system_event_log"); 464 if (ret) 465 goto out_free; 466 467 ret = sysfs_create_bin_file(entry->child, &dmi_sel_raw_attr); 468 if (ret) 469 goto out_del; 470 471 return 0; 472 473out_del: 474 kobject_del(entry->child); 475out_free: 476 kfree(entry->child); 477 return ret; 478} 479 480/************************************************* 481 * Generic DMI entry support. 482 *************************************************/ 483 484static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf) 485{ 486 return sprintf(buf, "%d\n", entry->dh.length); 487} 488 489static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf) 490{ 491 return sprintf(buf, "%d\n", entry->dh.handle); 492} 493 494static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf) 495{ 496 return sprintf(buf, "%d\n", entry->dh.type); 497} 498 499static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry, 500 char *buf) 501{ 502 return sprintf(buf, "%d\n", entry->instance); 503} 504 505static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry, 506 char *buf) 507{ 508 return sprintf(buf, "%d\n", entry->position); 509} 510 511static DMI_SYSFS_ATTR(entry, length); 512static DMI_SYSFS_ATTR(entry, handle); 513static DMI_SYSFS_ATTR(entry, type); 514static DMI_SYSFS_ATTR(entry, instance); 515static DMI_SYSFS_ATTR(entry, position); 516 517static 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}; 525ATTRIBUTE_GROUPS(dmi_sysfs_entry); 526 527static 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(state->buf, state->count, 537 &state->pos, dh, entry_length); 538} 539 540static 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, dmi_entry_raw_read_helper, &state); 553} 554 555static const struct bin_attribute dmi_entry_raw_attr = { 556 .attr = {.name = "raw", .mode = 0400}, 557 .read = dmi_entry_raw_read, 558}; 559 560static void dmi_sysfs_entry_release(struct kobject *kobj) 561{ 562 struct dmi_sysfs_entry *entry = to_entry(kobj); 563 564 spin_lock(&entry_list_lock); 565 list_del(&entry->list); 566 spin_unlock(&entry_list_lock); 567 kfree(entry); 568} 569 570static 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 576static struct kset *dmi_kset; 577 578/* Global count of all instances seen. Only for setup */ 579static int __initdata instance_counts[MAX_ENTRY_TYPE + 1]; 580 581/* Global positional count of all entries seen. Only for setup */ 582static int __initdata position_count; 583 584static 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(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(&entry->kobj, &dmi_sysfs_entry_ktype, NULL, 608 "%d-%d", dh->type, entry->instance); 609 610 /* Thread on the global list for cleanup */ 611 spin_lock(&entry_list_lock); 612 list_add_tail(&entry->list, &entry_list); 613 spin_unlock(&entry_list_lock); 614 615 if (*ret) { 616 kobject_put(&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(&entry->kobj, &dmi_entry_raw_attr); 634 if (*ret) 635 goto out_err; 636 637 return; 638out_err: 639 kobject_put(entry->child); 640 kobject_put(&entry->kobj); 641 return; 642} 643 644static 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(entry->child); 651 kobject_put(&entry->kobj); 652 } 653} 654 655static 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("entries", NULL, dmi_kobj); 667 if (!dmi_kset) { 668 error = -ENOMEM; 669 goto err; 670 } 671 672 val = 0; 673 error = dmi_walk(dmi_sysfs_register_handle, &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; 684err: 685 cleanup_entry_list(); 686 kset_unregister(dmi_kset); 687 return error; 688} 689 690/* clean up everything. */ 691static void __exit dmi_sysfs_exit(void) 692{ 693 pr_debug("dmi-sysfs: unloading.\n"); 694 cleanup_entry_list(); 695 kset_unregister(dmi_kset); 696} 697 698module_init(dmi_sysfs_init); 699module_exit(dmi_sysfs_exit); 700 701MODULE_AUTHOR("Mike Waychison <mikew@google.com>"); 702MODULE_DESCRIPTION("DMI sysfs support"); 703MODULE_LICENSE("GPL"); 704