2011-02-17 21:28:19

by Mike Waychison

[permalink] [raw]
Subject: [PATCH v1 0/5] Exporting DMI entries via syfs

This patchset applies to v2.6.38-rc5.

The following series exports information the DMI / SMBIOS tables via
sysfs under the path /sys/firmware/dmi.

For our purposes, we actively use the System Event Log, as described by
DMI entry Type 15. Currently, we have to grub around /dev/mem to find
this thing, and potentially need to issue IOs directly from userland to
get at the event log, which is suboptimal in terms of security and
architecture.

This series implements the basics needed to iterate through the DMI
entries safely from userland. Each entry has the handle, the formatted
length and the raw bytes exposed in their own sub-directory under
/sys/firmware/dmi/entries/<type>-<instance>.

As well, this series explodes type 15 as a proof-of-concept of how we
can expose kernel interpretations of the entry data. For most entries,
there isn't much sense in parsing the entry and exposing each field it
as a sysfs attribute as they can be interpreted from userland by
accessing the raw entry itself. Type 15 however describes an
indirection (pointing at the system event log), and it is generally
useful to export this log in raw form to the user (which requires
interpreting the DMI entry).

This driver relies solely on dmi_walk() for access to the dmi entries.
They are not kept mapped in the "rest" state. The only bits memo-ized
by the objects in sysfs are the dmi headers, which are used to the find
the entries via dmi_walk on read. Reading the raw bytes however is
'uncached' by this driver, so that any changes to the entries themselves
are reflected properly by the user-exposed interface.

We intend to use this on our production servers, though it seems to
also be sufficient to suit Chrome OS's needs
(http://code.google.com/p/chromium-os/issues/detail?id=6795) as well.

Thanks,

Mike Waychison

Related discussions
===================

- Andi Kleen suggesting that perhaps this data could be exported via
sysfs:

http://kerneltrap.org/mailarchive/linux-kernel/2010/9/28/4625643/thread

- Tim Hockin suggesting a potential layout:

https://lkml.org/lkml/2011/2/10/550



Patchset summary
================

firmware: Add DMI entry types to the headers
firmware: Basic dmi-sysfs support
firmware: Break out system_event_log in dmi-sysfs
firmware: Expose DMI type 15 System Event Log
firmware: Add documentation for /sys/firmware/dmi

Diffstat
========

Documentation/ABI/testing/sysfs-firmware-dmi | 101 ++++
drivers/firmware/Kconfig | 11
drivers/firmware/Makefile | 1
drivers/firmware/dmi-sysfs.c | 664 +++++++++++++++++++++++++++
include/linux/dmi.h | 47 +
5 files changed, 824 insertions(+)

changeLog:
==========
- v1
- Initial public send-out.


2011-02-17 21:28:23

by Mike Waychison

[permalink] [raw]
Subject: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

Introduce a new module "dmi-sysfs" that exports the broken out entries
of the DMI table through sysfs.

Entries are enumerated via dmi_walk() on module load, and are populated
as kobjects rooted at /sys/firmware/dmi/entries.

Entries are named "<type>-<instance>", where:
<type> : is the type of the entry, and
<instance> : is the ordinal count within the DMI table of that
entry type. This instance is used in lieu the DMI
entry's handle as no assurances are made by the kernel
that handles are unique.

All entries export the following attributes:
length : The length of the formatted portion of the entry
handle : The handle given to this entry by the firmware
raw : The raw bytes of the entire entry, including the
formatted portion, the unformatted (strings) portion,
and the two terminating nul characters.

Entries in dmi-sysfs are kobject backed members called "struct
dmi_sysfs_entry" and belong to dmi_kset. They are threaded through
entry_list (protected by entry_list_lock) so that we can find them at
cleanup time.

Signed-off-by: Mike Waychison <[email protected]>
---
drivers/firmware/Kconfig | 11 +
drivers/firmware/Makefile | 1
drivers/firmware/dmi-sysfs.c | 362 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 374 insertions(+), 0 deletions(-)
create mode 100644 drivers/firmware/dmi-sysfs.c

diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index e710424..959175d 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -113,6 +113,17 @@ config DMIID
information from userspace through /sys/class/dmi/id/ or if you want
DMI-based module auto-loading.

+config DMI_SYSFS
+ tristate "DMI table support in sysfs"
+ depends on SYSFS && DMI
+ default X86
+ help
+ Say Y or M here to enable the exporting of the raw DMI table
+ data via sysfs. This is useful for consuming the data without
+ requiring any access to /dev/mem at all. Tables are found
+ under /sys/firmware/dmi when this option is enabled and
+ loaded.
+
config ISCSI_IBFT_FIND
bool "iSCSI Boot Firmware Table Attributes"
depends on X86
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 1c3c173..20c17fc 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -2,6 +2,7 @@
# Makefile for the linux kernel.
#
obj-$(CONFIG_DMI) += dmi_scan.o
+obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
obj-$(CONFIG_EDD) += edd.o
obj-$(CONFIG_EFI_VARS) += efivars.o
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 0000000..adcd604
--- /dev/null
+++ b/drivers/firmware/dmi-sysfs.c
@@ -0,0 +1,362 @@
+/*
+ * dmi-sysfs.c
+ *
+ * This module exports the DMI tables read-only to userspace through the
+ * sysfs file system.
+ *
+ * Data is currently found below
+ * /sys/firmware/dmi/...
+ *
+ * DMI attributes are presented in attribute files with names
+ * formatted using %d-%d, so that the first integer indicates the
+ * structure type (0-255), and the second field is the instance of that
+ * entry.
+ *
+ * Copyright 2010 Google, Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kobject.h>
+#include <linux/dmi.h>
+#include <linux/capability.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/io.h>
+
+#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
+ the top entry type is only 8 bits */
+
+struct dmi_sysfs_entry {
+ struct dmi_header dh;
+ struct kobject kobj;
+ int instance;
+ struct list_head list;
+};
+
+/*
+ * Global list of dmi_sysfs_entry. Even though this should only be
+ * manipulated at setup and teardown, the lazy nature of the kobject
+ * system means we get lazy removes.
+ */
+static LIST_HEAD(entry_list);
+static DEFINE_SPINLOCK(entry_list_lock);
+
+/* dmi_sysfs_attribute - Top level attribute. used by all entries. */
+struct dmi_sysfs_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
+};
+
+#define DMI_SYSFS_ATTR(_entry, _name) \
+struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
+ .attr = {.name = __stringify(_name), .mode = 0400}, \
+ .show = dmi_sysfs_##_entry##_##_name, \
+}
+
+/*
+ * dmi_sysfs_mapped_attribute - Attribute where we require the entry be
+ * mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops.
+ */
+struct dmi_sysfs_mapped_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct dmi_sysfs_entry *entry,
+ const struct dmi_header *dh,
+ char *buf);
+};
+
+#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
+struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
+ .attr = {.name = __stringify(_name), .mode = 0400}, \
+ .show = dmi_sysfs_##_entry##_##_name, \
+}
+
+/*************************************************
+ * Generic DMI entry support.
+ *************************************************/
+
+static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
+{
+ return container_of(kobj, struct dmi_sysfs_entry, kobj);
+}
+
+static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
+{
+ return container_of(attr, struct dmi_sysfs_attribute, attr);
+}
+
+static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
+ struct attribute *_attr, char *buf)
+{
+ struct dmi_sysfs_entry *entry = to_entry(kobj);
+ struct dmi_sysfs_attribute *attr = to_attr(_attr);
+
+ /* DMI stuff is only ever admin visible */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ return attr->show(entry, buf);
+}
+
+static const struct sysfs_ops dmi_sysfs_attr_ops = {
+ .show = dmi_sysfs_attr_show,
+};
+
+typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
+ const struct dmi_header *dh, void *);
+
+struct find_dmi_data {
+ struct dmi_sysfs_entry *entry;
+ dmi_callback callback;
+ void *private;
+ int instance_countdown;
+ ssize_t ret;
+};
+
+static void find_dmi_entry_helper(const struct dmi_header *dh,
+ void *_data)
+{
+ struct find_dmi_data *data = _data;
+ struct dmi_sysfs_entry *entry = data->entry;
+
+ /* Is this the entry we want? */
+ if (dh->type != entry->dh.type)
+ return;
+
+ if (data->instance_countdown != 0) {
+ /* try the next instance? */
+ data->instance_countdown--;
+ return;
+ }
+
+ /* Found the entry */
+ data->ret = data->callback(entry, dh, data->private);
+}
+
+/* State for passing the read parameters through dmi_find_entry() */
+struct dmi_read_state {
+ char *buf;
+ loff_t pos;
+ size_t count;
+};
+
+static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
+ dmi_callback callback, void *private)
+{
+ struct find_dmi_data data = {
+ .entry = entry,
+ .callback = callback,
+ .private = private,
+ .instance_countdown = entry->instance,
+ .ret = -EIO, /* To signal the entry disappeared */
+ };
+ int ret;
+
+ ret = dmi_walk(find_dmi_entry_helper, &data);
+ /* This shouldn't happen, but just in case. */
+ if (ret)
+ return -EINVAL;
+ return data.ret;
+}
+
+/*
+ * Calculate and return the byte length of the dmi entry identified by
+ * dh. This includes both the formatted portion as well as the
+ * unformatted string space, including the two trailing nul characters.
+ */
+static size_t dmi_entry_length(const struct dmi_header *dh)
+{
+ const char *p = (const char *)dh;
+
+ p += dh->length;
+
+ while (p[0] || p[1])
+ p++;
+
+ return 2 + p - (const char *)dh;
+}
+
+/*************************************************
+ * Generic DMI entry support.
+ *************************************************/
+
+static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
+{
+ return sprintf(buf, "%d\n", entry->dh.length);
+}
+
+static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
+{
+ return sprintf(buf, "%d\n", entry->dh.handle);
+}
+
+static DMI_SYSFS_ATTR(entry, length);
+static DMI_SYSFS_ATTR(entry, handle);
+
+static struct attribute *dmi_sysfs_entry_attrs[] = {
+ &dmi_sysfs_attr_entry_length.attr,
+ &dmi_sysfs_attr_entry_handle.attr,
+ NULL,
+};
+
+static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
+ const struct dmi_header *dh,
+ void *_state)
+{
+ struct dmi_read_state *state = _state;
+ size_t entry_length;
+
+ entry_length = dmi_entry_length(dh);
+
+ return memory_read_from_buffer(state->buf, state->count,
+ &state->pos, dh, entry_length);
+}
+
+static ssize_t dmi_entry_raw_read(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t pos, size_t count)
+{
+ struct dmi_sysfs_entry *entry = to_entry(kobj);
+ struct dmi_read_state state = {
+ .buf = buf,
+ .pos = pos,
+ .count = count,
+ };
+
+ return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
+}
+
+static const struct bin_attribute dmi_entry_raw_attr = {
+ .attr = {.name = "raw", .mode = 0400},
+ .read = dmi_entry_raw_read,
+};
+
+static void dmi_sysfs_entry_release(struct kobject *kobj)
+{
+ struct dmi_sysfs_entry *entry = to_entry(kobj);
+ sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr);
+ spin_lock(&entry_list_lock);
+ list_del(&entry->list);
+ spin_unlock(&entry_list_lock);
+ kfree(entry);
+}
+
+static struct kobj_type dmi_sysfs_entry_ktype = {
+ .release = dmi_sysfs_entry_release,
+ .sysfs_ops = &dmi_sysfs_attr_ops,
+ .default_attrs = dmi_sysfs_entry_attrs,
+};
+
+static struct kobject *dmi_kobj;
+static struct kset *dmi_kset;
+
+/* Global count of all instances seen. Only for setup */
+static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
+
+static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
+ void *_ret)
+{
+ struct dmi_sysfs_entry *entry;
+ int *ret = _ret;
+
+ /* If a previous entry saw an error, short circuit */
+ if (*ret)
+ return;
+
+ /* Allocate and register a new entry into the entries set */
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ *ret = -ENOMEM;
+ return;
+ }
+
+ /* Set the key */
+ entry->dh = *dh;
+ entry->instance = instance_counts[dh->type]++;
+
+ entry->kobj.kset = dmi_kset;
+ *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
+ "%d-%d", dh->type, entry->instance);
+
+ if (*ret) {
+ kfree(entry);
+ return;
+ }
+
+ /* Thread on the global list for cleanup */
+ spin_lock(&entry_list_lock);
+ list_add_tail(&entry->list, &entry_list);
+ spin_unlock(&entry_list_lock);
+
+ /* Create the raw binary file to access the entry */
+ *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
+ if (*ret)
+ goto out_err;
+
+ return;
+out_err:
+ kobject_put(&entry->kobj);
+ return;
+}
+
+static void cleanup_entry_list(void)
+{
+ struct dmi_sysfs_entry *entry, *next;
+
+ /* No locks, we are on our way out */
+ list_for_each_entry_safe(entry, next, &entry_list, list) {
+ kobject_put(&entry->kobj);
+ }
+}
+
+static int __init dmi_sysfs_init(void)
+{
+ int error = -ENOMEM;
+ int val;
+
+ /* Set up our directory */
+ dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
+ if (!dmi_kobj)
+ goto err;
+
+ dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
+ if (!dmi_kset)
+ goto err;
+
+ val = 0;
+ error = dmi_walk(dmi_sysfs_register_handle, &val);
+ if (error)
+ goto err;
+ if (val) {
+ error = val;
+ goto err;
+ }
+
+ pr_info("dmi-sysfs: loaded.\n");
+
+ return 0;
+err:
+ cleanup_entry_list();
+ kset_unregister(dmi_kset);
+ kobject_put(dmi_kobj);
+ return error;
+}
+
+/* clean up everything. */
+static void __exit dmi_sysfs_exit(void)
+{
+ pr_info("dmi-sysfs: unloading.\n");
+ cleanup_entry_list();
+ kset_unregister(dmi_kset);
+ kobject_put(dmi_kobj);
+}
+
+module_init(dmi_sysfs_init);
+module_exit(dmi_sysfs_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_DESCRIPTION("DMI sysfs support");
+MODULE_LICENSE("GPL");

2011-02-17 21:28:34

by Mike Waychison

[permalink] [raw]
Subject: [PATCH v1 1/5] firmware: Add DMI entry types to the headers

In preparation for the upcoming commits, introduce the DMI entry types to
the headers. These type names are based on those specified in the DMTF
SMBIOS specification version 2.7.1.

Signed-off-by: Mike Waychison <[email protected]>
---
include/linux/dmi.h | 47 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 47 insertions(+), 0 deletions(-)

diff --git a/include/linux/dmi.h b/include/linux/dmi.h
index 90e087f..f156cca 100644
--- a/include/linux/dmi.h
+++ b/include/linux/dmi.h
@@ -23,6 +23,53 @@ enum dmi_device_type {
DMI_DEV_TYPE_DEV_ONBOARD = -3,
};

+enum dmi_entry_type {
+ DMI_ENTRY_BIOS = 0,
+ DMI_ENTRY_SYSTEM,
+ DMI_ENTRY_BASEBOARD,
+ DMI_ENTRY_CHASSIS,
+ DMI_ENTRY_PROCESSOR,
+ DMI_ENTRY_MEM_CONTROLLER,
+ DMI_ENTRY_MEM_MODULE,
+ DMI_ENTRY_CACHE,
+ DMI_ENTRY_PORT_CONNECTOR,
+ DMI_ENTRY_SYSTEM_SLOT,
+ DMI_ENTRY_ONBOARD_DEVICE,
+ DMI_ENTRY_OEMSTRINGS,
+ DMI_ENTRY_SYSCONF,
+ DMI_ENTRY_BIOS_LANG,
+ DMI_ENTRY_GROUP_ASSOC,
+ DMI_ENTRY_SYSTEM_EVENT_LOG,
+ DMI_ENTRY_PHYS_MEM_ARRAY,
+ DMI_ENTRY_MEM_DEVICE,
+ DMI_ENTRY_32_MEM_ERROR,
+ DMI_ENTRY_MEM_ARRAY_MAPPED_ADDR,
+ DMI_ENTRY_MEM_DEV_MAPPED_ADDR,
+ DMI_ENTRY_BUILTIN_POINTING_DEV,
+ DMI_ENTRY_PORTABLE_BATTERY,
+ DMI_ENTRY_SYSTEM_RESET,
+ DMI_ENTRY_HW_SECURITY,
+ DMI_ENTRY_SYSTEM_POWER_CONTROLS,
+ DMI_ENTRY_VOLTAGE_PROBE,
+ DMI_ENTRY_COOLING_DEV,
+ DMI_ENTRY_TEMP_PROBE,
+ DMI_ENTRY_ELECTRICAL_CURRENT_PROBE,
+ DMI_ENTRY_OOB_REMOTE_ACCESS,
+ DMI_ENTRY_BIS_ENTRY,
+ DMI_ENTRY_SYSTEM_BOOT,
+ DMI_ENTRY_MGMT_DEV,
+ DMI_ENTRY_MGMT_DEV_COMPONENT,
+ DMI_ENTRY_MGMT_DEV_THRES,
+ DMI_ENTRY_MEM_CHANNEL,
+ DMI_ENTRY_IPMI_DEV,
+ DMI_ENTRY_SYS_POWER_SUPPLY,
+ DMI_ENTRY_ADDITIONAL,
+ DMI_ENTRY_ONBOARD_DEV_EXT,
+ DMI_ENTRY_MGMT_CONTROLLER_HOST,
+ DMI_ENTRY_INACTIVE = 126,
+ DMI_ENTRY_END_OF_TABLE = 127,
+};
+
struct dmi_header {
u8 type;
u8 length;

2011-02-17 21:28:37

by Mike Waychison

[permalink] [raw]
Subject: [PATCH v1 3/5] firmware: Break out system_event_log in dmi-sysfs

The optional type 15 entry of the DMI table describes a non-volatile
storage-backed system event log.

In preparation for the next commit which exposes the raw bits of the
event log to userland, create a new sub-directory within the dmi entry
called "system_event_log" and expose attribute files that describe the
event log itself.

Currently, only a single child object is permitted within a
dmi_sysfs_entry. We simply point at this child from the dmi_sysfs_entry
if it exists.

Signed-off-by: Mike Waychison <[email protected]>
---
drivers/firmware/dmi-sysfs.c | 159 ++++++++++++++++++++++++++++++++++++++++++
1 files changed, 159 insertions(+), 0 deletions(-)

diff --git a/drivers/firmware/dmi-sysfs.c b/drivers/firmware/dmi-sysfs.c
index adcd604..3f49042 100644
--- a/drivers/firmware/dmi-sysfs.c
+++ b/drivers/firmware/dmi-sysfs.c
@@ -34,6 +34,7 @@ struct dmi_sysfs_entry {
struct kobject kobj;
int instance;
struct list_head list;
+ struct kobject *child;
};

/*
@@ -76,6 +77,10 @@ struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
/*************************************************
* Generic DMI entry support.
*************************************************/
+static void dmi_entry_free(struct kobject *kobj)
+{
+ kfree(kobj);
+}

static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
{
@@ -179,6 +184,146 @@ static size_t dmi_entry_length(const struct dmi_header *dh)
}

/*************************************************
+ * Support bits for specialized DMI entry support
+ *************************************************/
+struct dmi_entry_attr_show_data {
+ struct attribute *attr;
+ char *buf;
+};
+
+static ssize_t dmi_entry_attr_show_helper(struct dmi_sysfs_entry *entry,
+ const struct dmi_header *dh,
+ void *_data)
+{
+ struct dmi_entry_attr_show_data *data = _data;
+ struct dmi_sysfs_mapped_attribute *attr;
+
+ attr = container_of(data->attr,
+ struct dmi_sysfs_mapped_attribute, attr);
+ return attr->show(entry, dh, data->buf);
+}
+
+static ssize_t dmi_entry_attr_show(struct kobject *kobj,
+ struct attribute *attr,
+ char *buf)
+{
+ struct dmi_entry_attr_show_data data = {
+ .attr = attr,
+ .buf = buf,
+ };
+ /* Find the entry according to our parent and call the
+ * normalized show method hanging off of the attribute */
+ return find_dmi_entry(to_entry(kobj->parent),
+ dmi_entry_attr_show_helper, &data);
+}
+
+static const struct sysfs_ops dmi_sysfs_specialize_attr_ops = {
+ .show = dmi_entry_attr_show,
+};
+
+/*************************************************
+ * Specialized DMI entry support.
+ *************************************************/
+
+/*** Type 15 - System Event Table ***/
+
+#define DMI_SEL_ACCESS_METHOD_IO8 0x00
+#define DMI_SEL_ACCESS_METHOD_IO2x8 0x01
+#define DMI_SEL_ACCESS_METHOD_IO16 0x02
+#define DMI_SEL_ACCESS_METHOD_PHYS32 0x03
+#define DMI_SEL_ACCESS_METHOD_GPNV 0x04
+
+struct dmi_system_event_log {
+ struct dmi_header header;
+ u16 area_length;
+ u16 header_start_offset;
+ u16 data_start_offset;
+ u8 access_method;
+ u8 status;
+ u32 change_token;
+ union {
+ struct {
+ u16 index_addr;
+ u16 data_addr;
+ } io;
+ u32 phys_addr32;
+ u16 gpnv_handle;
+ u32 access_method_address;
+ };
+ u8 header_format;
+ u8 type_descriptors_supported_count;
+ u8 per_log_type_descriptor_length;
+ u8 supported_log_type_descriptos[0];
+} __packed;
+
+static const struct dmi_system_event_log *to_sel(const struct dmi_header *dh)
+{
+ return (const struct dmi_system_event_log *)dh;
+}
+
+#define DMI_SYSFS_SEL_FIELD(_field) \
+static ssize_t dmi_sysfs_sel_##_field(struct dmi_sysfs_entry *entry, \
+ const struct dmi_header *dh, \
+ char *buf) \
+{ \
+ const struct dmi_system_event_log *sel = to_sel(dh); \
+ if (sizeof(*sel) > dmi_entry_length(dh)) \
+ return -EIO; \
+ return sprintf(buf, "%u\n", sel->_field); \
+} \
+static DMI_SYSFS_MAPPED_ATTR(sel, _field)
+
+DMI_SYSFS_SEL_FIELD(area_length);
+DMI_SYSFS_SEL_FIELD(header_start_offset);
+DMI_SYSFS_SEL_FIELD(data_start_offset);
+DMI_SYSFS_SEL_FIELD(access_method);
+DMI_SYSFS_SEL_FIELD(status);
+DMI_SYSFS_SEL_FIELD(change_token);
+DMI_SYSFS_SEL_FIELD(access_method_address);
+DMI_SYSFS_SEL_FIELD(header_format);
+DMI_SYSFS_SEL_FIELD(type_descriptors_supported_count);
+DMI_SYSFS_SEL_FIELD(per_log_type_descriptor_length);
+
+static struct attribute *dmi_sysfs_sel_attrs[] = {
+ &dmi_sysfs_attr_sel_area_length.attr,
+ &dmi_sysfs_attr_sel_header_start_offset.attr,
+ &dmi_sysfs_attr_sel_data_start_offset.attr,
+ &dmi_sysfs_attr_sel_access_method.attr,
+ &dmi_sysfs_attr_sel_status.attr,
+ &dmi_sysfs_attr_sel_change_token.attr,
+ &dmi_sysfs_attr_sel_access_method_address.attr,
+ &dmi_sysfs_attr_sel_header_format.attr,
+ &dmi_sysfs_attr_sel_type_descriptors_supported_count.attr,
+ &dmi_sysfs_attr_sel_per_log_type_descriptor_length.attr,
+ NULL,
+};
+
+
+static struct kobj_type dmi_system_event_log_ktype = {
+ .release = dmi_entry_free,
+ .sysfs_ops = &dmi_sysfs_specialize_attr_ops,
+ .default_attrs = dmi_sysfs_sel_attrs,
+};
+
+static int dmi_system_event_log(struct dmi_sysfs_entry *entry)
+{
+ int ret;
+
+ entry->child = kzalloc(sizeof(*entry->child), GFP_KERNEL);
+ if (!entry->child)
+ return -ENOMEM;
+ ret = kobject_init_and_add(entry->child,
+ &dmi_system_event_log_ktype,
+ &entry->kobj,
+ "system_event_log");
+ if (ret)
+ goto out_free;
+out_free:
+ kfree(entry->child);
+ return ret;
+}
+
+/*************************************************
* Generic DMI entry support.
*************************************************/

@@ -291,6 +436,18 @@ static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
list_add_tail(&entry->list, &entry_list);
spin_unlock(&entry_list_lock);

+ /* Handle specializations by type */
+ switch (dh->type) {
+ case DMI_ENTRY_SYSTEM_EVENT_LOG:
+ *ret = dmi_system_event_log(entry);
+ break;
+ default:
+ /* No specialization */
+ break;
+ }
+ if (*ret)
+ goto out_err;
+
/* Create the raw binary file to access the entry */
*ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
if (*ret)
@@ -298,6 +455,7 @@ static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,

return;
out_err:
+ kobject_put(entry->child);
kobject_put(&entry->kobj);
return;
}
@@ -308,6 +466,7 @@ static void cleanup_entry_list(void)

/* No locks, we are on our way out */
list_for_each_entry_safe(entry, next, &entry_list, list) {
+ kobject_put(entry->child);
kobject_put(&entry->kobj);
}
}

2011-02-17 21:29:04

by Mike Waychison

[permalink] [raw]
Subject: [PATCH v1 4/5] firmware: Expose DMI type 15 System Event Log

The System Event Log described by DMI entry type 15 may be backed by
either memory or may be indirectly accessed via an IO index/data
register pair.

In order to get read access to this log, expose it in the
"system_event_log" sub-directory of type 15 DMI entries, ie:
/sys/firmware/dmi/entries/15-0/system_event_log/raw_event_log.

This commit handles both IO accessed and memory access system event
logs. OEM specific access and GPNV support is explicitly not handled
and we error out in the logs when we do not recognize the access method.

Signed-off-by: Mike Waychison <[email protected]>
---
drivers/firmware/dmi-sysfs.c | 143 ++++++++++++++++++++++++++++++++++++++++++
1 files changed, 143 insertions(+), 0 deletions(-)

diff --git a/drivers/firmware/dmi-sysfs.c b/drivers/firmware/dmi-sysfs.c
index 3f49042..51462fe 100644
--- a/drivers/firmware/dmi-sysfs.c
+++ b/drivers/firmware/dmi-sysfs.c
@@ -305,6 +305,140 @@ static struct kobj_type dmi_system_event_log_ktype = {
.default_attrs = dmi_sysfs_sel_attrs,
};

+typedef u8 (*sel_io_reader)(const struct dmi_system_event_log *sel,
+ loff_t offset);
+
+static DEFINE_MUTEX(io_port_lock);
+
+static u8 read_sel_8bit_indexed_io(const struct dmi_system_event_log *sel,
+ loff_t offset)
+{
+ u8 ret;
+
+ mutex_lock(&io_port_lock);
+ outb((u8)offset, sel->io.index_addr);
+ ret = inb(sel->io.data_addr);
+ mutex_unlock(&io_port_lock);
+ return ret;
+}
+
+static u8 read_sel_2x8bit_indexed_io(const struct dmi_system_event_log *sel,
+ loff_t offset)
+{
+ u8 ret;
+
+ mutex_lock(&io_port_lock);
+ outb((u8)offset, sel->io.index_addr);
+ outb((u8)(offset >> 8), sel->io.index_addr + 1);
+ ret = inb(sel->io.data_addr);
+ mutex_unlock(&io_port_lock);
+ return ret;
+}
+
+static u8 read_sel_16bit_indexed_io(const struct dmi_system_event_log *sel,
+ loff_t offset)
+{
+ u8 ret;
+
+ mutex_lock(&io_port_lock);
+ outw((u16)offset, sel->io.index_addr);
+ ret = inb(sel->io.data_addr);
+ mutex_unlock(&io_port_lock);
+ return ret;
+}
+
+static sel_io_reader sel_io_readers[] = {
+ [DMI_SEL_ACCESS_METHOD_IO8] = read_sel_8bit_indexed_io,
+ [DMI_SEL_ACCESS_METHOD_IO2x8] = read_sel_2x8bit_indexed_io,
+ [DMI_SEL_ACCESS_METHOD_IO16] = read_sel_16bit_indexed_io,
+};
+
+static ssize_t dmi_sel_raw_read_io(struct dmi_sysfs_entry *entry,
+ const struct dmi_system_event_log *sel,
+ char *buf, loff_t pos, size_t count)
+{
+ ssize_t wrote = 0;
+
+ sel_io_reader io_reader = sel_io_readers[sel->access_method];
+
+ while (count && pos < sel->area_length) {
+ count--;
+ *(buf++) = io_reader(sel, pos++);
+ wrote++;
+ }
+
+ return wrote;
+}
+
+static ssize_t dmi_sel_raw_read_phys32(struct dmi_sysfs_entry *entry,
+ const struct dmi_system_event_log *sel,
+ char *buf, loff_t pos, size_t count)
+{
+ u8 __iomem *mapped;
+ ssize_t wrote = 0;
+
+ mapped = ioremap(sel->access_method_address, sel->area_length);
+ if (!mapped)
+ return -EIO;
+
+ while (count && pos < sel->area_length) {
+ count--;
+ *(buf++) = readb(mapped + pos++);
+ wrote++;
+ }
+
+ iounmap(mapped);
+ return wrote;
+}
+
+static ssize_t dmi_sel_raw_read_helper(struct dmi_sysfs_entry *entry,
+ const struct dmi_header *dh,
+ void *_state)
+{
+ struct dmi_read_state *state = _state;
+ const struct dmi_system_event_log *sel = to_sel(dh);
+
+ if (sizeof(*sel) > dmi_entry_length(dh))
+ return -EIO;
+
+ switch (sel->access_method) {
+ case DMI_SEL_ACCESS_METHOD_IO8:
+ case DMI_SEL_ACCESS_METHOD_IO2x8:
+ case DMI_SEL_ACCESS_METHOD_IO16:
+ return dmi_sel_raw_read_io(entry, sel, state->buf,
+ state->pos, state->count);
+ case DMI_SEL_ACCESS_METHOD_PHYS32:
+ return dmi_sel_raw_read_phys32(entry, sel, state->buf,
+ state->pos, state->count);
+ case DMI_SEL_ACCESS_METHOD_GPNV:
+ pr_info("dmi-sysfs: GPNV support missing.\n");
+ return -EIO;
+ default:
+ pr_info("dmi-sysfs: Unknown access method %02x\n",
+ sel->access_method);
+ return -EIO;
+ }
+}
+
+static ssize_t dmi_sel_raw_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t pos, size_t count)
+{
+ struct dmi_sysfs_entry *entry = to_entry(kobj->parent);
+ struct dmi_read_state state = {
+ .buf = buf,
+ .pos = pos,
+ .count = count,
+ };
+
+ return find_dmi_entry(entry, dmi_sel_raw_read_helper, &state);
+}
+
+static struct bin_attribute dmi_sel_raw_attr = {
+ .attr = {.name = "raw_event_log", .mode = 0400},
+ .read = dmi_sel_raw_read,
+};
+
static int dmi_system_event_log(struct dmi_sysfs_entry *entry)
{
int ret;
@@ -318,6 +452,15 @@ static int dmi_system_event_log(struct dmi_sysfs_entry *entry)
"system_event_log");
if (ret)
goto out_free;
+
+ ret = sysfs_create_bin_file(entry->child, &dmi_sel_raw_attr);
+ if (ret)
+ goto out_del;
+
+ return 0;
+
+out_del:
+ kobject_del(entry->child);
out_free:
kfree(entry->child);
return ret;

2011-02-17 21:28:48

by Mike Waychison

[permalink] [raw]
Subject: [PATCH v1 5/5] firmware: Add documentation for /sys/firmware/dmi

Document the new ABI added by the dmi-sysfs module.

Signed-off-by: Mike Waychison <[email protected]>
---
Documentation/ABI/testing/sysfs-firmware-dmi | 101 ++++++++++++++++++++++++++
1 files changed, 101 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-firmware-dmi

diff --git a/Documentation/ABI/testing/sysfs-firmware-dmi b/Documentation/ABI/testing/sysfs-firmware-dmi
new file mode 100644
index 0000000..c6526b0
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-firmware-dmi
@@ -0,0 +1,101 @@
+What: /sys/firmware/dmi/
+Date: February 2011
+Contact: Mike Waychison <[email protected]>
+Description:
+ Many machine's firmware (x86 and ia64) export DMI /
+ SMBIOS tables to the operating system. Getting at this
+ information is often valuable to userland, especially in
+ cases where there are OEM extensions used.
+
+ The kernel itself does not rely on the majority of the
+ information in these tables being correct. It equally
+ cannot ensure that the data as exported to userland is
+ without error either.
+
+ DMI is structured as a large table of entries, where
+ each entry has a common header indicating the type and
+ length of the entry, as well as 'handle' that is
+ supposed to be unique amongst all entries.
+
+ Some entries are required by the specification, but many
+ others are optional. In general though, user's should
+ never expect to find a specific entry type on their
+ system unless they know for certain what their firmware
+ is doing. Machine to machine will vary.
+
+ Multiple entries of the same type are allowed. In order
+ to handle these duplicate entry types, each entry is
+ assigned by the operating system an 'instance', which is
+ derived from an entry type's ordinal position. That is
+ to say, if there are 'N' multiple entries with the same type
+ 'T' in the DMI tables (adjacent or spread apart, it
+ doesn't matter), they will be represented in sysfs as
+ entries "T-0" through "T-(N-1)":
+
+ Example entry directories:
+
+ /sys/firmware/dmi/entries/17-0
+ /sys/firmware/dmi/entries/17-1
+ /sys/firmware/dmi/entries/17-2
+ /sys/firmware/dmi/entries/17-3
+ ...
+
+ Instance numbers are used in lieu of the firmware
+ assigned entry handles as there is kernel itself makes
+ no guarantees that handles as exported are unique, and
+ there are likely firmware images that get this wrong in
+ the wild.
+
+ Each DMI entry in sysfs has the common header values
+ exported as attributes:
+
+ handle : The 16bit 'handle' that is assigned to this
+ entry by the firmware. This handle may be
+ referred to by other entries.
+ length : The length of the entry, as presented in the
+ entry itself. Note that this is _not the
+ total count of bytes associated with the
+ entry_. This value represents the length of
+ the "formatted" portion of the entry. This
+ "formatted" region is sometimes followed by
+ the "unformatted" region composed of nul
+ terminated strings, with termination signalled
+ by a two nul characters in series.
+ raw : The raw bytes of the entry. This includes the
+ "formatted" portion of the entry, the
+ "unformatted" strings portion of the entry,
+ and the two terminating nul characters.
+
+ === Entry Specialization ===
+
+ Some entry types may have other information available in
+ sysfs.
+
+ --- Type 15 - System Event Log ---
+
+ This entry allows the firmware to export a log of
+ events the system has taken. This information is
+ typically backed by nvram, but the implementation
+ details are abstracted by this table. This entries data
+ is exported in the directory:
+
+ /sys/firmware/dmi/entries/15-0/system_event_log
+
+ and has the following attributes (documented in the
+ SMBIOS / DMI specification under "System Event Log (Type 15)":
+
+ area_length
+ header_start_offset
+ data_start_offset
+ access_method
+ status
+ change_token
+ access_method_address
+ header_format
+ per_log_type_descriptor_length
+ type_descriptors_supported_count
+
+ As well, the kernel exports the binary attribute:
+
+ raw_event_log : The raw binary bits of the event log
+ as described by the DMI entry.

2011-02-17 21:43:13

by Tim Hockin

[permalink] [raw]
Subject: Re: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

On Thu, Feb 17, 2011 at 1:28 PM, Mike Waychison <[email protected]> wrote:
> Introduce a new module "dmi-sysfs" that exports the broken out entries
> of the DMI table through sysfs.
>
> Entries are enumerated via dmi_walk() on module load, and are populated
> as kobjects rooted at /sys/firmware/dmi/entries.
>
> Entries are named "<type>-<instance>", where:
> ? <type> ? ? ? : is the type of the entry, and
> ? <instance> ? : is the ordinal count within the DMI table of that
> ? ? ? ? ? ? ? ? ?entry type. ?This instance is used in lieu the DMI
> ? ? ? ? ? ? ? ? ?entry's handle as no assurances are made by the kernel
> ? ? ? ? ? ? ? ? ?that handles are unique.
>
> All entries export the following attributes:
> ? length ? ? ? : The length of the formatted portion of the entry
> ? handle ? ? ? : The handle given to this entry by the firmware
> ? raw ? ? ? ? ?: The raw bytes of the entire entry, including the
> ? ? ? ? ? ? ? ? ?formatted portion, the unformatted (strings) portion,
> ? ? ? ? ? ? ? ? ?and the two terminating nul characters.

Is it worth dropping another file here for type (and maybe one for
ordinal)? I know they are in the dir name, but something nags at me
that they would be useful here.

>
> Entries in dmi-sysfs are kobject backed members called "struct
> dmi_sysfs_entry" and belong to dmi_kset. ?They are threaded through
> entry_list (protected by entry_list_lock) so that we can find them at
> cleanup time.
>
> Signed-off-by: Mike Waychison <[email protected]>
> ---
> ?drivers/firmware/Kconfig ? ? | ? 11 +
> ?drivers/firmware/Makefile ? ?| ? ?1
> ?drivers/firmware/dmi-sysfs.c | ?362 ++++++++++++++++++++++++++++++++++++++++++
> ?3 files changed, 374 insertions(+), 0 deletions(-)
> ?create mode 100644 drivers/firmware/dmi-sysfs.c
>
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index e710424..959175d 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -113,6 +113,17 @@ config DMIID
> ? ? ? ? ?information from userspace through /sys/class/dmi/id/ or if you want
> ? ? ? ? ?DMI-based module auto-loading.
>
> +config DMI_SYSFS
> + ? ? ? tristate "DMI table support in sysfs"
> + ? ? ? depends on SYSFS && DMI
> + ? ? ? default X86
> + ? ? ? help
> + ? ? ? ? Say Y or M here to enable the exporting of the raw DMI table
> + ? ? ? ? data via sysfs. ?This is useful for consuming the data without
> + ? ? ? ? requiring any access to /dev/mem at all. ?Tables are found
> + ? ? ? ? under /sys/firmware/dmi when this option is enabled and
> + ? ? ? ? loaded.
> +
> ?config ISCSI_IBFT_FIND
> ? ? ? ?bool "iSCSI Boot Firmware Table Attributes"
> ? ? ? ?depends on X86
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
> index 1c3c173..20c17fc 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -2,6 +2,7 @@
> ?# Makefile for the linux kernel.
> ?#
> ?obj-$(CONFIG_DMI) ? ? ? ? ? ? ?+= dmi_scan.o
> +obj-$(CONFIG_DMI_SYSFS) ? ? ? ? ? ? ? ?+= dmi-sysfs.o
> ?obj-$(CONFIG_EDD) ? ? ? ? ? ? ?+= edd.o
> ?obj-$(CONFIG_EFI_VARS) ? ? ? ? += efivars.o
> ?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 0000000..adcd604
> --- /dev/null
> +++ b/drivers/firmware/dmi-sysfs.c
> @@ -0,0 +1,362 @@
> +/*
> + * dmi-sysfs.c
> + *
> + * This module exports the DMI tables read-only to userspace through the
> + * sysfs file system.
> + *
> + * Data is currently found below
> + * ? ?/sys/firmware/dmi/...
> + *
> + * DMI attributes are presented in attribute files with names
> + * formatted using %d-%d, so that the first integer indicates the
> + * structure type (0-255), and the second field is the instance of that
> + * entry.
> + *
> + * Copyright 2010 Google, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/kobject.h>
> +#include <linux/dmi.h>
> +#include <linux/capability.h>
> +#include <linux/slab.h>
> +#include <linux/list.h>
> +#include <linux/io.h>
> +
> +#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? the top entry type is only 8 bits */
> +
> +struct dmi_sysfs_entry {
> + ? ? ? struct dmi_header dh;
> + ? ? ? struct kobject kobj;
> + ? ? ? int instance;
> + ? ? ? struct list_head list;
> +};
> +
> +/*
> + * Global list of dmi_sysfs_entry. ?Even though this should only be
> + * manipulated at setup and teardown, the lazy nature of the kobject
> + * system means we get lazy removes.
> + */
> +static LIST_HEAD(entry_list);
> +static DEFINE_SPINLOCK(entry_list_lock);
> +
> +/* dmi_sysfs_attribute - Top level attribute. used by all entries. */
> +struct dmi_sysfs_attribute {
> + ? ? ? struct attribute attr;
> + ? ? ? ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
> +};
> +
> +#define DMI_SYSFS_ATTR(_entry, _name) \
> +struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
> + ? ? ? .attr = {.name = __stringify(_name), .mode = 0400}, \
> + ? ? ? .show = dmi_sysfs_##_entry##_##_name, \
> +}
> +
> +/*
> + * dmi_sysfs_mapped_attribute - Attribute where we require the entry be
> + * mapped in. ?Use in conjunction with dmi_sysfs_specialize_attr_ops.
> + */
> +struct dmi_sysfs_mapped_attribute {
> + ? ? ? struct attribute attr;
> + ? ? ? ssize_t (*show)(struct dmi_sysfs_entry *entry,
> + ? ? ? ? ? ? ? ? ? ? ? const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? char *buf);
> +};
> +
> +#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
> +struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
> + ? ? ? .attr = {.name = __stringify(_name), .mode = 0400}, \
> + ? ? ? .show = dmi_sysfs_##_entry##_##_name, \
> +}
> +
> +/*************************************************
> + * Generic DMI entry support.
> + *************************************************/
> +
> +static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
> +{
> + ? ? ? return container_of(kobj, struct dmi_sysfs_entry, kobj);
> +}
> +
> +static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
> +{
> + ? ? ? return container_of(attr, struct dmi_sysfs_attribute, attr);
> +}
> +
> +static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?struct attribute *_attr, char *buf)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry = to_entry(kobj);
> + ? ? ? struct dmi_sysfs_attribute *attr = to_attr(_attr);
> +
> + ? ? ? /* DMI stuff is only ever admin visible */
> + ? ? ? if (!capable(CAP_SYS_ADMIN))
> + ? ? ? ? ? ? ? return -EACCES;
> +
> + ? ? ? return attr->show(entry, buf);
> +}
> +
> +static const struct sysfs_ops dmi_sysfs_attr_ops = {
> + ? ? ? .show = dmi_sysfs_attr_show,
> +};
> +
> +typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? const struct dmi_header *dh, void *);
> +
> +struct find_dmi_data {
> + ? ? ? struct dmi_sysfs_entry ?*entry;
> + ? ? ? dmi_callback ? ? ? ? ? ?callback;
> + ? ? ? void ? ? ? ? ? ? ? ? ? ?*private;
> + ? ? ? int ? ? ? ? ? ? ? ? ? ? instance_countdown;
> + ? ? ? ssize_t ? ? ? ? ? ? ? ? ret;
> +};
> +
> +static void find_dmi_entry_helper(const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? void *_data)
> +{
> + ? ? ? struct find_dmi_data *data = _data;
> + ? ? ? struct dmi_sysfs_entry *entry = data->entry;
> +
> + ? ? ? /* Is this the entry we want? */
> + ? ? ? if (dh->type != entry->dh.type)
> + ? ? ? ? ? ? ? return;
> +
> + ? ? ? if (data->instance_countdown != 0) {
> + ? ? ? ? ? ? ? /* try the next instance? */
> + ? ? ? ? ? ? ? data->instance_countdown--;
> + ? ? ? ? ? ? ? return;
> + ? ? ? }
> +
> + ? ? ? /* Found the entry */
> + ? ? ? data->ret = data->callback(entry, dh, data->private);
> +}
> +
> +/* State for passing the read parameters through dmi_find_entry() */
> +struct dmi_read_state {
> + ? ? ? char *buf;
> + ? ? ? loff_t pos;
> + ? ? ? size_t count;
> +};
> +
> +static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? dmi_callback callback, void *private)
> +{
> + ? ? ? struct find_dmi_data data = {
> + ? ? ? ? ? ? ? .entry = entry,
> + ? ? ? ? ? ? ? .callback = callback,
> + ? ? ? ? ? ? ? .private = private,
> + ? ? ? ? ? ? ? .instance_countdown = entry->instance,
> + ? ? ? ? ? ? ? .ret = -EIO, ?/* To signal the entry disappeared */
> + ? ? ? };
> + ? ? ? int ret;
> +
> + ? ? ? ret = dmi_walk(find_dmi_entry_helper, &data);
> + ? ? ? /* This shouldn't happen, but just in case. */
> + ? ? ? if (ret)
> + ? ? ? ? ? ? ? return -EINVAL;
> + ? ? ? return data.ret;
> +}
> +
> +/*
> + * Calculate and return the byte length of the dmi entry identified by
> + * dh. ?This includes both the formatted portion as well as the
> + * unformatted string space, including the two trailing nul characters.
> + */
> +static size_t dmi_entry_length(const struct dmi_header *dh)
> +{
> + ? ? ? const char *p = (const char *)dh;
> +
> + ? ? ? p += dh->length;
> +
> + ? ? ? while (p[0] || p[1])
> + ? ? ? ? ? ? ? p++;
> +
> + ? ? ? return 2 + p - (const char *)dh;
> +}
> +
> +/*************************************************
> + * Generic DMI entry support.
> + *************************************************/
> +
> +static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
> +{
> + ? ? ? return sprintf(buf, "%d\n", entry->dh.length);
> +}
> +
> +static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
> +{
> + ? ? ? return sprintf(buf, "%d\n", entry->dh.handle);
> +}
> +
> +static DMI_SYSFS_ATTR(entry, length);
> +static DMI_SYSFS_ATTR(entry, handle);
> +
> +static struct attribute *dmi_sysfs_entry_attrs[] = {
> + ? ? ? &dmi_sysfs_attr_entry_length.attr,
> + ? ? ? &dmi_sysfs_attr_entry_handle.attr,
> + ? ? ? NULL,
> +};
> +
> +static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?void *_state)
> +{
> + ? ? ? struct dmi_read_state *state = _state;
> + ? ? ? size_t entry_length;
> +
> + ? ? ? entry_length = dmi_entry_length(dh);
> +
> + ? ? ? return memory_read_from_buffer(state->buf, state->count,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?&state->pos, dh, entry_length);
> +}
> +
> +static ssize_t dmi_entry_raw_read(struct file *filp,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct kobject *kobj,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct bin_attribute *bin_attr,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? char *buf, loff_t pos, size_t count)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry = to_entry(kobj);
> + ? ? ? struct dmi_read_state state = {
> + ? ? ? ? ? ? ? .buf = buf,
> + ? ? ? ? ? ? ? .pos = pos,
> + ? ? ? ? ? ? ? .count = count,
> + ? ? ? };
> +
> + ? ? ? return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
> +}
> +
> +static const struct bin_attribute dmi_entry_raw_attr = {
> + ? ? ? .attr = {.name = "raw", .mode = 0400},
> + ? ? ? .read = dmi_entry_raw_read,
> +};
> +
> +static void dmi_sysfs_entry_release(struct kobject *kobj)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry = to_entry(kobj);
> + ? ? ? sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr);
> + ? ? ? spin_lock(&entry_list_lock);
> + ? ? ? list_del(&entry->list);
> + ? ? ? spin_unlock(&entry_list_lock);
> + ? ? ? kfree(entry);
> +}
> +
> +static struct kobj_type dmi_sysfs_entry_ktype = {
> + ? ? ? .release = dmi_sysfs_entry_release,
> + ? ? ? .sysfs_ops = &dmi_sysfs_attr_ops,
> + ? ? ? .default_attrs = dmi_sysfs_entry_attrs,
> +};
> +
> +static struct kobject *dmi_kobj;
> +static struct kset *dmi_kset;
> +
> +/* Global count of all instances seen. ?Only for setup */
> +static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
> +
> +static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?void *_ret)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry;
> + ? ? ? int *ret = _ret;
> +
> + ? ? ? /* If a previous entry saw an error, short circuit */
> + ? ? ? if (*ret)
> + ? ? ? ? ? ? ? return;
> +
> + ? ? ? /* Allocate and register a new entry into the entries set */
> + ? ? ? entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> + ? ? ? if (!entry) {
> + ? ? ? ? ? ? ? *ret = -ENOMEM;
> + ? ? ? ? ? ? ? return;
> + ? ? ? }
> +
> + ? ? ? /* Set the key */
> + ? ? ? entry->dh = *dh;
> + ? ? ? entry->instance = instance_counts[dh->type]++;
> +
> + ? ? ? entry->kobj.kset = dmi_kset;
> + ? ? ? *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "%d-%d", dh->type, entry->instance);
> +
> + ? ? ? if (*ret) {
> + ? ? ? ? ? ? ? kfree(entry);
> + ? ? ? ? ? ? ? return;
> + ? ? ? }
> +
> + ? ? ? /* Thread on the global list for cleanup */
> + ? ? ? spin_lock(&entry_list_lock);
> + ? ? ? list_add_tail(&entry->list, &entry_list);
> + ? ? ? spin_unlock(&entry_list_lock);
> +
> + ? ? ? /* Create the raw binary file to access the entry */
> + ? ? ? *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
> + ? ? ? if (*ret)
> + ? ? ? ? ? ? ? goto out_err;
> +
> + ? ? ? return;
> +out_err:
> + ? ? ? kobject_put(&entry->kobj);
> + ? ? ? return;
> +}
> +
> +static void cleanup_entry_list(void)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry, *next;
> +
> + ? ? ? /* No locks, we are on our way out */
> + ? ? ? list_for_each_entry_safe(entry, next, &entry_list, list) {
> + ? ? ? ? ? ? ? kobject_put(&entry->kobj);
> + ? ? ? }
> +}
> +
> +static int __init dmi_sysfs_init(void)
> +{
> + ? ? ? int error = -ENOMEM;
> + ? ? ? int val;
> +
> + ? ? ? /* Set up our directory */
> + ? ? ? dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
> + ? ? ? if (!dmi_kobj)
> + ? ? ? ? ? ? ? goto err;
> +
> + ? ? ? dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
> + ? ? ? if (!dmi_kset)
> + ? ? ? ? ? ? ? goto err;
> +
> + ? ? ? val = 0;
> + ? ? ? error = dmi_walk(dmi_sysfs_register_handle, &val);
> + ? ? ? if (error)
> + ? ? ? ? ? ? ? goto err;
> + ? ? ? if (val) {
> + ? ? ? ? ? ? ? error = val;
> + ? ? ? ? ? ? ? goto err;
> + ? ? ? }
> +
> + ? ? ? pr_info("dmi-sysfs: loaded.\n");
> +
> + ? ? ? return 0;
> +err:
> + ? ? ? cleanup_entry_list();
> + ? ? ? kset_unregister(dmi_kset);
> + ? ? ? kobject_put(dmi_kobj);
> + ? ? ? return error;
> +}
> +
> +/* clean up everything. */
> +static void __exit dmi_sysfs_exit(void)
> +{
> + ? ? ? pr_info("dmi-sysfs: unloading.\n");
> + ? ? ? cleanup_entry_list();
> + ? ? ? kset_unregister(dmi_kset);
> + ? ? ? kobject_put(dmi_kobj);
> +}
> +
> +module_init(dmi_sysfs_init);
> +module_exit(dmi_sysfs_exit);
> +
> +MODULE_AUTHOR("Google, Inc.");
> +MODULE_DESCRIPTION("DMI sysfs support");
> +MODULE_LICENSE("GPL");
>
>

2011-02-17 21:50:41

by Tim Hockin

[permalink] [raw]
Subject: Re: [PATCH v1 5/5] firmware: Add documentation for /sys/firmware/dmi

On Thu, Feb 17, 2011 at 1:28 PM, Mike Waychison <[email protected]> wrote:
> Document the new ABI added by the dmi-sysfs module.
>
> Signed-off-by: Mike Waychison <[email protected]>
> ---
> ?Documentation/ABI/testing/sysfs-firmware-dmi | ?101 ++++++++++++++++++++++++++
> ?1 files changed, 101 insertions(+), 0 deletions(-)
> ?create mode 100644 Documentation/ABI/testing/sysfs-firmware-dmi
>
> diff --git a/Documentation/ABI/testing/sysfs-firmware-dmi b/Documentation/ABI/testing/sysfs-firmware-dmi
> new file mode 100644
> index 0000000..c6526b0
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-firmware-dmi
> @@ -0,0 +1,101 @@
> +What: ? ? ? ? ?/sys/firmware/dmi/
> +Date: ? ? ? ? ?February 2011
> +Contact: ? ? ? Mike Waychison <[email protected]>
> +Description:
> + ? ? ? ? ? ? ? Many machine's firmware (x86 and ia64) export DMI /

machines'

> + ? ? ? ? ? ? ? SMBIOS tables to the operating system. ?Getting at this
> + ? ? ? ? ? ? ? information is often valuable to userland, especially in
> + ? ? ? ? ? ? ? cases where there are OEM extensions used.
> +
> + ? ? ? ? ? ? ? The kernel itself does not rely on the majority of the
> + ? ? ? ? ? ? ? information in these tables being correct. ?It equally
> + ? ? ? ? ? ? ? cannot ensure that the data as exported to userland is
> + ? ? ? ? ? ? ? without error either.
> +
> + ? ? ? ? ? ? ? DMI is structured as a large table of entries, where
> + ? ? ? ? ? ? ? each entry has a common header indicating the type and
> + ? ? ? ? ? ? ? length of the entry, as well as 'handle' that is
> + ? ? ? ? ? ? ? supposed to be unique amongst all entries.
> +
> + ? ? ? ? ? ? ? Some entries are required by the specification, but many
> + ? ? ? ? ? ? ? others are optional. ?In general though, user's should

users

> + ? ? ? ? ? ? ? never expect to find a specific entry type on their
> + ? ? ? ? ? ? ? system unless they know for certain what their firmware
> + ? ? ? ? ? ? ? is doing. ?Machine to machine will vary.
> +
> + ? ? ? ? ? ? ? Multiple entries of the same type are allowed. ?In order
> + ? ? ? ? ? ? ? to handle these duplicate entry types, each entry is
> + ? ? ? ? ? ? ? assigned by the operating system an 'instance', which is
> + ? ? ? ? ? ? ? derived from an entry type's ordinal position. ?That is
> + ? ? ? ? ? ? ? to say, if there are 'N' multiple entries with the same type
> + ? ? ? ? ? ? ? 'T' in the DMI tables (adjacent or spread apart, it
> + ? ? ? ? ? ? ? doesn't matter), they will be represented in sysfs as
> + ? ? ? ? ? ? ? entries "T-0" through "T-(N-1)":
> +
> + ? ? ? ? ? ? ? Example entry directories:
> +
> + ? ? ? ? ? ? ? ? ? ? ? /sys/firmware/dmi/entries/17-0
> + ? ? ? ? ? ? ? ? ? ? ? /sys/firmware/dmi/entries/17-1
> + ? ? ? ? ? ? ? ? ? ? ? /sys/firmware/dmi/entries/17-2
> + ? ? ? ? ? ? ? ? ? ? ? /sys/firmware/dmi/entries/17-3
> + ? ? ? ? ? ? ? ? ? ? ? ...
> +
> + ? ? ? ? ? ? ? Instance numbers are used in lieu of the firmware
> + ? ? ? ? ? ? ? assigned entry handles as there is kernel itself makes

s/there is/the/

> + ? ? ? ? ? ? ? no guarantees that handles as exported are unique, and
> + ? ? ? ? ? ? ? there are likely firmware images that get this wrong in
> + ? ? ? ? ? ? ? the wild.
> +
> + ? ? ? ? ? ? ? Each DMI entry in sysfs has the common header values
> + ? ? ? ? ? ? ? exported as attributes:
> +
> + ? ? ? ? ? ? ? handle ?: The 16bit 'handle' that is assigned to this
> + ? ? ? ? ? ? ? ? ? ? ? ? entry by the firmware. ?This handle may be
> + ? ? ? ? ? ? ? ? ? ? ? ? referred to by other entries.
> + ? ? ? ? ? ? ? length ?: The length of the entry, as presented in the
> + ? ? ? ? ? ? ? ? ? ? ? ? entry itself. ?Note that this is _not the
> + ? ? ? ? ? ? ? ? ? ? ? ? total count of bytes associated with the
> + ? ? ? ? ? ? ? ? ? ? ? ? entry_. ?This value represents the length of
> + ? ? ? ? ? ? ? ? ? ? ? ? the "formatted" portion of the entry. ?This
> + ? ? ? ? ? ? ? ? ? ? ? ? "formatted" region is sometimes followed by
> + ? ? ? ? ? ? ? ? ? ? ? ? the "unformatted" region composed of nul
> + ? ? ? ? ? ? ? ? ? ? ? ? terminated strings, with termination signalled
> + ? ? ? ? ? ? ? ? ? ? ? ? by a two nul characters in series.
> + ? ? ? ? ? ? ? raw ? ? : The raw bytes of the entry. This includes the
> + ? ? ? ? ? ? ? ? ? ? ? ? "formatted" portion of the entry, the
> + ? ? ? ? ? ? ? ? ? ? ? ? "unformatted" strings portion of the entry,
> + ? ? ? ? ? ? ? ? ? ? ? ? and the two terminating nul characters.
> +
> + ? ? ? ? ? ? ? === Entry Specialization ===
> +
> + ? ? ? ? ? ? ? Some entry types may have other information available in
> + ? ? ? ? ? ? ? sysfs.
> +
> + ? ? ? ? ? ? ? --- Type 15 - System Event Log ---
> +
> + ? ? ? ? ? ? ? This entry allows the firmware to export a log of
> + ? ? ? ? ? ? ? events the system has taken. ?This information is
> + ? ? ? ? ? ? ? typically backed by nvram, but the implementation
> + ? ? ? ? ? ? ? details are abstracted by this table. ?This entries data
> + ? ? ? ? ? ? ? is exported in the directory:
> +
> + ? ? ? ? ? ? ? /sys/firmware/dmi/entries/15-0/system_event_log
> +
> + ? ? ? ? ? ? ? and has the following attributes (documented in the
> + ? ? ? ? ? ? ? SMBIOS / DMI specification under "System Event Log (Type 15)":
> +
> + ? ? ? ? ? ? ? area_length
> + ? ? ? ? ? ? ? header_start_offset
> + ? ? ? ? ? ? ? data_start_offset
> + ? ? ? ? ? ? ? access_method
> + ? ? ? ? ? ? ? status
> + ? ? ? ? ? ? ? change_token
> + ? ? ? ? ? ? ? access_method_address
> + ? ? ? ? ? ? ? header_format
> + ? ? ? ? ? ? ? per_log_type_descriptor_length
> + ? ? ? ? ? ? ? type_descriptors_supported_count
> +
> + ? ? ? ? ? ? ? As well, the kernel exports the binary attribute:
> +
> + ? ? ? ? ? ? ? raw_event_log ? : The raw binary bits of the event log
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? as described by the DMI entry.
>
>

2011-02-17 21:58:31

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

Overall, this looks great, just a few minor comments below:

On Thu, Feb 17, 2011 at 01:28:05PM -0800, Mike Waychison wrote:
> +config DMI_SYSFS
> + tristate "DMI table support in sysfs"
> + depends on SYSFS && DMI
> + default X86

Huh? Default should be 'N' for any new feature, unless it keeps your
machine from booting.

I think you want this option to depend on X86 though, right?

> + help
> + Say Y or M here to enable the exporting of the raw DMI table
> + data via sysfs. This is useful for consuming the data without
> + requiring any access to /dev/mem at all. Tables are found
> + under /sys/firmware/dmi when this option is enabled and
> + loaded.

I just realized (due to other work I'm doing on a laptop) that we have a
bunch of entries today in /sys/class/dmi/id which is a pointer to the
dmi "device".

Now I think this really is different (these are the raw DMI tables), but
this doesn't have anything to do with that code, right?

> + *
> + * Copyright 2010 Google, Inc.

It's 2011 :)

> +static int __init dmi_sysfs_init(void)
> +{
> + int error = -ENOMEM;
> + int val;
> +
> + /* Set up our directory */
> + dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
> + if (!dmi_kobj)
> + goto err;
> +
> + dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
> + if (!dmi_kset)
> + goto err;
> +
> + val = 0;
> + error = dmi_walk(dmi_sysfs_register_handle, &val);
> + if (error)
> + goto err;
> + if (val) {
> + error = val;
> + goto err;
> + }
> +
> + pr_info("dmi-sysfs: loaded.\n");

Is this really needed? pr_dbg() instead please?

> +
> + return 0;
> +err:
> + cleanup_entry_list();
> + kset_unregister(dmi_kset);
> + kobject_put(dmi_kobj);
> + return error;
> +}
> +
> +/* clean up everything. */
> +static void __exit dmi_sysfs_exit(void)
> +{
> + pr_info("dmi-sysfs: unloading.\n");
> + cleanup_entry_list();
> + kset_unregister(dmi_kset);
> + kobject_put(dmi_kobj);
> +}
> +
> +module_init(dmi_sysfs_init);
> +module_exit(dmi_sysfs_exit);
> +
> +MODULE_AUTHOR("Google, Inc.");

You don't want your name here?

thanks,

greg k-h

2011-02-22 23:31:12

by Mike Waychison

[permalink] [raw]
Subject: Re: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

On 02/17/11 13:56, Greg KH wrote:
> Overall, this looks great, just a few minor comments below:
>
> On Thu, Feb 17, 2011 at 01:28:05PM -0800, Mike Waychison wrote:
>> +config DMI_SYSFS
>> + tristate "DMI table support in sysfs"
>> + depends on SYSFS&& DMI
>> + default X86
>
> Huh? Default should be 'N' for any new feature, unless it keeps your
> machine from booting.
>
> I think you want this option to depend on X86 though, right?

Looks to be supported on ia64 as well, though I don't have any hardware
to test this code on for that arch. Tony: I think this DMI exporting
code should just work on ia64 as all it is using is dmi_walk() and
parsing the entries as returned as dmi_headers in the callback. Does
this sound sane to you?

>
>> + help
>> + Say Y or M here to enable the exporting of the raw DMI table
>> + data via sysfs. This is useful for consuming the data without
>> + requiring any access to /dev/mem at all. Tables are found
>> + under /sys/firmware/dmi when this option is enabled and
>> + loaded.
>
> I just realized (due to other work I'm doing on a laptop) that we have a
> bunch of entries today in /sys/class/dmi/id which is a pointer to the
> dmi "device".
>
> Now I think this really is different (these are the raw DMI tables), but
> this doesn't have anything to do with that code, right?

Ya, it is similar, though the primary goal I have is to export these raw
bytes. The data comes from the same place, however the dmi-id code is
just exporting the in-kernel copies of the strings parsed at boot.

These could probably be better exported under
/sys/firmware/dmi/entries/[0123]-*/ files imo.

Mike Waychison

2011-02-22 23:49:56

by Rob Lippert

[permalink] [raw]
Subject: Re: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

On Thu, Feb 17, 2011 at 1:28 PM, Mike Waychison <[email protected]> wrote:
> Introduce a new module "dmi-sysfs" that exports the broken out entries
> of the DMI table through sysfs.
>
> Entries are enumerated via dmi_walk() on module load, and are populated
> as kobjects rooted at /sys/firmware/dmi/entries.
>
> Entries are named "<type>-<instance>", where:
> ? <type> ? ? ? : is the type of the entry, and
> ? <instance> ? : is the ordinal count within the DMI table of that
> ? ? ? ? ? ? ? ? ?entry type. ?This instance is used in lieu the DMI
> ? ? ? ? ? ? ? ? ?entry's handle as no assurances are made by the kernel
> ? ? ? ? ? ? ? ? ?that handles are unique.
>
> All entries export the following attributes:

Can you add an entry here for index/ordinal in the set of all tables?
This way the user can walk the tables in the original order exported
by the BIOS. Some (buggy) BIOSes don't properly set up links between
things like MEM_ARRAY and MEM_DEVICE tables but depend on the position
of the table relative to other tables.

> ? length ? ? ? : The length of the formatted portion of the entry
> ? handle ? ? ? : The handle given to this entry by the firmware
> ? raw ? ? ? ? ?: The raw bytes of the entire entry, including the
> ? ? ? ? ? ? ? ? ?formatted portion, the unformatted (strings) portion,
> ? ? ? ? ? ? ? ? ?and the two terminating nul characters.
>
> Entries in dmi-sysfs are kobject backed members called "struct
> dmi_sysfs_entry" and belong to dmi_kset. ?They are threaded through
> entry_list (protected by entry_list_lock) so that we can find them at
> cleanup time.
>
> Signed-off-by: Mike Waychison <[email protected]>
> ---
> ?drivers/firmware/Kconfig ? ? | ? 11 +
> ?drivers/firmware/Makefile ? ?| ? ?1
> ?drivers/firmware/dmi-sysfs.c | ?362 ++++++++++++++++++++++++++++++++++++++++++
> ?3 files changed, 374 insertions(+), 0 deletions(-)
> ?create mode 100644 drivers/firmware/dmi-sysfs.c
>
> diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
> index e710424..959175d 100644
> --- a/drivers/firmware/Kconfig
> +++ b/drivers/firmware/Kconfig
> @@ -113,6 +113,17 @@ config DMIID
> ? ? ? ? ?information from userspace through /sys/class/dmi/id/ or if you want
> ? ? ? ? ?DMI-based module auto-loading.
>
> +config DMI_SYSFS
> + ? ? ? tristate "DMI table support in sysfs"
> + ? ? ? depends on SYSFS && DMI
> + ? ? ? default X86
> + ? ? ? help
> + ? ? ? ? Say Y or M here to enable the exporting of the raw DMI table
> + ? ? ? ? data via sysfs. ?This is useful for consuming the data without
> + ? ? ? ? requiring any access to /dev/mem at all. ?Tables are found
> + ? ? ? ? under /sys/firmware/dmi when this option is enabled and
> + ? ? ? ? loaded.
> +
> ?config ISCSI_IBFT_FIND
> ? ? ? ?bool "iSCSI Boot Firmware Table Attributes"
> ? ? ? ?depends on X86
> diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
> index 1c3c173..20c17fc 100644
> --- a/drivers/firmware/Makefile
> +++ b/drivers/firmware/Makefile
> @@ -2,6 +2,7 @@
> ?# Makefile for the linux kernel.
> ?#
> ?obj-$(CONFIG_DMI) ? ? ? ? ? ? ?+= dmi_scan.o
> +obj-$(CONFIG_DMI_SYSFS) ? ? ? ? ? ? ? ?+= dmi-sysfs.o
> ?obj-$(CONFIG_EDD) ? ? ? ? ? ? ?+= edd.o
> ?obj-$(CONFIG_EFI_VARS) ? ? ? ? += efivars.o
> ?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 0000000..adcd604
> --- /dev/null
> +++ b/drivers/firmware/dmi-sysfs.c
> @@ -0,0 +1,362 @@
> +/*
> + * dmi-sysfs.c
> + *
> + * This module exports the DMI tables read-only to userspace through the
> + * sysfs file system.
> + *
> + * Data is currently found below
> + * ? ?/sys/firmware/dmi/...
> + *
> + * DMI attributes are presented in attribute files with names
> + * formatted using %d-%d, so that the first integer indicates the
> + * structure type (0-255), and the second field is the instance of that
> + * entry.
> + *
> + * Copyright 2010 Google, Inc.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/kobject.h>
> +#include <linux/dmi.h>
> +#include <linux/capability.h>
> +#include <linux/slab.h>
> +#include <linux/list.h>
> +#include <linux/io.h>
> +
> +#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? the top entry type is only 8 bits */
> +
> +struct dmi_sysfs_entry {
> + ? ? ? struct dmi_header dh;
> + ? ? ? struct kobject kobj;
> + ? ? ? int instance;
> + ? ? ? struct list_head list;
> +};
> +
> +/*
> + * Global list of dmi_sysfs_entry. ?Even though this should only be
> + * manipulated at setup and teardown, the lazy nature of the kobject
> + * system means we get lazy removes.
> + */
> +static LIST_HEAD(entry_list);
> +static DEFINE_SPINLOCK(entry_list_lock);
> +
> +/* dmi_sysfs_attribute - Top level attribute. used by all entries. */
> +struct dmi_sysfs_attribute {
> + ? ? ? struct attribute attr;
> + ? ? ? ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
> +};
> +
> +#define DMI_SYSFS_ATTR(_entry, _name) \
> +struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
> + ? ? ? .attr = {.name = __stringify(_name), .mode = 0400}, \
> + ? ? ? .show = dmi_sysfs_##_entry##_##_name, \
> +}
> +
> +/*
> + * dmi_sysfs_mapped_attribute - Attribute where we require the entry be
> + * mapped in. ?Use in conjunction with dmi_sysfs_specialize_attr_ops.
> + */
> +struct dmi_sysfs_mapped_attribute {
> + ? ? ? struct attribute attr;
> + ? ? ? ssize_t (*show)(struct dmi_sysfs_entry *entry,
> + ? ? ? ? ? ? ? ? ? ? ? const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? char *buf);
> +};
> +
> +#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
> +struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
> + ? ? ? .attr = {.name = __stringify(_name), .mode = 0400}, \
> + ? ? ? .show = dmi_sysfs_##_entry##_##_name, \
> +}
> +
> +/*************************************************
> + * Generic DMI entry support.
> + *************************************************/
> +
> +static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
> +{
> + ? ? ? return container_of(kobj, struct dmi_sysfs_entry, kobj);
> +}
> +
> +static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
> +{
> + ? ? ? return container_of(attr, struct dmi_sysfs_attribute, attr);
> +}
> +
> +static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?struct attribute *_attr, char *buf)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry = to_entry(kobj);
> + ? ? ? struct dmi_sysfs_attribute *attr = to_attr(_attr);
> +
> + ? ? ? /* DMI stuff is only ever admin visible */
> + ? ? ? if (!capable(CAP_SYS_ADMIN))
> + ? ? ? ? ? ? ? return -EACCES;
> +
> + ? ? ? return attr->show(entry, buf);
> +}
> +
> +static const struct sysfs_ops dmi_sysfs_attr_ops = {
> + ? ? ? .show = dmi_sysfs_attr_show,
> +};
> +
> +typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? const struct dmi_header *dh, void *);
> +
> +struct find_dmi_data {
> + ? ? ? struct dmi_sysfs_entry ?*entry;
> + ? ? ? dmi_callback ? ? ? ? ? ?callback;
> + ? ? ? void ? ? ? ? ? ? ? ? ? ?*private;
> + ? ? ? int ? ? ? ? ? ? ? ? ? ? instance_countdown;
> + ? ? ? ssize_t ? ? ? ? ? ? ? ? ret;
> +};
> +
> +static void find_dmi_entry_helper(const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? void *_data)
> +{
> + ? ? ? struct find_dmi_data *data = _data;
> + ? ? ? struct dmi_sysfs_entry *entry = data->entry;
> +
> + ? ? ? /* Is this the entry we want? */
> + ? ? ? if (dh->type != entry->dh.type)
> + ? ? ? ? ? ? ? return;
> +
> + ? ? ? if (data->instance_countdown != 0) {
> + ? ? ? ? ? ? ? /* try the next instance? */
> + ? ? ? ? ? ? ? data->instance_countdown--;
> + ? ? ? ? ? ? ? return;
> + ? ? ? }
> +
> + ? ? ? /* Found the entry */
> + ? ? ? data->ret = data->callback(entry, dh, data->private);
> +}
> +
> +/* State for passing the read parameters through dmi_find_entry() */
> +struct dmi_read_state {
> + ? ? ? char *buf;
> + ? ? ? loff_t pos;
> + ? ? ? size_t count;
> +};
> +
> +static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? dmi_callback callback, void *private)
> +{
> + ? ? ? struct find_dmi_data data = {
> + ? ? ? ? ? ? ? .entry = entry,
> + ? ? ? ? ? ? ? .callback = callback,
> + ? ? ? ? ? ? ? .private = private,
> + ? ? ? ? ? ? ? .instance_countdown = entry->instance,
> + ? ? ? ? ? ? ? .ret = -EIO, ?/* To signal the entry disappeared */
> + ? ? ? };
> + ? ? ? int ret;
> +
> + ? ? ? ret = dmi_walk(find_dmi_entry_helper, &data);
> + ? ? ? /* This shouldn't happen, but just in case. */
> + ? ? ? if (ret)
> + ? ? ? ? ? ? ? return -EINVAL;
> + ? ? ? return data.ret;
> +}
> +
> +/*
> + * Calculate and return the byte length of the dmi entry identified by
> + * dh. ?This includes both the formatted portion as well as the
> + * unformatted string space, including the two trailing nul characters.
> + */
> +static size_t dmi_entry_length(const struct dmi_header *dh)
> +{
> + ? ? ? const char *p = (const char *)dh;
> +
> + ? ? ? p += dh->length;
> +
> + ? ? ? while (p[0] || p[1])
> + ? ? ? ? ? ? ? p++;
> +

Is there any chance this could walk outside of SMBIOS space on a
corrupted table? I can point you at some machines with corrupted
tables to test on if you want.

> + ? ? ? return 2 + p - (const char *)dh;
> +}
> +
> +/*************************************************
> + * Generic DMI entry support.
> + *************************************************/
> +
> +static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
> +{
> + ? ? ? return sprintf(buf, "%d\n", entry->dh.length);
> +}
> +
> +static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
> +{
> + ? ? ? return sprintf(buf, "%d\n", entry->dh.handle);
> +}
> +
> +static DMI_SYSFS_ATTR(entry, length);
> +static DMI_SYSFS_ATTR(entry, handle);
> +
> +static struct attribute *dmi_sysfs_entry_attrs[] = {
> + ? ? ? &dmi_sysfs_attr_entry_length.attr,
> + ? ? ? &dmi_sysfs_attr_entry_handle.attr,
> + ? ? ? NULL,
> +};
> +
> +static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?void *_state)
> +{
> + ? ? ? struct dmi_read_state *state = _state;
> + ? ? ? size_t entry_length;
> +
> + ? ? ? entry_length = dmi_entry_length(dh);
> +
> + ? ? ? return memory_read_from_buffer(state->buf, state->count,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?&state->pos, dh, entry_length);
> +}
> +
> +static ssize_t dmi_entry_raw_read(struct file *filp,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct kobject *kobj,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct bin_attribute *bin_attr,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? char *buf, loff_t pos, size_t count)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry = to_entry(kobj);
> + ? ? ? struct dmi_read_state state = {
> + ? ? ? ? ? ? ? .buf = buf,
> + ? ? ? ? ? ? ? .pos = pos,
> + ? ? ? ? ? ? ? .count = count,
> + ? ? ? };
> +
> + ? ? ? return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
> +}
> +
> +static const struct bin_attribute dmi_entry_raw_attr = {
> + ? ? ? .attr = {.name = "raw", .mode = 0400},
> + ? ? ? .read = dmi_entry_raw_read,
> +};
> +
> +static void dmi_sysfs_entry_release(struct kobject *kobj)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry = to_entry(kobj);
> + ? ? ? sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr);
> + ? ? ? spin_lock(&entry_list_lock);
> + ? ? ? list_del(&entry->list);
> + ? ? ? spin_unlock(&entry_list_lock);
> + ? ? ? kfree(entry);
> +}
> +
> +static struct kobj_type dmi_sysfs_entry_ktype = {
> + ? ? ? .release = dmi_sysfs_entry_release,
> + ? ? ? .sysfs_ops = &dmi_sysfs_attr_ops,
> + ? ? ? .default_attrs = dmi_sysfs_entry_attrs,
> +};
> +
> +static struct kobject *dmi_kobj;
> +static struct kset *dmi_kset;
> +
> +/* Global count of all instances seen. ?Only for setup */
> +static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
> +
> +static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?void *_ret)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry;
> + ? ? ? int *ret = _ret;
> +
> + ? ? ? /* If a previous entry saw an error, short circuit */
> + ? ? ? if (*ret)
> + ? ? ? ? ? ? ? return;
> +
> + ? ? ? /* Allocate and register a new entry into the entries set */
> + ? ? ? entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> + ? ? ? if (!entry) {
> + ? ? ? ? ? ? ? *ret = -ENOMEM;
> + ? ? ? ? ? ? ? return;
> + ? ? ? }
> +
> + ? ? ? /* Set the key */
> + ? ? ? entry->dh = *dh;
> + ? ? ? entry->instance = instance_counts[dh->type]++;
> +
> + ? ? ? entry->kobj.kset = dmi_kset;
> + ? ? ? *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "%d-%d", dh->type, entry->instance);
> +
> + ? ? ? if (*ret) {
> + ? ? ? ? ? ? ? kfree(entry);
> + ? ? ? ? ? ? ? return;
> + ? ? ? }
> +
> + ? ? ? /* Thread on the global list for cleanup */
> + ? ? ? spin_lock(&entry_list_lock);
> + ? ? ? list_add_tail(&entry->list, &entry_list);
> + ? ? ? spin_unlock(&entry_list_lock);
> +
> + ? ? ? /* Create the raw binary file to access the entry */
> + ? ? ? *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
> + ? ? ? if (*ret)
> + ? ? ? ? ? ? ? goto out_err;
> +
> + ? ? ? return;
> +out_err:
> + ? ? ? kobject_put(&entry->kobj);
> + ? ? ? return;
> +}
> +
> +static void cleanup_entry_list(void)
> +{
> + ? ? ? struct dmi_sysfs_entry *entry, *next;
> +
> + ? ? ? /* No locks, we are on our way out */
> + ? ? ? list_for_each_entry_safe(entry, next, &entry_list, list) {
> + ? ? ? ? ? ? ? kobject_put(&entry->kobj);
> + ? ? ? }
> +}
> +
> +static int __init dmi_sysfs_init(void)
> +{
> + ? ? ? int error = -ENOMEM;
> + ? ? ? int val;
> +
> + ? ? ? /* Set up our directory */
> + ? ? ? dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
> + ? ? ? if (!dmi_kobj)
> + ? ? ? ? ? ? ? goto err;
> +
> + ? ? ? dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
> + ? ? ? if (!dmi_kset)
> + ? ? ? ? ? ? ? goto err;
> +
> + ? ? ? val = 0;
> + ? ? ? error = dmi_walk(dmi_sysfs_register_handle, &val);
> + ? ? ? if (error)
> + ? ? ? ? ? ? ? goto err;
> + ? ? ? if (val) {
> + ? ? ? ? ? ? ? error = val;
> + ? ? ? ? ? ? ? goto err;
> + ? ? ? }
> +
> + ? ? ? pr_info("dmi-sysfs: loaded.\n");
> +
> + ? ? ? return 0;
> +err:
> + ? ? ? cleanup_entry_list();
> + ? ? ? kset_unregister(dmi_kset);
> + ? ? ? kobject_put(dmi_kobj);
> + ? ? ? return error;
> +}
> +
> +/* clean up everything. */
> +static void __exit dmi_sysfs_exit(void)
> +{
> + ? ? ? pr_info("dmi-sysfs: unloading.\n");
> + ? ? ? cleanup_entry_list();
> + ? ? ? kset_unregister(dmi_kset);
> + ? ? ? kobject_put(dmi_kobj);
> +}
> +
> +module_init(dmi_sysfs_init);
> +module_exit(dmi_sysfs_exit);
> +
> +MODULE_AUTHOR("Google, Inc.");
> +MODULE_DESCRIPTION("DMI sysfs support");
> +MODULE_LICENSE("GPL");
>
>

2011-02-22 23:58:44

by Mike Waychison

[permalink] [raw]
Subject: Re: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

On Tue, Feb 22, 2011 at 3:49 PM, Rob Lippert <[email protected]> wrote:
> On Thu, Feb 17, 2011 at 1:28 PM, Mike Waychison <[email protected]> wrote:
>> Introduce a new module "dmi-sysfs" that exports the broken out entries
>> of the DMI table through sysfs.
>>
>> Entries are enumerated via dmi_walk() on module load, and are populated
>> as kobjects rooted at /sys/firmware/dmi/entries.
>>
>> Entries are named "<type>-<instance>", where:
>> ? <type> ? ? ? : is the type of the entry, and
>> ? <instance> ? : is the ordinal count within the DMI table of that
>> ? ? ? ? ? ? ? ? ?entry type. ?This instance is used in lieu the DMI
>> ? ? ? ? ? ? ? ? ?entry's handle as no assurances are made by the kernel
>> ? ? ? ? ? ? ? ? ?that handles are unique.
>>
>> All entries export the following attributes:
>
> Can you add an entry here for index/ordinal in the set of all tables?
> This way the user can walk the tables in the original order exported
> by the BIOS. ?Some (buggy) BIOSes don't properly set up links between
> things like MEM_ARRAY and MEM_DEVICE tables but depend on the position
> of the table relative to other tables.
>

Oh weird. I can add an attribute called "position" if that works?
I'd rather not put the position in the directory name itself if I can
help it though, it just looks -- odd.

>> +/*
>> + * Calculate and return the byte length of the dmi entry identified by
>> + * dh. ?This includes both the formatted portion as well as the
>> + * unformatted string space, including the two trailing nul characters.
>> + */
>> +static size_t dmi_entry_length(const struct dmi_header *dh)
>> +{
>> + ? ? ? const char *p = (const char *)dh;
>> +
>> + ? ? ? p += dh->length;
>> +
>> + ? ? ? while (p[0] || p[1])
>> + ? ? ? ? ? ? ? p++;
>> +
>
> Is there any chance this could walk outside of SMBIOS space on a
> corrupted table? ?I can point you at some machines with corrupted
> tables to test on if you want.

AFAICT, this should be safe to do. dmi_table() is the code that
iterates through the table of entries, and will not callback any
entries if it can't find the double nul characters within dmi_len
bytes (as described by the dmi table header).

I'd be happy to test this on machines that get it wrong though. Just
add me to the auth files and IM me the hostnames :)

2011-02-23 00:07:42

by Luck, Tony

[permalink] [raw]
Subject: RE: [PATCH v1 2/5] firmware: Basic dmi-sysfs support

>Looks to be supported on ia64 as well, though I don't have any hardware
>to test this code on for that arch. Tony: I think this DMI exporting
>code should just work on ia64 as all it is using is dmi_walk() and
>parsing the entries as returned as dmi_headers in the callback. Does
>this sound sane to you?

Yes we have dmi on ia64 too.

-Tony