brintos

brintos / linux-shallow public Read only

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