/*
* 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/...
*
*
*/
#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;
int position;
struct list_head list;
struct kobject *child;
};
/*
* 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 void dmi_entry_free(struct kobject *kobj)
{
kfree(kobj);
}
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;
}
/*
* Don't ever revisit the instance. Short circuit later
* instances by letting the instance_countdown run negative
*/
data->instance_countdown--;
/* 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;
}
/*************************************************
* 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;
#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) \
{ \
struct dmi_system_event_log sel; \
if (sizeof(sel) > dmi_entry_length(dh)) \
return -EIO; \
memcpy(&sel, dh, sizeof(sel)); \
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,
评论0