aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware/dmi-sysfs.c
diff options
context:
space:
mode:
authorMike Waychison <mikew@google.com>2011-02-22 20:53:21 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2011-02-25 15:01:19 -0500
commit948af1f0bbc8526448e8cbe3f8d3bf211bdf5181 (patch)
tree6ecf0035c6466002d3ae32b4ab5230f5abb567eb /drivers/firmware/dmi-sysfs.c
parent93c890dbe5287d146007083021148e7318058e37 (diff)
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. type : The DMI entry type instance : The ordinal instance of this entry given its type. position : The position ordinal of the entry within the table in its entirety. 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 <mikew@google.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/firmware/dmi-sysfs.c')
-rw-r--r--drivers/firmware/dmi-sysfs.c396
1 files changed, 396 insertions, 0 deletions
diff --git a/drivers/firmware/dmi-sysfs.c b/drivers/firmware/dmi-sysfs.c
new file mode 100644
index 000000000000..2d8a04a95085
--- /dev/null
+++ b/drivers/firmware/dmi-sysfs.c
@@ -0,0 +1,396 @@
1/*
2 * dmi-sysfs.c
3 *
4 * This module exports the DMI tables read-only to userspace through the
5 * sysfs file system.
6 *
7 * Data is currently found below
8 * /sys/firmware/dmi/...
9 *
10 * DMI attributes are presented in attribute files with names
11 * formatted using %d-%d, so that the first integer indicates the
12 * structure type (0-255), and the second field is the instance of that
13 * entry.
14 *
15 * Copyright 2011 Google, Inc.
16 */
17
18#include <linux/kernel.h>
19#include <linux/init.h>
20#include <linux/module.h>
21#include <linux/types.h>
22#include <linux/kobject.h>
23#include <linux/dmi.h>
24#include <linux/capability.h>
25#include <linux/slab.h>
26#include <linux/list.h>
27#include <linux/io.h>
28
29#define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
30 the top entry type is only 8 bits */
31
32struct dmi_sysfs_entry {
33 struct dmi_header dh;
34 struct kobject kobj;
35 int instance;
36 int position;
37 struct list_head list;
38};
39
40/*
41 * Global list of dmi_sysfs_entry. Even though this should only be
42 * manipulated at setup and teardown, the lazy nature of the kobject
43 * system means we get lazy removes.
44 */
45static LIST_HEAD(entry_list);
46static DEFINE_SPINLOCK(entry_list_lock);
47
48/* dmi_sysfs_attribute - Top level attribute. used by all entries. */
49struct dmi_sysfs_attribute {
50 struct attribute attr;
51 ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
52};
53
54#define DMI_SYSFS_ATTR(_entry, _name) \
55struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
56 .attr = {.name = __stringify(_name), .mode = 0400}, \
57 .show = dmi_sysfs_##_entry##_##_name, \
58}
59
60/*
61 * dmi_sysfs_mapped_attribute - Attribute where we require the entry be
62 * mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops.
63 */
64struct dmi_sysfs_mapped_attribute {
65 struct attribute attr;
66 ssize_t (*show)(struct dmi_sysfs_entry *entry,
67 const struct dmi_header *dh,
68 char *buf);
69};
70
71#define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
72struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
73 .attr = {.name = __stringify(_name), .mode = 0400}, \
74 .show = dmi_sysfs_##_entry##_##_name, \
75}
76
77/*************************************************
78 * Generic DMI entry support.
79 *************************************************/
80
81static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
82{
83 return container_of(kobj, struct dmi_sysfs_entry, kobj);
84}
85
86static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
87{
88 return container_of(attr, struct dmi_sysfs_attribute, attr);
89}
90
91static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
92 struct attribute *_attr, char *buf)
93{
94 struct dmi_sysfs_entry *entry = to_entry(kobj);
95 struct dmi_sysfs_attribute *attr = to_attr(_attr);
96
97 /* DMI stuff is only ever admin visible */
98 if (!capable(CAP_SYS_ADMIN))
99 return -EACCES;
100
101 return attr->show(entry, buf);
102}
103
104static const struct sysfs_ops dmi_sysfs_attr_ops = {
105 .show = dmi_sysfs_attr_show,
106};
107
108typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
109 const struct dmi_header *dh, void *);
110
111struct find_dmi_data {
112 struct dmi_sysfs_entry *entry;
113 dmi_callback callback;
114 void *private;
115 int instance_countdown;
116 ssize_t ret;
117};
118
119static void find_dmi_entry_helper(const struct dmi_header *dh,
120 void *_data)
121{
122 struct find_dmi_data *data = _data;
123 struct dmi_sysfs_entry *entry = data->entry;
124
125 /* Is this the entry we want? */
126 if (dh->type != entry->dh.type)
127 return;
128
129 if (data->instance_countdown != 0) {
130 /* try the next instance? */
131 data->instance_countdown--;
132 return;
133 }
134
135 /*
136 * Don't ever revisit the instance. Short circuit later
137 * instances by letting the instance_countdown run negative
138 */
139 data->instance_countdown--;
140
141 /* Found the entry */
142 data->ret = data->callback(entry, dh, data->private);
143}
144
145/* State for passing the read parameters through dmi_find_entry() */
146struct dmi_read_state {
147 char *buf;
148 loff_t pos;
149 size_t count;
150};
151
152static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
153 dmi_callback callback, void *private)
154{
155 struct find_dmi_data data = {
156 .entry = entry,
157 .callback = callback,
158 .private = private,
159 .instance_countdown = entry->instance,
160 .ret = -EIO, /* To signal the entry disappeared */
161 };
162 int ret;
163
164 ret = dmi_walk(find_dmi_entry_helper, &data);
165 /* This shouldn't happen, but just in case. */
166 if (ret)
167 return -EINVAL;
168 return data.ret;
169}
170
171/*
172 * Calculate and return the byte length of the dmi entry identified by
173 * dh. This includes both the formatted portion as well as the
174 * unformatted string space, including the two trailing nul characters.
175 */
176static size_t dmi_entry_length(const struct dmi_header *dh)
177{
178 const char *p = (const char *)dh;
179
180 p += dh->length;
181
182 while (p[0] || p[1])
183 p++;
184
185 return 2 + p - (const char *)dh;
186}
187
188/*************************************************
189 * Generic DMI entry support.
190 *************************************************/
191
192static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
193{
194 return sprintf(buf, "%d\n", entry->dh.length);
195}
196
197static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
198{
199 return sprintf(buf, "%d\n", entry->dh.handle);
200}
201
202static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf)
203{
204 return sprintf(buf, "%d\n", entry->dh.type);
205}
206
207static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry,
208 char *buf)
209{
210 return sprintf(buf, "%d\n", entry->instance);
211}
212
213static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry,
214 char *buf)
215{
216 return sprintf(buf, "%d\n", entry->position);
217}
218
219static DMI_SYSFS_ATTR(entry, length);
220static DMI_SYSFS_ATTR(entry, handle);
221static DMI_SYSFS_ATTR(entry, type);
222static DMI_SYSFS_ATTR(entry, instance);
223static DMI_SYSFS_ATTR(entry, position);
224
225static struct attribute *dmi_sysfs_entry_attrs[] = {
226 &dmi_sysfs_attr_entry_length.attr,
227 &dmi_sysfs_attr_entry_handle.attr,
228 &dmi_sysfs_attr_entry_type.attr,
229 &dmi_sysfs_attr_entry_instance.attr,
230 &dmi_sysfs_attr_entry_position.attr,
231 NULL,
232};
233
234static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
235 const struct dmi_header *dh,
236 void *_state)
237{
238 struct dmi_read_state *state = _state;
239 size_t entry_length;
240
241 entry_length = dmi_entry_length(dh);
242
243 return memory_read_from_buffer(state->buf, state->count,
244 &state->pos, dh, entry_length);
245}
246
247static ssize_t dmi_entry_raw_read(struct file *filp,
248 struct kobject *kobj,
249 struct bin_attribute *bin_attr,
250 char *buf, loff_t pos, size_t count)
251{
252 struct dmi_sysfs_entry *entry = to_entry(kobj);
253 struct dmi_read_state state = {
254 .buf = buf,
255 .pos = pos,
256 .count = count,
257 };
258
259 return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
260}
261
262static const struct bin_attribute dmi_entry_raw_attr = {
263 .attr = {.name = "raw", .mode = 0400},
264 .read = dmi_entry_raw_read,
265};
266
267static void dmi_sysfs_entry_release(struct kobject *kobj)
268{
269 struct dmi_sysfs_entry *entry = to_entry(kobj);
270 sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr);
271 spin_lock(&entry_list_lock);
272 list_del(&entry->list);
273 spin_unlock(&entry_list_lock);
274 kfree(entry);
275}
276
277static struct kobj_type dmi_sysfs_entry_ktype = {
278 .release = dmi_sysfs_entry_release,
279 .sysfs_ops = &dmi_sysfs_attr_ops,
280 .default_attrs = dmi_sysfs_entry_attrs,
281};
282
283static struct kobject *dmi_kobj;
284static struct kset *dmi_kset;
285
286/* Global count of all instances seen. Only for setup */
287static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
288
289/* Global positional count of all entries seen. Only for setup */
290static int __initdata position_count;
291
292static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
293 void *_ret)
294{
295 struct dmi_sysfs_entry *entry;
296 int *ret = _ret;
297
298 /* If a previous entry saw an error, short circuit */
299 if (*ret)
300 return;
301
302 /* Allocate and register a new entry into the entries set */
303 entry = kzalloc(sizeof(*entry), GFP_KERNEL);
304 if (!entry) {
305 *ret = -ENOMEM;
306 return;
307 }
308
309 /* Set the key */
310 entry->dh = *dh;
311 entry->instance = instance_counts[dh->type]++;
312 entry->position = position_count++;
313
314 entry->kobj.kset = dmi_kset;
315 *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
316 "%d-%d", dh->type, entry->instance);
317
318 if (*ret) {
319 kfree(entry);
320 return;
321 }
322
323 /* Thread on the global list for cleanup */
324 spin_lock(&entry_list_lock);
325 list_add_tail(&entry->list, &entry_list);
326 spin_unlock(&entry_list_lock);
327
328 /* Create the raw binary file to access the entry */
329 *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
330 if (*ret)
331 goto out_err;
332
333 return;
334out_err:
335 kobject_put(&entry->kobj);
336 return;
337}
338
339static void cleanup_entry_list(void)
340{
341 struct dmi_sysfs_entry *entry, *next;
342
343 /* No locks, we are on our way out */
344 list_for_each_entry_safe(entry, next, &entry_list, list) {
345 kobject_put(&entry->kobj);
346 }
347}
348
349static int __init dmi_sysfs_init(void)
350{
351 int error = -ENOMEM;
352 int val;
353
354 /* Set up our directory */
355 dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
356 if (!dmi_kobj)
357 goto err;
358
359 dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
360 if (!dmi_kset)
361 goto err;
362
363 val = 0;
364 error = dmi_walk(dmi_sysfs_register_handle, &val);
365 if (error)
366 goto err;
367 if (val) {
368 error = val;
369 goto err;
370 }
371
372 pr_debug("dmi-sysfs: loaded.\n");
373
374 return 0;
375err:
376 cleanup_entry_list();
377 kset_unregister(dmi_kset);
378 kobject_put(dmi_kobj);
379 return error;
380}
381
382/* clean up everything. */
383static void __exit dmi_sysfs_exit(void)
384{
385 pr_debug("dmi-sysfs: unloading.\n");
386 cleanup_entry_list();
387 kset_unregister(dmi_kset);
388 kobject_put(dmi_kobj);
389}
390
391module_init(dmi_sysfs_init);
392module_exit(dmi_sysfs_exit);
393
394MODULE_AUTHOR("Mike Waychison <mikew@google.com>");
395MODULE_DESCRIPTION("DMI sysfs support");
396MODULE_LICENSE("GPL");