linux/drivers/firmware/google/vpd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * vpd.c
   4 *
   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      0x2c
  26#define VPD_CBMEM_MAGIC 0x43524f53
  27
  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 but
  72 * old firmware versions may have entries like "S/N" which are problematic when
  73 * exporting them as sysfs attributes. These keys present in old firmwares are
  74 * ignored.
  75 *
  76 * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
  77 *
  78 * @key: The key name to check
  79 * @key_len: key name length
  80 */
  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 key
 105         * 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 int 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        return 0;
 309}
 310
 311static struct coreboot_driver vpd_driver = {
 312        .probe = vpd_probe,
 313        .remove = vpd_remove,
 314        .drv = {
 315                .name = "vpd",
 316        },
 317        .tag = CB_TAG_VPD,
 318};
 319module_coreboot_driver(vpd_driver);
 320
 321MODULE_AUTHOR("Google, Inc.");
 322MODULE_LICENSE("GPL");
 323