1049a59dbSWei-Ning Huang /* 2049a59dbSWei-Ning Huang * vpd.c 3049a59dbSWei-Ning Huang * 4049a59dbSWei-Ning Huang * Driver for exporting VPD content to sysfs. 5049a59dbSWei-Ning Huang * 6049a59dbSWei-Ning Huang * Copyright 2017 Google Inc. 7049a59dbSWei-Ning Huang * 8049a59dbSWei-Ning Huang * This program is free software; you can redistribute it and/or modify 9049a59dbSWei-Ning Huang * it under the terms of the GNU General Public License v2.0 as published by 10049a59dbSWei-Ning Huang * the Free Software Foundation. 11049a59dbSWei-Ning Huang * 12049a59dbSWei-Ning Huang * This program is distributed in the hope that it will be useful, 13049a59dbSWei-Ning Huang * but WITHOUT ANY WARRANTY; without even the implied warranty of 14049a59dbSWei-Ning Huang * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15049a59dbSWei-Ning Huang * GNU General Public License for more details. 16049a59dbSWei-Ning Huang */ 17049a59dbSWei-Ning Huang 18049a59dbSWei-Ning Huang #include <linux/ctype.h> 19049a59dbSWei-Ning Huang #include <linux/init.h> 20049a59dbSWei-Ning Huang #include <linux/io.h> 21049a59dbSWei-Ning Huang #include <linux/kernel.h> 22049a59dbSWei-Ning Huang #include <linux/kobject.h> 23049a59dbSWei-Ning Huang #include <linux/list.h> 24049a59dbSWei-Ning Huang #include <linux/module.h> 2546505c80SDmitry Torokhov #include <linux/of_address.h> 2646505c80SDmitry Torokhov #include <linux/platform_device.h> 27049a59dbSWei-Ning Huang #include <linux/slab.h> 28049a59dbSWei-Ning Huang #include <linux/sysfs.h> 29049a59dbSWei-Ning Huang 30049a59dbSWei-Ning Huang #include "coreboot_table.h" 31049a59dbSWei-Ning Huang #include "vpd_decode.h" 32049a59dbSWei-Ning Huang 33049a59dbSWei-Ning Huang #define CB_TAG_VPD 0x2c 34049a59dbSWei-Ning Huang #define VPD_CBMEM_MAGIC 0x43524f53 35049a59dbSWei-Ning Huang 36049a59dbSWei-Ning Huang static struct kobject *vpd_kobj; 37049a59dbSWei-Ning Huang 38049a59dbSWei-Ning Huang struct vpd_cbmem { 39049a59dbSWei-Ning Huang u32 magic; 40049a59dbSWei-Ning Huang u32 version; 41049a59dbSWei-Ning Huang u32 ro_size; 42049a59dbSWei-Ning Huang u32 rw_size; 43049a59dbSWei-Ning Huang u8 blob[0]; 44049a59dbSWei-Ning Huang }; 45049a59dbSWei-Ning Huang 46049a59dbSWei-Ning Huang struct vpd_section { 47049a59dbSWei-Ning Huang bool enabled; 48049a59dbSWei-Ning Huang const char *name; 49049a59dbSWei-Ning Huang char *raw_name; /* the string name_raw */ 50049a59dbSWei-Ning Huang struct kobject *kobj; /* vpd/name directory */ 51049a59dbSWei-Ning Huang char *baseaddr; 52049a59dbSWei-Ning Huang struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */ 53049a59dbSWei-Ning Huang struct list_head attribs; /* key/value in vpd_attrib_info list */ 54049a59dbSWei-Ning Huang }; 55049a59dbSWei-Ning Huang 56049a59dbSWei-Ning Huang struct vpd_attrib_info { 57049a59dbSWei-Ning Huang char *key; 58049a59dbSWei-Ning Huang const char *value; 59049a59dbSWei-Ning Huang struct bin_attribute bin_attr; 60049a59dbSWei-Ning Huang struct list_head list; 61049a59dbSWei-Ning Huang }; 62049a59dbSWei-Ning Huang 63049a59dbSWei-Ning Huang static struct vpd_section ro_vpd; 64049a59dbSWei-Ning Huang static struct vpd_section rw_vpd; 65049a59dbSWei-Ning Huang 66049a59dbSWei-Ning Huang static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp, 67049a59dbSWei-Ning Huang struct bin_attribute *bin_attr, char *buf, 68049a59dbSWei-Ning Huang loff_t pos, size_t count) 69049a59dbSWei-Ning Huang { 70049a59dbSWei-Ning Huang struct vpd_attrib_info *info = bin_attr->private; 71049a59dbSWei-Ning Huang 72049a59dbSWei-Ning Huang return memory_read_from_buffer(buf, count, &pos, info->value, 73049a59dbSWei-Ning Huang info->bin_attr.size); 74049a59dbSWei-Ning Huang } 75049a59dbSWei-Ning Huang 76049a59dbSWei-Ning Huang /* 77049a59dbSWei-Ning Huang * vpd_section_check_key_name() 78049a59dbSWei-Ning Huang * 79049a59dbSWei-Ning Huang * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but 80049a59dbSWei-Ning Huang * old firmware versions may have entries like "S/N" which are problematic when 81049a59dbSWei-Ning Huang * exporting them as sysfs attributes. These keys present in old firmwares are 82049a59dbSWei-Ning Huang * ignored. 83049a59dbSWei-Ning Huang * 84049a59dbSWei-Ning Huang * Returns VPD_OK for a valid key name, VPD_FAIL otherwise. 85049a59dbSWei-Ning Huang * 86049a59dbSWei-Ning Huang * @key: The key name to check 87049a59dbSWei-Ning Huang * @key_len: key name length 88049a59dbSWei-Ning Huang */ 89049a59dbSWei-Ning Huang static int vpd_section_check_key_name(const u8 *key, s32 key_len) 90049a59dbSWei-Ning Huang { 91049a59dbSWei-Ning Huang int c; 92049a59dbSWei-Ning Huang 93049a59dbSWei-Ning Huang while (key_len-- > 0) { 94049a59dbSWei-Ning Huang c = *key++; 95049a59dbSWei-Ning Huang 96049a59dbSWei-Ning Huang if (!isalnum(c) && c != '_') 97049a59dbSWei-Ning Huang return VPD_FAIL; 98049a59dbSWei-Ning Huang } 99049a59dbSWei-Ning Huang 100049a59dbSWei-Ning Huang return VPD_OK; 101049a59dbSWei-Ning Huang } 102049a59dbSWei-Ning Huang 103049a59dbSWei-Ning Huang static int vpd_section_attrib_add(const u8 *key, s32 key_len, 104049a59dbSWei-Ning Huang const u8 *value, s32 value_len, 105049a59dbSWei-Ning Huang void *arg) 106049a59dbSWei-Ning Huang { 107049a59dbSWei-Ning Huang int ret; 108049a59dbSWei-Ning Huang struct vpd_section *sec = arg; 109049a59dbSWei-Ning Huang struct vpd_attrib_info *info; 110049a59dbSWei-Ning Huang 111049a59dbSWei-Ning Huang /* 112049a59dbSWei-Ning Huang * Return VPD_OK immediately to decode next entry if the current key 113049a59dbSWei-Ning Huang * name contains invalid characters. 114049a59dbSWei-Ning Huang */ 115049a59dbSWei-Ning Huang if (vpd_section_check_key_name(key, key_len) != VPD_OK) 116049a59dbSWei-Ning Huang return VPD_OK; 117049a59dbSWei-Ning Huang 118049a59dbSWei-Ning Huang info = kzalloc(sizeof(*info), GFP_KERNEL); 1199434cec1SChristophe JAILLET if (!info) 120049a59dbSWei-Ning Huang return -ENOMEM; 1213eec6a1cSDmitry Torokhov 1223eec6a1cSDmitry Torokhov info->key = kstrndup(key, key_len, GFP_KERNEL); 1239434cec1SChristophe JAILLET if (!info->key) { 1249434cec1SChristophe JAILLET ret = -ENOMEM; 1259434cec1SChristophe JAILLET goto free_info; 1269434cec1SChristophe JAILLET } 127049a59dbSWei-Ning Huang 128049a59dbSWei-Ning Huang sysfs_bin_attr_init(&info->bin_attr); 129049a59dbSWei-Ning Huang info->bin_attr.attr.name = info->key; 130049a59dbSWei-Ning Huang info->bin_attr.attr.mode = 0444; 131049a59dbSWei-Ning Huang info->bin_attr.size = value_len; 132049a59dbSWei-Ning Huang info->bin_attr.read = vpd_attrib_read; 133049a59dbSWei-Ning Huang info->bin_attr.private = info; 134049a59dbSWei-Ning Huang 135049a59dbSWei-Ning Huang info->value = value; 136049a59dbSWei-Ning Huang 137049a59dbSWei-Ning Huang INIT_LIST_HEAD(&info->list); 138049a59dbSWei-Ning Huang 139049a59dbSWei-Ning Huang ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr); 1409434cec1SChristophe JAILLET if (ret) 1419434cec1SChristophe JAILLET goto free_info_key; 142049a59dbSWei-Ning Huang 1437f53de20SDmitry Torokhov list_add_tail(&info->list, &sec->attribs); 144049a59dbSWei-Ning Huang return 0; 1459434cec1SChristophe JAILLET 1469434cec1SChristophe JAILLET free_info_key: 1479434cec1SChristophe JAILLET kfree(info->key); 1489434cec1SChristophe JAILLET free_info: 1499434cec1SChristophe JAILLET kfree(info); 1509434cec1SChristophe JAILLET 1519434cec1SChristophe JAILLET return ret; 152049a59dbSWei-Ning Huang } 153049a59dbSWei-Ning Huang 154049a59dbSWei-Ning Huang static void vpd_section_attrib_destroy(struct vpd_section *sec) 155049a59dbSWei-Ning Huang { 156049a59dbSWei-Ning Huang struct vpd_attrib_info *info; 157049a59dbSWei-Ning Huang struct vpd_attrib_info *temp; 158049a59dbSWei-Ning Huang 159049a59dbSWei-Ning Huang list_for_each_entry_safe(info, temp, &sec->attribs, list) { 160049a59dbSWei-Ning Huang sysfs_remove_bin_file(sec->kobj, &info->bin_attr); 161ce57cba3SDmitry Torokhov kfree(info->key); 162049a59dbSWei-Ning Huang kfree(info); 163049a59dbSWei-Ning Huang } 164049a59dbSWei-Ning Huang } 165049a59dbSWei-Ning Huang 166049a59dbSWei-Ning Huang static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp, 167049a59dbSWei-Ning Huang struct bin_attribute *bin_attr, char *buf, 168049a59dbSWei-Ning Huang loff_t pos, size_t count) 169049a59dbSWei-Ning Huang { 170049a59dbSWei-Ning Huang struct vpd_section *sec = bin_attr->private; 171049a59dbSWei-Ning Huang 172049a59dbSWei-Ning Huang return memory_read_from_buffer(buf, count, &pos, sec->baseaddr, 173049a59dbSWei-Ning Huang sec->bin_attr.size); 174049a59dbSWei-Ning Huang } 175049a59dbSWei-Ning Huang 176049a59dbSWei-Ning Huang static int vpd_section_create_attribs(struct vpd_section *sec) 177049a59dbSWei-Ning Huang { 178049a59dbSWei-Ning Huang s32 consumed; 179049a59dbSWei-Ning Huang int ret; 180049a59dbSWei-Ning Huang 181049a59dbSWei-Ning Huang consumed = 0; 182049a59dbSWei-Ning Huang do { 183049a59dbSWei-Ning Huang ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr, 184049a59dbSWei-Ning Huang &consumed, vpd_section_attrib_add, sec); 185049a59dbSWei-Ning Huang } while (ret == VPD_OK); 186049a59dbSWei-Ning Huang 187049a59dbSWei-Ning Huang return 0; 188049a59dbSWei-Ning Huang } 189049a59dbSWei-Ning Huang 190049a59dbSWei-Ning Huang static int vpd_section_init(const char *name, struct vpd_section *sec, 191049a59dbSWei-Ning Huang phys_addr_t physaddr, size_t size) 192049a59dbSWei-Ning Huang { 1939920a33eSDmitry Torokhov int err; 194049a59dbSWei-Ning Huang 195049a59dbSWei-Ning Huang sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB); 196049a59dbSWei-Ning Huang if (!sec->baseaddr) 197049a59dbSWei-Ning Huang return -ENOMEM; 198049a59dbSWei-Ning Huang 199049a59dbSWei-Ning Huang sec->name = name; 200049a59dbSWei-Ning Huang 201049a59dbSWei-Ning Huang /* We want to export the raw partion with name ${name}_raw */ 2029920a33eSDmitry Torokhov sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name); 2039920a33eSDmitry Torokhov if (!sec->raw_name) { 2049920a33eSDmitry Torokhov err = -ENOMEM; 20575e5bae6SPan Bian goto err_memunmap; 2069920a33eSDmitry Torokhov } 207049a59dbSWei-Ning Huang 208049a59dbSWei-Ning Huang sysfs_bin_attr_init(&sec->bin_attr); 209049a59dbSWei-Ning Huang sec->bin_attr.attr.name = sec->raw_name; 210049a59dbSWei-Ning Huang sec->bin_attr.attr.mode = 0444; 211049a59dbSWei-Ning Huang sec->bin_attr.size = size; 212049a59dbSWei-Ning Huang sec->bin_attr.read = vpd_section_read; 213049a59dbSWei-Ning Huang sec->bin_attr.private = sec; 214049a59dbSWei-Ning Huang 2159920a33eSDmitry Torokhov err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr); 2169920a33eSDmitry Torokhov if (err) 2179920a33eSDmitry Torokhov goto err_free_raw_name; 218049a59dbSWei-Ning Huang 219049a59dbSWei-Ning Huang sec->kobj = kobject_create_and_add(name, vpd_kobj); 220049a59dbSWei-Ning Huang if (!sec->kobj) { 2219920a33eSDmitry Torokhov err = -EINVAL; 2229920a33eSDmitry Torokhov goto err_sysfs_remove; 223049a59dbSWei-Ning Huang } 224049a59dbSWei-Ning Huang 225049a59dbSWei-Ning Huang INIT_LIST_HEAD(&sec->attribs); 226049a59dbSWei-Ning Huang vpd_section_create_attribs(sec); 227049a59dbSWei-Ning Huang 228049a59dbSWei-Ning Huang sec->enabled = true; 229049a59dbSWei-Ning Huang 230049a59dbSWei-Ning Huang return 0; 231049a59dbSWei-Ning Huang 2329920a33eSDmitry Torokhov err_sysfs_remove: 233049a59dbSWei-Ning Huang sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); 2349920a33eSDmitry Torokhov err_free_raw_name: 235049a59dbSWei-Ning Huang kfree(sec->raw_name); 23675e5bae6SPan Bian err_memunmap: 23775e5bae6SPan Bian memunmap(sec->baseaddr); 2389920a33eSDmitry Torokhov return err; 239049a59dbSWei-Ning Huang } 240049a59dbSWei-Ning Huang 241049a59dbSWei-Ning Huang static int vpd_section_destroy(struct vpd_section *sec) 242049a59dbSWei-Ning Huang { 243049a59dbSWei-Ning Huang if (sec->enabled) { 244049a59dbSWei-Ning Huang vpd_section_attrib_destroy(sec); 2452ee1cc70SDmitry Torokhov kobject_put(sec->kobj); 246049a59dbSWei-Ning Huang sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr); 247049a59dbSWei-Ning Huang kfree(sec->raw_name); 24875e5bae6SPan Bian memunmap(sec->baseaddr); 249049a59dbSWei-Ning Huang } 250049a59dbSWei-Ning Huang 251049a59dbSWei-Ning Huang return 0; 252049a59dbSWei-Ning Huang } 253049a59dbSWei-Ning Huang 254049a59dbSWei-Ning Huang static int vpd_sections_init(phys_addr_t physaddr) 255049a59dbSWei-Ning Huang { 256049a59dbSWei-Ning Huang struct vpd_cbmem __iomem *temp; 257049a59dbSWei-Ning Huang struct vpd_cbmem header; 258049a59dbSWei-Ning Huang int ret = 0; 259049a59dbSWei-Ning Huang 260049a59dbSWei-Ning Huang temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB); 261049a59dbSWei-Ning Huang if (!temp) 262049a59dbSWei-Ning Huang return -ENOMEM; 263049a59dbSWei-Ning Huang 264049a59dbSWei-Ning Huang memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem)); 26575e5bae6SPan Bian memunmap(temp); 266049a59dbSWei-Ning Huang 267049a59dbSWei-Ning Huang if (header.magic != VPD_CBMEM_MAGIC) 268049a59dbSWei-Ning Huang return -ENODEV; 269049a59dbSWei-Ning Huang 270049a59dbSWei-Ning Huang if (header.ro_size) { 271049a59dbSWei-Ning Huang ret = vpd_section_init("ro", &ro_vpd, 272049a59dbSWei-Ning Huang physaddr + sizeof(struct vpd_cbmem), 273049a59dbSWei-Ning Huang header.ro_size); 274049a59dbSWei-Ning Huang if (ret) 275049a59dbSWei-Ning Huang return ret; 276049a59dbSWei-Ning Huang } 277049a59dbSWei-Ning Huang 278049a59dbSWei-Ning Huang if (header.rw_size) { 279049a59dbSWei-Ning Huang ret = vpd_section_init("rw", &rw_vpd, 280049a59dbSWei-Ning Huang physaddr + sizeof(struct vpd_cbmem) + 281049a59dbSWei-Ning Huang header.ro_size, header.rw_size); 28246505c80SDmitry Torokhov if (ret) 283049a59dbSWei-Ning Huang return ret; 284049a59dbSWei-Ning Huang } 285049a59dbSWei-Ning Huang 286049a59dbSWei-Ning Huang return 0; 287049a59dbSWei-Ning Huang } 288049a59dbSWei-Ning Huang 28946505c80SDmitry Torokhov static int vpd_probe(struct platform_device *pdev) 29046505c80SDmitry Torokhov { 29146505c80SDmitry Torokhov int ret; 29246505c80SDmitry Torokhov struct lb_cbmem_ref entry; 29346505c80SDmitry Torokhov 29446505c80SDmitry Torokhov ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry)); 29546505c80SDmitry Torokhov if (ret) 29646505c80SDmitry Torokhov return ret; 29746505c80SDmitry Torokhov 29846505c80SDmitry Torokhov return vpd_sections_init(entry.cbmem_addr); 29946505c80SDmitry Torokhov } 30046505c80SDmitry Torokhov 301*811d7e02SGuenter Roeck static int vpd_remove(struct platform_device *pdev) 302*811d7e02SGuenter Roeck { 303*811d7e02SGuenter Roeck vpd_section_destroy(&ro_vpd); 304*811d7e02SGuenter Roeck vpd_section_destroy(&rw_vpd); 305*811d7e02SGuenter Roeck 306*811d7e02SGuenter Roeck return 0; 307*811d7e02SGuenter Roeck } 308*811d7e02SGuenter Roeck 30946505c80SDmitry Torokhov static struct platform_driver vpd_driver = { 31046505c80SDmitry Torokhov .probe = vpd_probe, 311*811d7e02SGuenter Roeck .remove = vpd_remove, 31246505c80SDmitry Torokhov .driver = { 31346505c80SDmitry Torokhov .name = "vpd", 31446505c80SDmitry Torokhov }, 31546505c80SDmitry Torokhov }; 31646505c80SDmitry Torokhov 317049a59dbSWei-Ning Huang static int __init vpd_platform_init(void) 318049a59dbSWei-Ning Huang { 31946505c80SDmitry Torokhov struct platform_device *pdev; 32046505c80SDmitry Torokhov 32146505c80SDmitry Torokhov pdev = platform_device_register_simple("vpd", -1, NULL, 0); 32246505c80SDmitry Torokhov if (IS_ERR(pdev)) 32346505c80SDmitry Torokhov return PTR_ERR(pdev); 324049a59dbSWei-Ning Huang 325049a59dbSWei-Ning Huang vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); 326049a59dbSWei-Ning Huang if (!vpd_kobj) 327049a59dbSWei-Ning Huang return -ENOMEM; 328049a59dbSWei-Ning Huang 32946505c80SDmitry Torokhov platform_driver_register(&vpd_driver); 330049a59dbSWei-Ning Huang 331049a59dbSWei-Ning Huang return 0; 332049a59dbSWei-Ning Huang } 333049a59dbSWei-Ning Huang 334049a59dbSWei-Ning Huang static void __exit vpd_platform_exit(void) 335049a59dbSWei-Ning Huang { 3362ee1cc70SDmitry Torokhov kobject_put(vpd_kobj); 337049a59dbSWei-Ning Huang } 338049a59dbSWei-Ning Huang 339049a59dbSWei-Ning Huang module_init(vpd_platform_init); 340049a59dbSWei-Ning Huang module_exit(vpd_platform_exit); 341049a59dbSWei-Ning Huang 342049a59dbSWei-Ning Huang MODULE_AUTHOR("Google, Inc."); 343049a59dbSWei-Ning Huang MODULE_LICENSE("GPL"); 344