328 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * vpd.c4 *5 * Driver for exporting VPD content to sysfs.6 *7 * Copyright 2017 Google Inc.8 */9 10#include <linux/ctype.h>11#include <linux/init.h>12#include <linux/io.h>13#include <linux/kernel.h>14#include <linux/kobject.h>15#include <linux/list.h>16#include <linux/module.h>17#include <linux/of_address.h>18#include <linux/platform_device.h>19#include <linux/slab.h>20#include <linux/sysfs.h>21 22#include "coreboot_table.h"23#include "vpd_decode.h"24 25#define CB_TAG_VPD 0x2c26#define VPD_CBMEM_MAGIC 0x43524f5327 28static struct kobject *vpd_kobj;29 30struct vpd_cbmem {31 u32 magic;32 u32 version;33 u32 ro_size;34 u32 rw_size;35 u8 blob[];36};37 38struct vpd_section {39 bool enabled;40 const char *name;41 char *raw_name; /* the string name_raw */42 struct kobject *kobj; /* vpd/name directory */43 char *baseaddr;44 struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */45 struct list_head attribs; /* key/value in vpd_attrib_info list */46};47 48struct vpd_attrib_info {49 char *key;50 const char *value;51 struct bin_attribute bin_attr;52 struct list_head list;53};54 55static struct vpd_section ro_vpd;56static struct vpd_section rw_vpd;57 58static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,59 struct bin_attribute *bin_attr, char *buf,60 loff_t pos, size_t count)61{62 struct vpd_attrib_info *info = bin_attr->private;63 64 return memory_read_from_buffer(buf, count, &pos, info->value,65 info->bin_attr.size);66}67 68/*69 * vpd_section_check_key_name()70 *71 * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but72 * old firmware versions may have entries like "S/N" which are problematic when73 * exporting them as sysfs attributes. These keys present in old firmwares are74 * ignored.75 *76 * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.77 *78 * @key: The key name to check79 * @key_len: key name length80 */81static int vpd_section_check_key_name(const u8 *key, s32 key_len)82{83 int c;84 85 while (key_len-- > 0) {86 c = *key++;87 88 if (!isalnum(c) && c != '_')89 return VPD_FAIL;90 }91 92 return VPD_OK;93}94 95static int vpd_section_attrib_add(const u8 *key, u32 key_len,96 const u8 *value, u32 value_len,97 void *arg)98{99 int ret;100 struct vpd_section *sec = arg;101 struct vpd_attrib_info *info;102 103 /*104 * Return VPD_OK immediately to decode next entry if the current key105 * name contains invalid characters.106 */107 if (vpd_section_check_key_name(key, key_len) != VPD_OK)108 return VPD_OK;109 110 info = kzalloc(sizeof(*info), GFP_KERNEL);111 if (!info)112 return -ENOMEM;113 114 info->key = kstrndup(key, key_len, GFP_KERNEL);115 if (!info->key) {116 ret = -ENOMEM;117 goto free_info;118 }119 120 sysfs_bin_attr_init(&info->bin_attr);121 info->bin_attr.attr.name = info->key;122 info->bin_attr.attr.mode = 0444;123 info->bin_attr.size = value_len;124 info->bin_attr.read = vpd_attrib_read;125 info->bin_attr.private = info;126 127 info->value = value;128 129 INIT_LIST_HEAD(&info->list);130 131 ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);132 if (ret)133 goto free_info_key;134 135 list_add_tail(&info->list, &sec->attribs);136 return 0;137 138free_info_key:139 kfree(info->key);140free_info:141 kfree(info);142 143 return ret;144}145 146static void vpd_section_attrib_destroy(struct vpd_section *sec)147{148 struct vpd_attrib_info *info;149 struct vpd_attrib_info *temp;150 151 list_for_each_entry_safe(info, temp, &sec->attribs, list) {152 sysfs_remove_bin_file(sec->kobj, &info->bin_attr);153 kfree(info->key);154 kfree(info);155 }156}157 158static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,159 struct bin_attribute *bin_attr, char *buf,160 loff_t pos, size_t count)161{162 struct vpd_section *sec = bin_attr->private;163 164 return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,165 sec->bin_attr.size);166}167 168static int vpd_section_create_attribs(struct vpd_section *sec)169{170 s32 consumed;171 int ret;172 173 consumed = 0;174 do {175 ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,176 &consumed, vpd_section_attrib_add, sec);177 } while (ret == VPD_OK);178 179 return 0;180}181 182static int vpd_section_init(const char *name, struct vpd_section *sec,183 phys_addr_t physaddr, size_t size)184{185 int err;186 187 sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);188 if (!sec->baseaddr)189 return -ENOMEM;190 191 sec->name = name;192 193 /* We want to export the raw partition with name ${name}_raw */194 sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);195 if (!sec->raw_name) {196 err = -ENOMEM;197 goto err_memunmap;198 }199 200 sysfs_bin_attr_init(&sec->bin_attr);201 sec->bin_attr.attr.name = sec->raw_name;202 sec->bin_attr.attr.mode = 0444;203 sec->bin_attr.size = size;204 sec->bin_attr.read = vpd_section_read;205 sec->bin_attr.private = sec;206 207 err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);208 if (err)209 goto err_free_raw_name;210 211 sec->kobj = kobject_create_and_add(name, vpd_kobj);212 if (!sec->kobj) {213 err = -EINVAL;214 goto err_sysfs_remove;215 }216 217 INIT_LIST_HEAD(&sec->attribs);218 vpd_section_create_attribs(sec);219 220 sec->enabled = true;221 222 return 0;223 224err_sysfs_remove:225 sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);226err_free_raw_name:227 kfree(sec->raw_name);228err_memunmap:229 memunmap(sec->baseaddr);230 return err;231}232 233static int vpd_section_destroy(struct vpd_section *sec)234{235 if (sec->enabled) {236 vpd_section_attrib_destroy(sec);237 kobject_put(sec->kobj);238 sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);239 kfree(sec->raw_name);240 memunmap(sec->baseaddr);241 sec->enabled = false;242 }243 244 return 0;245}246 247static int vpd_sections_init(phys_addr_t physaddr)248{249 struct vpd_cbmem *temp;250 struct vpd_cbmem header;251 int ret = 0;252 253 temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);254 if (!temp)255 return -ENOMEM;256 257 memcpy(&header, temp, sizeof(struct vpd_cbmem));258 memunmap(temp);259 260 if (header.magic != VPD_CBMEM_MAGIC)261 return -ENODEV;262 263 if (header.ro_size) {264 ret = vpd_section_init("ro", &ro_vpd,265 physaddr + sizeof(struct vpd_cbmem),266 header.ro_size);267 if (ret)268 return ret;269 }270 271 if (header.rw_size) {272 ret = vpd_section_init("rw", &rw_vpd,273 physaddr + sizeof(struct vpd_cbmem) +274 header.ro_size, header.rw_size);275 if (ret) {276 vpd_section_destroy(&ro_vpd);277 return ret;278 }279 }280 281 return 0;282}283 284static int vpd_probe(struct coreboot_device *dev)285{286 int ret;287 288 vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);289 if (!vpd_kobj)290 return -ENOMEM;291 292 ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr);293 if (ret) {294 kobject_put(vpd_kobj);295 return ret;296 }297 298 return 0;299}300 301static void vpd_remove(struct coreboot_device *dev)302{303 vpd_section_destroy(&ro_vpd);304 vpd_section_destroy(&rw_vpd);305 306 kobject_put(vpd_kobj);307}308 309static const struct coreboot_device_id vpd_ids[] = {310 { .tag = CB_TAG_VPD },311 { /* sentinel */ }312};313MODULE_DEVICE_TABLE(coreboot, vpd_ids);314 315static struct coreboot_driver vpd_driver = {316 .probe = vpd_probe,317 .remove = vpd_remove,318 .drv = {319 .name = "vpd",320 },321 .id_table = vpd_ids,322};323module_coreboot_driver(vpd_driver);324 325MODULE_AUTHOR("Google, Inc.");326MODULE_DESCRIPTION("Driver for exporting Vital Product Data content to sysfs");327MODULE_LICENSE("GPL");328