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