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