1948af1f0SMike Waychison /* 2948af1f0SMike Waychison * dmi-sysfs.c 3948af1f0SMike Waychison * 4948af1f0SMike Waychison * This module exports the DMI tables read-only to userspace through the 5948af1f0SMike Waychison * sysfs file system. 6948af1f0SMike Waychison * 7948af1f0SMike Waychison * Data is currently found below 8948af1f0SMike Waychison * /sys/firmware/dmi/... 9948af1f0SMike Waychison * 10948af1f0SMike Waychison * DMI attributes are presented in attribute files with names 11948af1f0SMike Waychison * formatted using %d-%d, so that the first integer indicates the 12948af1f0SMike Waychison * structure type (0-255), and the second field is the instance of that 13948af1f0SMike Waychison * entry. 14948af1f0SMike Waychison * 15948af1f0SMike Waychison * Copyright 2011 Google, Inc. 16948af1f0SMike Waychison */ 17948af1f0SMike Waychison 18948af1f0SMike Waychison #include <linux/kernel.h> 19948af1f0SMike Waychison #include <linux/init.h> 20948af1f0SMike Waychison #include <linux/module.h> 21948af1f0SMike Waychison #include <linux/types.h> 22948af1f0SMike Waychison #include <linux/kobject.h> 23948af1f0SMike Waychison #include <linux/dmi.h> 24948af1f0SMike Waychison #include <linux/capability.h> 25948af1f0SMike Waychison #include <linux/slab.h> 26948af1f0SMike Waychison #include <linux/list.h> 27948af1f0SMike Waychison #include <linux/io.h> 28948af1f0SMike Waychison 29948af1f0SMike Waychison #define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider 30948af1f0SMike Waychison the top entry type is only 8 bits */ 31948af1f0SMike Waychison 32948af1f0SMike Waychison struct dmi_sysfs_entry { 33948af1f0SMike Waychison struct dmi_header dh; 34948af1f0SMike Waychison struct kobject kobj; 35948af1f0SMike Waychison int instance; 36948af1f0SMike Waychison int position; 37948af1f0SMike Waychison struct list_head list; 38948af1f0SMike Waychison }; 39948af1f0SMike Waychison 40948af1f0SMike Waychison /* 41948af1f0SMike Waychison * Global list of dmi_sysfs_entry. Even though this should only be 42948af1f0SMike Waychison * manipulated at setup and teardown, the lazy nature of the kobject 43948af1f0SMike Waychison * system means we get lazy removes. 44948af1f0SMike Waychison */ 45948af1f0SMike Waychison static LIST_HEAD(entry_list); 46948af1f0SMike Waychison static DEFINE_SPINLOCK(entry_list_lock); 47948af1f0SMike Waychison 48948af1f0SMike Waychison /* dmi_sysfs_attribute - Top level attribute. used by all entries. */ 49948af1f0SMike Waychison struct dmi_sysfs_attribute { 50948af1f0SMike Waychison struct attribute attr; 51948af1f0SMike Waychison ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf); 52948af1f0SMike Waychison }; 53948af1f0SMike Waychison 54948af1f0SMike Waychison #define DMI_SYSFS_ATTR(_entry, _name) \ 55948af1f0SMike Waychison struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \ 56948af1f0SMike Waychison .attr = {.name = __stringify(_name), .mode = 0400}, \ 57948af1f0SMike Waychison .show = dmi_sysfs_##_entry##_##_name, \ 58948af1f0SMike Waychison } 59948af1f0SMike Waychison 60948af1f0SMike Waychison /* 61948af1f0SMike Waychison * dmi_sysfs_mapped_attribute - Attribute where we require the entry be 62948af1f0SMike Waychison * mapped in. Use in conjunction with dmi_sysfs_specialize_attr_ops. 63948af1f0SMike Waychison */ 64948af1f0SMike Waychison struct dmi_sysfs_mapped_attribute { 65948af1f0SMike Waychison struct attribute attr; 66948af1f0SMike Waychison ssize_t (*show)(struct dmi_sysfs_entry *entry, 67948af1f0SMike Waychison const struct dmi_header *dh, 68948af1f0SMike Waychison char *buf); 69948af1f0SMike Waychison }; 70948af1f0SMike Waychison 71948af1f0SMike Waychison #define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \ 72948af1f0SMike Waychison struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \ 73948af1f0SMike Waychison .attr = {.name = __stringify(_name), .mode = 0400}, \ 74948af1f0SMike Waychison .show = dmi_sysfs_##_entry##_##_name, \ 75948af1f0SMike Waychison } 76948af1f0SMike Waychison 77948af1f0SMike Waychison /************************************************* 78948af1f0SMike Waychison * Generic DMI entry support. 79948af1f0SMike Waychison *************************************************/ 80948af1f0SMike Waychison 81948af1f0SMike Waychison static struct dmi_sysfs_entry *to_entry(struct kobject *kobj) 82948af1f0SMike Waychison { 83948af1f0SMike Waychison return container_of(kobj, struct dmi_sysfs_entry, kobj); 84948af1f0SMike Waychison } 85948af1f0SMike Waychison 86948af1f0SMike Waychison static struct dmi_sysfs_attribute *to_attr(struct attribute *attr) 87948af1f0SMike Waychison { 88948af1f0SMike Waychison return container_of(attr, struct dmi_sysfs_attribute, attr); 89948af1f0SMike Waychison } 90948af1f0SMike Waychison 91948af1f0SMike Waychison static ssize_t dmi_sysfs_attr_show(struct kobject *kobj, 92948af1f0SMike Waychison struct attribute *_attr, char *buf) 93948af1f0SMike Waychison { 94948af1f0SMike Waychison struct dmi_sysfs_entry *entry = to_entry(kobj); 95948af1f0SMike Waychison struct dmi_sysfs_attribute *attr = to_attr(_attr); 96948af1f0SMike Waychison 97948af1f0SMike Waychison /* DMI stuff is only ever admin visible */ 98948af1f0SMike Waychison if (!capable(CAP_SYS_ADMIN)) 99948af1f0SMike Waychison return -EACCES; 100948af1f0SMike Waychison 101948af1f0SMike Waychison return attr->show(entry, buf); 102948af1f0SMike Waychison } 103948af1f0SMike Waychison 104948af1f0SMike Waychison static const struct sysfs_ops dmi_sysfs_attr_ops = { 105948af1f0SMike Waychison .show = dmi_sysfs_attr_show, 106948af1f0SMike Waychison }; 107948af1f0SMike Waychison 108948af1f0SMike Waychison typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *, 109948af1f0SMike Waychison const struct dmi_header *dh, void *); 110948af1f0SMike Waychison 111948af1f0SMike Waychison struct find_dmi_data { 112948af1f0SMike Waychison struct dmi_sysfs_entry *entry; 113948af1f0SMike Waychison dmi_callback callback; 114948af1f0SMike Waychison void *private; 115948af1f0SMike Waychison int instance_countdown; 116948af1f0SMike Waychison ssize_t ret; 117948af1f0SMike Waychison }; 118948af1f0SMike Waychison 119948af1f0SMike Waychison static void find_dmi_entry_helper(const struct dmi_header *dh, 120948af1f0SMike Waychison void *_data) 121948af1f0SMike Waychison { 122948af1f0SMike Waychison struct find_dmi_data *data = _data; 123948af1f0SMike Waychison struct dmi_sysfs_entry *entry = data->entry; 124948af1f0SMike Waychison 125948af1f0SMike Waychison /* Is this the entry we want? */ 126948af1f0SMike Waychison if (dh->type != entry->dh.type) 127948af1f0SMike Waychison return; 128948af1f0SMike Waychison 129948af1f0SMike Waychison if (data->instance_countdown != 0) { 130948af1f0SMike Waychison /* try the next instance? */ 131948af1f0SMike Waychison data->instance_countdown--; 132948af1f0SMike Waychison return; 133948af1f0SMike Waychison } 134948af1f0SMike Waychison 135948af1f0SMike Waychison /* 136948af1f0SMike Waychison * Don't ever revisit the instance. Short circuit later 137948af1f0SMike Waychison * instances by letting the instance_countdown run negative 138948af1f0SMike Waychison */ 139948af1f0SMike Waychison data->instance_countdown--; 140948af1f0SMike Waychison 141948af1f0SMike Waychison /* Found the entry */ 142948af1f0SMike Waychison data->ret = data->callback(entry, dh, data->private); 143948af1f0SMike Waychison } 144948af1f0SMike Waychison 145948af1f0SMike Waychison /* State for passing the read parameters through dmi_find_entry() */ 146948af1f0SMike Waychison struct dmi_read_state { 147948af1f0SMike Waychison char *buf; 148948af1f0SMike Waychison loff_t pos; 149948af1f0SMike Waychison size_t count; 150948af1f0SMike Waychison }; 151948af1f0SMike Waychison 152948af1f0SMike Waychison static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry, 153948af1f0SMike Waychison dmi_callback callback, void *private) 154948af1f0SMike Waychison { 155948af1f0SMike Waychison struct find_dmi_data data = { 156948af1f0SMike Waychison .entry = entry, 157948af1f0SMike Waychison .callback = callback, 158948af1f0SMike Waychison .private = private, 159948af1f0SMike Waychison .instance_countdown = entry->instance, 160948af1f0SMike Waychison .ret = -EIO, /* To signal the entry disappeared */ 161948af1f0SMike Waychison }; 162948af1f0SMike Waychison int ret; 163948af1f0SMike Waychison 164948af1f0SMike Waychison ret = dmi_walk(find_dmi_entry_helper, &data); 165948af1f0SMike Waychison /* This shouldn't happen, but just in case. */ 166948af1f0SMike Waychison if (ret) 167948af1f0SMike Waychison return -EINVAL; 168948af1f0SMike Waychison return data.ret; 169948af1f0SMike Waychison } 170948af1f0SMike Waychison 171948af1f0SMike Waychison /* 172948af1f0SMike Waychison * Calculate and return the byte length of the dmi entry identified by 173948af1f0SMike Waychison * dh. This includes both the formatted portion as well as the 174948af1f0SMike Waychison * unformatted string space, including the two trailing nul characters. 175948af1f0SMike Waychison */ 176948af1f0SMike Waychison static size_t dmi_entry_length(const struct dmi_header *dh) 177948af1f0SMike Waychison { 178948af1f0SMike Waychison const char *p = (const char *)dh; 179948af1f0SMike Waychison 180948af1f0SMike Waychison p += dh->length; 181948af1f0SMike Waychison 182948af1f0SMike Waychison while (p[0] || p[1]) 183948af1f0SMike Waychison p++; 184948af1f0SMike Waychison 185948af1f0SMike Waychison return 2 + p - (const char *)dh; 186948af1f0SMike Waychison } 187948af1f0SMike Waychison 188948af1f0SMike Waychison /************************************************* 189948af1f0SMike Waychison * Generic DMI entry support. 190948af1f0SMike Waychison *************************************************/ 191948af1f0SMike Waychison 192948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf) 193948af1f0SMike Waychison { 194948af1f0SMike Waychison return sprintf(buf, "%d\n", entry->dh.length); 195948af1f0SMike Waychison } 196948af1f0SMike Waychison 197948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf) 198948af1f0SMike Waychison { 199948af1f0SMike Waychison return sprintf(buf, "%d\n", entry->dh.handle); 200948af1f0SMike Waychison } 201948af1f0SMike Waychison 202948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf) 203948af1f0SMike Waychison { 204948af1f0SMike Waychison return sprintf(buf, "%d\n", entry->dh.type); 205948af1f0SMike Waychison } 206948af1f0SMike Waychison 207948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry, 208948af1f0SMike Waychison char *buf) 209948af1f0SMike Waychison { 210948af1f0SMike Waychison return sprintf(buf, "%d\n", entry->instance); 211948af1f0SMike Waychison } 212948af1f0SMike Waychison 213948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry, 214948af1f0SMike Waychison char *buf) 215948af1f0SMike Waychison { 216948af1f0SMike Waychison return sprintf(buf, "%d\n", entry->position); 217948af1f0SMike Waychison } 218948af1f0SMike Waychison 219948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, length); 220948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, handle); 221948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, type); 222948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, instance); 223948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, position); 224948af1f0SMike Waychison 225948af1f0SMike Waychison static struct attribute *dmi_sysfs_entry_attrs[] = { 226948af1f0SMike Waychison &dmi_sysfs_attr_entry_length.attr, 227948af1f0SMike Waychison &dmi_sysfs_attr_entry_handle.attr, 228948af1f0SMike Waychison &dmi_sysfs_attr_entry_type.attr, 229948af1f0SMike Waychison &dmi_sysfs_attr_entry_instance.attr, 230948af1f0SMike Waychison &dmi_sysfs_attr_entry_position.attr, 231948af1f0SMike Waychison NULL, 232948af1f0SMike Waychison }; 233948af1f0SMike Waychison 234948af1f0SMike Waychison static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry, 235948af1f0SMike Waychison const struct dmi_header *dh, 236948af1f0SMike Waychison void *_state) 237948af1f0SMike Waychison { 238948af1f0SMike Waychison struct dmi_read_state *state = _state; 239948af1f0SMike Waychison size_t entry_length; 240948af1f0SMike Waychison 241948af1f0SMike Waychison entry_length = dmi_entry_length(dh); 242948af1f0SMike Waychison 243948af1f0SMike Waychison return memory_read_from_buffer(state->buf, state->count, 244948af1f0SMike Waychison &state->pos, dh, entry_length); 245948af1f0SMike Waychison } 246948af1f0SMike Waychison 247948af1f0SMike Waychison static ssize_t dmi_entry_raw_read(struct file *filp, 248948af1f0SMike Waychison struct kobject *kobj, 249948af1f0SMike Waychison struct bin_attribute *bin_attr, 250948af1f0SMike Waychison char *buf, loff_t pos, size_t count) 251948af1f0SMike Waychison { 252948af1f0SMike Waychison struct dmi_sysfs_entry *entry = to_entry(kobj); 253948af1f0SMike Waychison struct dmi_read_state state = { 254948af1f0SMike Waychison .buf = buf, 255948af1f0SMike Waychison .pos = pos, 256948af1f0SMike Waychison .count = count, 257948af1f0SMike Waychison }; 258948af1f0SMike Waychison 259948af1f0SMike Waychison return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state); 260948af1f0SMike Waychison } 261948af1f0SMike Waychison 262948af1f0SMike Waychison static const struct bin_attribute dmi_entry_raw_attr = { 263948af1f0SMike Waychison .attr = {.name = "raw", .mode = 0400}, 264948af1f0SMike Waychison .read = dmi_entry_raw_read, 265948af1f0SMike Waychison }; 266948af1f0SMike Waychison 267948af1f0SMike Waychison static void dmi_sysfs_entry_release(struct kobject *kobj) 268948af1f0SMike Waychison { 269948af1f0SMike Waychison struct dmi_sysfs_entry *entry = to_entry(kobj); 270948af1f0SMike Waychison sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr); 271948af1f0SMike Waychison spin_lock(&entry_list_lock); 272948af1f0SMike Waychison list_del(&entry->list); 273948af1f0SMike Waychison spin_unlock(&entry_list_lock); 274948af1f0SMike Waychison kfree(entry); 275948af1f0SMike Waychison } 276948af1f0SMike Waychison 277948af1f0SMike Waychison static struct kobj_type dmi_sysfs_entry_ktype = { 278948af1f0SMike Waychison .release = dmi_sysfs_entry_release, 279948af1f0SMike Waychison .sysfs_ops = &dmi_sysfs_attr_ops, 280948af1f0SMike Waychison .default_attrs = dmi_sysfs_entry_attrs, 281948af1f0SMike Waychison }; 282948af1f0SMike Waychison 283948af1f0SMike Waychison static struct kobject *dmi_kobj; 284948af1f0SMike Waychison static struct kset *dmi_kset; 285948af1f0SMike Waychison 286948af1f0SMike Waychison /* Global count of all instances seen. Only for setup */ 287948af1f0SMike Waychison static int __initdata instance_counts[MAX_ENTRY_TYPE + 1]; 288948af1f0SMike Waychison 289948af1f0SMike Waychison /* Global positional count of all entries seen. Only for setup */ 290948af1f0SMike Waychison static int __initdata position_count; 291948af1f0SMike Waychison 292948af1f0SMike Waychison static void __init dmi_sysfs_register_handle(const struct dmi_header *dh, 293948af1f0SMike Waychison void *_ret) 294948af1f0SMike Waychison { 295948af1f0SMike Waychison struct dmi_sysfs_entry *entry; 296948af1f0SMike Waychison int *ret = _ret; 297948af1f0SMike Waychison 298948af1f0SMike Waychison /* If a previous entry saw an error, short circuit */ 299948af1f0SMike Waychison if (*ret) 300948af1f0SMike Waychison return; 301948af1f0SMike Waychison 302948af1f0SMike Waychison /* Allocate and register a new entry into the entries set */ 303948af1f0SMike Waychison entry = kzalloc(sizeof(*entry), GFP_KERNEL); 304948af1f0SMike Waychison if (!entry) { 305948af1f0SMike Waychison *ret = -ENOMEM; 306948af1f0SMike Waychison return; 307948af1f0SMike Waychison } 308948af1f0SMike Waychison 309948af1f0SMike Waychison /* Set the key */ 310948af1f0SMike Waychison entry->dh = *dh; 311948af1f0SMike Waychison entry->instance = instance_counts[dh->type]++; 312948af1f0SMike Waychison entry->position = position_count++; 313948af1f0SMike Waychison 314948af1f0SMike Waychison entry->kobj.kset = dmi_kset; 315948af1f0SMike Waychison *ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL, 316948af1f0SMike Waychison "%d-%d", dh->type, entry->instance); 317948af1f0SMike Waychison 318948af1f0SMike Waychison if (*ret) { 319948af1f0SMike Waychison kfree(entry); 320948af1f0SMike Waychison return; 321948af1f0SMike Waychison } 322948af1f0SMike Waychison 323948af1f0SMike Waychison /* Thread on the global list for cleanup */ 324948af1f0SMike Waychison spin_lock(&entry_list_lock); 325948af1f0SMike Waychison list_add_tail(&entry->list, &entry_list); 326948af1f0SMike Waychison spin_unlock(&entry_list_lock); 327948af1f0SMike Waychison 328948af1f0SMike Waychison /* Create the raw binary file to access the entry */ 329948af1f0SMike Waychison *ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr); 330948af1f0SMike Waychison if (*ret) 331948af1f0SMike Waychison goto out_err; 332948af1f0SMike Waychison 333948af1f0SMike Waychison return; 334948af1f0SMike Waychison out_err: 335948af1f0SMike Waychison kobject_put(&entry->kobj); 336948af1f0SMike Waychison return; 337948af1f0SMike Waychison } 338948af1f0SMike Waychison 339948af1f0SMike Waychison static void cleanup_entry_list(void) 340948af1f0SMike Waychison { 341948af1f0SMike Waychison struct dmi_sysfs_entry *entry, *next; 342948af1f0SMike Waychison 343948af1f0SMike Waychison /* No locks, we are on our way out */ 344948af1f0SMike Waychison list_for_each_entry_safe(entry, next, &entry_list, list) { 345948af1f0SMike Waychison kobject_put(&entry->kobj); 346948af1f0SMike Waychison } 347948af1f0SMike Waychison } 348948af1f0SMike Waychison 349948af1f0SMike Waychison static int __init dmi_sysfs_init(void) 350948af1f0SMike Waychison { 351948af1f0SMike Waychison int error = -ENOMEM; 352948af1f0SMike Waychison int val; 353948af1f0SMike Waychison 354948af1f0SMike Waychison /* Set up our directory */ 355948af1f0SMike Waychison dmi_kobj = kobject_create_and_add("dmi", firmware_kobj); 356948af1f0SMike Waychison if (!dmi_kobj) 357948af1f0SMike Waychison goto err; 358948af1f0SMike Waychison 359948af1f0SMike Waychison dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj); 360948af1f0SMike Waychison if (!dmi_kset) 361948af1f0SMike Waychison goto err; 362948af1f0SMike Waychison 363948af1f0SMike Waychison val = 0; 364948af1f0SMike Waychison error = dmi_walk(dmi_sysfs_register_handle, &val); 365948af1f0SMike Waychison if (error) 366948af1f0SMike Waychison goto err; 367948af1f0SMike Waychison if (val) { 368948af1f0SMike Waychison error = val; 369948af1f0SMike Waychison goto err; 370948af1f0SMike Waychison } 371948af1f0SMike Waychison 372948af1f0SMike Waychison pr_debug("dmi-sysfs: loaded.\n"); 373948af1f0SMike Waychison 374948af1f0SMike Waychison return 0; 375948af1f0SMike Waychison err: 376948af1f0SMike Waychison cleanup_entry_list(); 377948af1f0SMike Waychison kset_unregister(dmi_kset); 378948af1f0SMike Waychison kobject_put(dmi_kobj); 379948af1f0SMike Waychison return error; 380948af1f0SMike Waychison } 381948af1f0SMike Waychison 382948af1f0SMike Waychison /* clean up everything. */ 383948af1f0SMike Waychison static void __exit dmi_sysfs_exit(void) 384948af1f0SMike Waychison { 385948af1f0SMike Waychison pr_debug("dmi-sysfs: unloading.\n"); 386948af1f0SMike Waychison cleanup_entry_list(); 387948af1f0SMike Waychison kset_unregister(dmi_kset); 388948af1f0SMike Waychison kobject_put(dmi_kobj); 389948af1f0SMike Waychison } 390948af1f0SMike Waychison 391948af1f0SMike Waychison module_init(dmi_sysfs_init); 392948af1f0SMike Waychison module_exit(dmi_sysfs_exit); 393948af1f0SMike Waychison 394948af1f0SMike Waychison MODULE_AUTHOR("Mike Waychison <mikew@google.com>"); 395948af1f0SMike Waychison MODULE_DESCRIPTION("DMI sysfs support"); 396948af1f0SMike Waychison MODULE_LICENSE("GPL"); 397