blob: 2eb46c4760e7dd0cbb52604340593e393609a0cd [file] [log] [blame]
/* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <uuid/uuid.h>
#include "mosys/alloc.h"
#include "mosys/callbacks.h"
#include "mosys/globals.h"
#include "mosys/log.h"
#include "mosys/platform.h"
#include "mosys/output.h"
#include "intf/mmio.h"
#include "lib/string.h"
#include "lib/vpd.h"
#include "lib/vpd_binary_blob.h"
/*
* These represent the memory-mapped address space which the VPD
* can be found in. That is, the VPD resides somewhere in the range
* vpd_rom_base to (vpd_rom_base + vpd_rom_len).
*/
unsigned int vpd_rom_base;
unsigned int vpd_rom_size;
/* Iterator used for table parsing */
struct vpd_iterator {
struct vpd_entry *entry; /* entry pointer */
struct vpd_header *header; /* current header */
uint8_t *data; /* table data */
uint8_t *current; /* pointer into data */
};
static struct vpd_iterator *vpd_itr = NULL;
/*
* vpd_parse_string_table - parse strings into table structure
*
* @ptr: pointer to start of strings
* @strings: array of VPD_MAX_STRINGS VPD_MAX_STRING_LENGTH-char
* strings to fill in.
*
* returns number of strings present in table, < 0 on error.
*/
int vpd_parse_string_table(char *ptr,
char strings[VPD_MAX_STRINGS][VPD_MAX_STRING_LENGTH])
{
int id, len, i;
if (ptr == NULL || strings == NULL)
return -1;
/* The first string is purposefully nulled out because these fields
* directly indexed by VPD string indexes (1-based). If a 0 is
* accidentaly used as an index make sure there is an empty string
* waiting. */
strings[0][0] = '\0';
for (id = 1; *ptr; ptr += len + 1) {
len = strlen(ptr);
/* Fill in string if there is room left. */
if (id < VPD_MAX_STRINGS) {
/* filter non-ascii characters */
for (i = 0; i < len &&
i < VPD_MAX_STRING_LENGTH - 1; i++) {
if (isprint(ptr[i]))
strings[id][i] = ptr[i];
else
strings[id][i] = '.';
}
strings[id][i] = '\0';
}
++id;
}
return id - 1;
}
/*
* vpd_string_table_len - find length of vpd string table
*
* @ptr: pointer to start of strings
*
* returns offset to end of string list (including terminating null byte).
* returns < 0 if ptr is invalid.
*/
int vpd_string_table_len(const char *ptr)
{
int len, tlen;
if (ptr == NULL)
return -1;
/* empty string list */
if (!*ptr)
return 2;
/* count each string length */
for (tlen = 1; ptr && *ptr; ptr += len) {
len = strlen(ptr) + 1;
tlen += len;
}
return tlen;
}
/*
* vpd_get_string - return string at index
*
* @ptr: pointer to start of strings
* @num: number of string to return (0-based)
*
* returns pointer to string
*/
char *vpd_get_string(const char *ptr, int num)
{
if (num < 0 || ptr == NULL)
return NULL;
for (; num && ptr && *ptr; num--)
ptr += strlen(ptr) + 1;
if (!ptr || !*ptr)
return NULL;
else
return (char *)ptr;
}
/*
* vpd_find_entry - locate entry pointer
*
* @intf: platform interface
* @entry: buffer to store entry pointer must be allocated by caller
* @baseaddr: address to begin search
* @len: length of region to search (in bytes)
* @offset: offset to store entry point structure address, if found
*
* returns 0 to indicate success
* returns <0 if not found
*/
int vpd_find_entry(struct platform_intf *intf,
struct vpd_entry *entry,
unsigned long int baseaddr,
unsigned long int len,
size_t *offset)
{
uint8_t csum;
uint8_t *data;
int i;
uint8_t vpd_magic[] = VPD_ENTRY_MAGIC;
data = mmio_map(intf, O_RDONLY, baseaddr, len);
if (data == NULL) {
lprintf(LOG_DEBUG, "Unable to map VPD entry buffer.\n");
return -1;
}
if (find_pattern(data, len,
&vpd_magic[0], 4, 16, offset) < 0) {
lprintf(LOG_DEBUG, "Unable to find VPD entry.\n");
mmio_unmap(intf, data, baseaddr, len);
return -1;
}
lprintf(LOG_DEBUG, "VPD Table Entry @ 0x%lx\n", baseaddr + *offset);
/* copy entry into user-provided buffer */
memset(entry, 0, sizeof(*entry));
memcpy(entry, data + *offset, sizeof(*entry));
/* verify entry pointer checksum */
for (csum = i = 0; i < entry->entry_length; i++)
csum += data[*offset + i];
mmio_unmap(intf, data, baseaddr, len);
if (csum != 0) {
lprintf(LOG_DEBUG, "Invalid VPD checksum: %02x\n", csum);
}
lprintf(LOG_DEBUG, "VPD Entry Version: %d.%d\n",
entry->major_ver, entry->minor_ver);
lprintf(LOG_DEBUG, "VPD Table Offset: 0x%08x\n",
entry->table_address);
lprintf(LOG_DEBUG, "VPD Table Length: %d\n",
entry->table_length);
lprintf(LOG_DEBUG, "VPD Table Count: %d\n",
entry->table_entry_count);
return 0;
}
/*
* vpd_itr_destroy - clean up iterator
*
* @intf: platform interface
*/
static void vpd_itr_destroy(void *arg)
{
struct platform_intf *intf = arg;
if (vpd_itr) {
/* clean up vpd table buffer */
if (vpd_itr->data) {
mmio_unmap(intf, vpd_itr->data,
vpd_itr->entry->table_address,
vpd_itr->entry->table_length);
}
/* clean up vpd entry */
if (vpd_itr->entry)
free(vpd_itr->entry);
/* clean up iterator */
free(vpd_itr);
vpd_itr = NULL;
}
}
/*
* vpd_itr_setup - setup iterator for vpd searching
*
* @intf: platform interface
* @baseaddr: base address to start searching in
* @len: length of region to search (in bytes)
*
* returns 0 to indicate successful setup or reset to setup
* returns <0 to indicate failure
*/
static int vpd_itr_setup(struct platform_intf *intf,
unsigned int baseaddr, unsigned int len)
{
unsigned int addr, remaining;
size_t offset;
int entry_found = 0;
/* already setup? */
if (vpd_itr) {
/* reset to start of tables */
vpd_itr->current = vpd_itr->data;
vpd_itr->header =
(struct vpd_header *)vpd_itr->current;
return 0;
}
/* setup iterator */
vpd_itr = mosys_malloc(sizeof(*vpd_itr));
memset(vpd_itr, 0, sizeof(*vpd_itr));
vpd_itr->entry = mosys_malloc(sizeof(*vpd_itr->entry));
memset(vpd_itr->entry, 0, sizeof(*vpd_itr->entry));
addr = baseaddr;
remaining = len;
while (!entry_found) {
uint32_t x;
/* search for entry pointer with a valid table */
if (vpd_find_entry(intf, vpd_itr->entry,
addr, remaining, &offset) < 0) {
vpd_itr_destroy(intf);
return -1;
}
x = vpd_itr->entry->table_address +
vpd_itr->entry->table_length;
/* if a sanity check fails, continue to search for a
* valid entry point */
if (x < vpd_itr->entry->table_address) {
lprintf(LOG_DEBUG, "Table address/length overrun\n");
} else if (vpd_itr->entry->table_length == 0) {
lprintf(LOG_DEBUG, "Zero table-length detected\n");
} else {
entry_found = 1;
}
addr = baseaddr + offset + 16;
remaining = baseaddr + len - addr;
}
/* mmap in entire vpd area */
vpd_itr->data = mmio_map(intf, O_RDONLY,
baseaddr + vpd_itr->entry->table_address,
vpd_itr->entry->table_length);
if (vpd_itr->data == NULL) {
lprintf(LOG_ERR, "Unable to find VPD tables at 0x%08x\n",
vpd_itr->entry->table_address);
vpd_itr_destroy(intf);
return -1;
}
if (mosys_get_verbosity() == LOG_DEBUG)
print_buffer(vpd_itr->data, vpd_itr->entry->table_length);
/* start pointer at table start */
vpd_itr->current = vpd_itr->data;
vpd_itr->header = (struct vpd_header *)vpd_itr->current;
/* make sure we get torn down at exit time. */
add_destroy_callback(vpd_itr_destroy, intf);
return 0;
}
/*
* vpd_itr_next - find next table from current iterator
*
* @intf platform interface
*
* returns 0 if found
* returns 1 if search wrapped to start
* returns <0 if iterator not setup or invalid
*/
static int vpd_itr_next(struct platform_intf *intf)
{
int rc = 0;
if (!vpd_itr || !vpd_itr->current || !vpd_itr->header)
return -1;
/* adjust pointer to end of table data + strings */
vpd_itr->current += vpd_itr->header->length +
vpd_string_table_len((char *)vpd_itr->current +
vpd_itr->header->length);
/* check for overflow */
if (vpd_itr->current >= (vpd_itr->data +
vpd_itr->entry->table_length - 1)) {
/* start pointer over */
lprintf(LOG_DEBUG, "vpd_itr_next: search wrapped\n");
vpd_itr->current = vpd_itr->data;
rc = 1;
}
/* adjust header pointer */
vpd_itr->header = (struct vpd_header *)vpd_itr->current;
return rc;
}
/*
* vpd_find_table_raw - locate table and store in buffer
*
* @intf: platform interface
* @type: vpd table type
* @instance: vpd table instance
* @baseaddr: base address to start searching in
* @len: length of region to search (in bytes)
*
* returns 0 if found
* returns <0 if not found
*/
static int vpd_find_table_raw(struct platform_intf *intf,
enum vpd_types type, int instance,
unsigned int baseaddr, unsigned int len)
{
int ret = -1;
/* start iterator */
if (vpd_itr_setup(intf, baseaddr, len) >= 0) {
do {
/* check for correct type */
if (vpd_itr->header->type != type)
continue;
/* check for correct instance */
if (instance-- > 0)
continue;
if (mosys_get_verbosity() == LOG_DEBUG)
print_buffer(vpd_itr->current,
vpd_itr->header->length);
/* found */
ret = 0;
break;
} while (vpd_itr_next(intf) == 0);
}
return ret;
}
/*
* vpd_find_table - locate specified VPD table in memory
*
* @intf: platform interface
* @type: vpd table type to locate
* @instance: what instance to retrieve (0-based)
* @table: OUTPUT buffer to store table data
* @baseaddr: base address to start searching in
* @len: length of region to search (in bytes)
*
* returns 0 to indicate success
* returns <0 to indicate failure
*/
int vpd_find_table(struct platform_intf *intf, enum vpd_types type,
int instance, struct vpd_table *table,
unsigned int baseaddr, unsigned int len)
{
if (!table)
return -1;
/* get the table as raw buffer */
if (vpd_find_table_raw(intf, type, instance, baseaddr, len) < 0) {
lprintf(LOG_DEBUG, "Unable to locate table %d:%d\n",
type, instance);
return -1;
}
/* copy header first */
memset(table, 0, sizeof(*table));
memcpy(&table->header, vpd_itr->header, sizeof(table->header));
/* then table data */
memcpy(&table->data.data, vpd_itr->current + sizeof(table->header),
vpd_itr->header->length - sizeof(table->header));
/* finally parse table strings */
vpd_parse_string_table((char *)vpd_itr->current +
vpd_itr->header->length, table->string);
return 0;
}
/*
* vpd_find_string - locate specific string in VPD table
*
* @intf: platform interface
* @type: vpd table type
* @number: string number to retrieve
* @baseaddr: base address to start searching in
* @len: length of region to search (in bytes)
*
* returns allocated buffer containing null-terminated string
* !! caller is expected to free returned buffer !!
* returns NULL to indicate error
*/
char *vpd_find_string(struct platform_intf *intf,
enum vpd_types type, int number,
unsigned int baseaddr, unsigned int len)
{
char *sptr;
if (type > VPD_TYPE_END)
return NULL;
/* get instance 0 of the table */
if (vpd_find_table_raw(intf, type, 0, baseaddr, len) < 0) {
lprintf(LOG_ERR, "Unable to locate table %d\n", type);
return NULL;
}
/* lookup string location in table */
sptr = vpd_get_string((char *)vpd_itr->current +
vpd_itr->header->length,
number);
if (!sptr) {
lprintf(LOG_DEBUG, "String %d not found in table %d\n",
number, type);
return NULL;
}
/* return allocated copy of string */
return mosys_strdup(sptr);
}
struct blob_handler blob_handlers[] = {
{ .uuid = "b9468091-ccd2-4b8b-8300-8cd8336a14f6",
.print = print_agz_blob_v3 },
{ .uuid = "0eea3385-602a-4f6d-b593-6118dda238b2",
.print = print_agz_blob_v5 },
{ .uuid = "08f8a2b0-15fd-4cfd-968f-8378f2c508ce",
.print = print_google_blob_v1_1 },
{ .uuid = "0a7c23d3-8a27-4252-99bf-7868a2e26b61",
.get = get_google_blob_v2_0,
.print = print_google_blob_v2_0 },
{ NULL },
};
/* This function will allocate memory for buffer which the caller must free */
extern int vpd_get_blob(struct platform_intf *intf,
struct vpd_table_binary_blob_pointer *bbp,
uint8_t **buf)
{
struct blob_handler *handler;
char s[37];
int ret = 0, max_size, actual_size;
uint32_t offset;
uint8_t *tmp = NULL;
/* This is the trival case -- Blob size and offset are given via the
* VPD structure table, so finding and copying it is trivial. */
if (bbp->size) {
offset = vpd_rom_base + bbp->offset;
actual_size = bbp->size;
*buf = mosys_malloc(actual_size);
if (mmio_read(intf, offset, actual_size, (void *)*buf) < 0) {
lprintf(LOG_DEBUG, "%s: cannot map %x bytes at offset"
" %d\n", __func__,
offset, actual_size);
ret = -1;
goto vpd_get_blob_exit;
} else {
return actual_size;
}
}
/* If the blob is variable-length, things get more complicated... */
uuid_unparse(bbp->uuid, s);
for (handler = &blob_handlers[0]; handler && handler->uuid; handler++) {
if (!strcmp(handler->uuid, s)) {
lprintf(LOG_DEBUG, "found matching uuid: %s\n", s);
break;
}
}
if (!handler->get) {
lprintf(LOG_DEBUG, "%s: no suitable handler found\n", __func__);
ret = -1;
goto vpd_get_blob_exit;
}
/*
* Since we don't know the size in advance, we'll mmap the
* remainder of the ROM.
* FIXME: This is sub-optimal... Can we do better?
*/
offset = vpd_rom_base + bbp->offset;
max_size = vpd_rom_size - bbp->offset;
tmp = mosys_malloc(max_size);
if (mmio_read(intf, offset, max_size, tmp) < 0) {
lprintf(LOG_DEBUG, "%s: cannot map %x bytes at offset"
" %d\n", __func__,
offset, max_size);
ret = -1;
goto vpd_get_blob_exit;
}
if ((actual_size = handler->get(buf, tmp, max_size)) < 0) {
ret = -1;
goto vpd_get_blob_exit;
}
/* if all is well, we'll return the size of the binary blob */
ret = actual_size;
vpd_get_blob_exit:
free(tmp);
return ret;
}
extern int vpd_print_blob(struct platform_intf *intf,
struct kv_pair *kv, struct vpd_table *table)
{
struct blob_handler *handler;
char s[37];
int rc = 0;
uint32_t size;
uint8_t *blob;
uint32_t offset;
uuid_unparse(table->data.blob.uuid, s);
for (handler = &blob_handlers[0]; handler && handler->uuid; handler++) {
if (!strcmp(handler->uuid, s)) {
lprintf(LOG_DEBUG, "%s: found matching uuid: %s\n",
__func__, s);
break;
}
}
if (!handler || !handler->print) {
lprintf(LOG_DEBUG, "%s: no suitable handler found\n", __func__);
return -1;
}
/*
* Structures with a size in the table will be treated as fixed-
* length. Tables without will be treated as variable length.
*/
if (table->data.blob.size) {
offset = vpd_rom_base+table->data.blob.offset;
size = table->data.blob.size;
blob = mosys_malloc(size);
} else {
/*
* Since we don't know the size in advance, we'll just
* mmap the remainder of the ROM.
* FIXME: This is sub-optimal... Can we do better?
*/
offset = vpd_rom_base + table->data.blob.offset;
size = vpd_rom_size - table->data.blob.offset;
blob = mosys_malloc(size);
}
if (mmio_read(intf, offset, size, blob) < 0) {
lprintf(LOG_DEBUG, "%s: cannot map %x bytes at offset %x\n",
__func__, offset, size);
rc = -1;
} else {
lprintf(LOG_DEBUG, "%s: blob offset: 0x%x, blob size: %u\n",
__func__, offset, size);
rc = handler->print(blob, table->data.blob.size, kv);
}
free(blob);
return rc;
}