| /* |
| * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <assert.h> |
| #include <console/console.h> |
| #include <cbmem.h> |
| #include <fmap.h> |
| #include <string.h> |
| #include <timestamp.h> |
| |
| #include "vpd.h" |
| #include "vpd_decode.h" |
| #include "vpd_tables.h" |
| |
| struct vpd_gets_arg { |
| const uint8_t *key; |
| const uint8_t *value; |
| int32_t key_len, value_len; |
| int matched; |
| }; |
| |
| struct vpd_blob vpd_blob; |
| |
| /* |
| * returns the size of data in a VPD 2.0 formatted fmap region, or 0. |
| * Also sets *base as the region's base address. |
| */ |
| static int32_t get_vpd_size(const char *fmap_name, int32_t *base) |
| { |
| struct google_vpd_info info; |
| struct region_device vpd; |
| int32_t size; |
| |
| if (fmap_locate_area_as_rdev(fmap_name, &vpd)) { |
| printk(BIOS_ERR, "%s: No %s FMAP section.\n", __func__, |
| fmap_name); |
| return 0; |
| } |
| |
| size = region_device_sz(&vpd); |
| |
| if ((size < GOOGLE_VPD_2_0_OFFSET + sizeof(info)) || |
| rdev_chain(&vpd, &vpd, GOOGLE_VPD_2_0_OFFSET, |
| size - GOOGLE_VPD_2_0_OFFSET)) { |
| printk(BIOS_ERR, "%s: Too small (%d) for Google VPD 2.0.\n", |
| __func__, size); |
| return 0; |
| } |
| |
| /* Try if we can find a google_vpd_info, otherwise read whole VPD. */ |
| if (rdev_readat(&vpd, &info, *base, sizeof(info)) != sizeof(info)) { |
| printk(BIOS_ERR, "ERROR: Failed to read %s header.\n", |
| fmap_name); |
| return 0; |
| } |
| |
| if (memcmp(info.header.magic, VPD_INFO_MAGIC, sizeof(info.header.magic)) |
| == 0 && size >= info.size + sizeof(info)) { |
| *base += sizeof(info); |
| size = info.size; |
| } else if (info.header.tlv.type == VPD_TYPE_TERMINATOR || |
| info.header.tlv.type == VPD_TYPE_IMPLICIT_TERMINATOR) { |
| printk(BIOS_WARNING, "WARNING: %s is uninitialized or empty.\n", |
| fmap_name); |
| size = 0; |
| } else { |
| size -= GOOGLE_VPD_2_0_OFFSET; |
| } |
| |
| return size; |
| } |
| |
| static void vpd_get_blob(void) |
| { |
| int32_t ro_vpd_base = 0; |
| int32_t rw_vpd_base = 0; |
| int32_t ro_vpd_size = get_vpd_size("RO_VPD", &ro_vpd_base); |
| int32_t rw_vpd_size = get_vpd_size("RW_VPD", &rw_vpd_base); |
| |
| /* Return if no VPD at all */ |
| if (ro_vpd_size == 0 && rw_vpd_size == 0) |
| return; |
| |
| vpd_blob.ro_base = NULL; |
| vpd_blob.ro_size = 0; |
| vpd_blob.rw_base = NULL; |
| vpd_blob.rw_size = 0; |
| |
| struct region_device vpd; |
| |
| if (ro_vpd_size) { |
| if (fmap_locate_area_as_rdev("RO_VPD", &vpd)) { |
| /* shouldn't happen, but let's be extra defensive */ |
| printk(BIOS_ERR, "%s: No RO_VPD FMAP section.\n", |
| __func__); |
| return; |
| } |
| rdev_chain(&vpd, &vpd, GOOGLE_VPD_2_0_OFFSET, |
| region_device_sz(&vpd) - GOOGLE_VPD_2_0_OFFSET); |
| vpd_blob.ro_base = (uint8_t *)(rdev_mmap_full(&vpd) + |
| sizeof(struct google_vpd_info)); |
| vpd_blob.ro_size = ro_vpd_size; |
| } |
| if (rw_vpd_size) { |
| if (fmap_locate_area_as_rdev("RW_VPD", &vpd)) { |
| /* shouldn't happen, but let's be extra defensive */ |
| printk(BIOS_ERR, "%s: No RW_VPD FMAP section.\n", |
| __func__); |
| return; |
| } |
| rdev_chain(&vpd, &vpd, GOOGLE_VPD_2_0_OFFSET, |
| region_device_sz(&vpd) - GOOGLE_VPD_2_0_OFFSET); |
| vpd_blob.rw_base = (uint8_t *)(rdev_mmap_full(&vpd) + |
| sizeof(struct google_vpd_info)); |
| vpd_blob.rw_size = rw_vpd_size; |
| } |
| vpd_blob.initialized = true; |
| } |
| |
| const struct vpd_blob *vpd_load_blob(void) |
| { |
| if (vpd_blob.initialized == false) |
| vpd_get_blob(); |
| |
| return &vpd_blob; |
| } |
| |
| static int vpd_gets_callback(const uint8_t *key, uint32_t key_len, |
| const uint8_t *value, uint32_t value_len, |
| void *arg) |
| { |
| struct vpd_gets_arg *result = (struct vpd_gets_arg *)arg; |
| if (key_len != result->key_len || |
| memcmp(key, result->key, key_len) != 0) |
| /* Returns VPD_DECODE_OK to continue parsing. */ |
| return VPD_DECODE_OK; |
| |
| result->matched = 1; |
| result->value = value; |
| result->value_len = value_len; |
| /* Returns VPD_DECODE_FAIL to stop parsing. */ |
| return VPD_DECODE_FAIL; |
| } |
| |
| const void *vpd_find(const char *key, int *size, enum vpd_region region) |
| { |
| struct vpd_blob blob = {0}; |
| |
| vpd_get_buffers(&blob); |
| if (blob.ro_size == 0 && blob.rw_size == 0) |
| return NULL; |
| |
| struct vpd_gets_arg arg = {0}; |
| uint32_t consumed = 0; |
| |
| arg.key = (const uint8_t *)key; |
| arg.key_len = strlen(key); |
| |
| if ((region == VPD_ANY || region == VPD_RO) && blob.ro_size != 0) { |
| while (vpd_decode_string(blob.ro_size, blob.ro_base, |
| &consumed, vpd_gets_callback, &arg) == VPD_DECODE_OK) { |
| /* Iterate until found or no more entries. */ |
| } |
| } |
| |
| if ((!arg.matched && region != VPD_RO) && blob.rw_size != 0) { |
| while (vpd_decode_string(blob.rw_size, blob.rw_base, |
| &consumed, vpd_gets_callback, &arg) == VPD_DECODE_OK) { |
| /* Iterate until found or no more entries. */ |
| } |
| } |
| |
| if (!arg.matched) |
| return NULL; |
| |
| *size = arg.value_len; |
| return arg.value; |
| } |
| |
| char *vpd_gets(const char *key, char *buffer, int size, enum vpd_region region) |
| { |
| const void *string_address; |
| int string_size; |
| |
| string_address = vpd_find(key, &string_size, region); |
| |
| if (!string_address) |
| return NULL; |
| |
| assert(size > 0); |
| int copy_size = MIN(size - 1, string_size); |
| memcpy(buffer, string_address, copy_size); |
| buffer[copy_size] = '\0'; |
| return buffer; |
| } |
| |
| /* |
| * Find value of boolean type vpd key. |
| * |
| * During the process, necessary checking is done, such as making |
| * sure the value length is 1, and value is either '1' or '0'. |
| */ |
| bool vpd_get_bool(const char *key, enum vpd_region region, uint8_t *val) |
| { |
| int size; |
| const char *value; |
| |
| value = vpd_find(key, &size, region); |
| if (!value) { |
| printk(BIOS_CRIT, "problem returning from vpd_find.\n"); |
| return false; |
| } |
| |
| if (size != 1) |
| return false; |
| |
| /* Make sure the value is either '1' or '0' */ |
| if (*value == '1') { |
| *val = 1; |
| return true; |
| } else if (*value == '0') { |
| *val = 0; |
| return true; |
| } else |
| return false; |
| } |