| /* |
| * Copyright (c) 2012 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 <ctype.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <uuid/uuid.h> |
| #include "lib/flashrom.h" |
| #include "lib/fmap.h" |
| #include "lib/lib_vpd.h" |
| #include "lib/lib_smbios.h" |
| #include "lib/vpd.h" |
| #include "lib/vpd_tables.h" |
| |
| /* The buffer length. Right now the VPD partition size on flash is 128KB. */ |
| #define BUF_LEN (128 * 1024) |
| |
| /* The comment shown in the begin of --sh output */ |
| #define SH_COMMENT \ |
| "#\n" \ |
| "# Prepend 'vpd -O' before this text to always reset VPD content.\n" \ |
| "# Append more -s at end to set additional key/values.\n" \ |
| "# Or an empty line followed by other commands.\n" \ |
| "#\n" |
| |
| /* Forward reference(s) */ |
| static uint8_t *readFileContent(const char* filename, uint32_t *filesize); |
| |
| /* Linked list to track temporary files |
| */ |
| struct TempfileNode { |
| char *filename; |
| struct TempfileNode *next; |
| } *tempfile_list = NULL; |
| |
| |
| enum FileFlag { |
| HAS_SPD = (1 << 0), |
| HAS_VPD_2_0 = (1 << 1), |
| HAS_VPD_1_2 = (1 << 2), |
| /* TODO(yjlou): refine those variables in main() to here: |
| * write_back_to_flash, overwrite_it, modified. */ |
| } file_flag = 0; |
| |
| |
| /* 2 containers: |
| * file: stores decoded pairs from file. |
| * argument: stores parsed pairs from command arguments. |
| */ |
| struct PairContainer file; |
| struct PairContainer set_argument; |
| struct PairContainer del_argument; |
| |
| int export_type = VPD_EXPORT_KEY_VALUE; |
| |
| /* The current padding length value. |
| * Default: VPD_AS_LONG_AS |
| */ |
| int pad_value_len = VPD_AS_LONG_AS; |
| |
| /* The output buffer */ |
| uint8_t buf[BUF_LEN]; |
| int buf_len = 0; |
| int max_buf_len = sizeof(buf); |
| |
| /* The EPS base address used to fill the EPS table entry. |
| * If the VPD partition can be found in fmap, this points to the starting |
| * offset of VPD partition. If not found, this is used to be the base address |
| * to increase SPD and VPD 2.0 offset fields. |
| * |
| * User can overwrite this by -E argument. |
| */ |
| #define UNKNOWN_EPS_BASE ((uint32_t)-1) |
| uint32_t eps_base = UNKNOWN_EPS_BASE; |
| int eps_base_force_specified = 0; /* a bool value to indicate if -E argument |
| * is given. */ |
| |
| /* the fmap name of VPD. */ |
| char fmap_vpd_area_name[FMAP_STRLEN] = "RO_VPD"; |
| |
| /* If found_vpd, replace the VPD partition when saveFile(). |
| * If not found, always create new file when saveFlie(). */ |
| int found_vpd = 0; |
| |
| /* The VPD partition offset and size in buf[]. The whole partition includes: |
| * |
| * SMBIOS EPS |
| * SMBIOS tables[] |
| * SPD |
| * VPD 2.0 data |
| * |
| */ |
| uint32_t vpd_offset = 0, vpd_size; /* The whole partition */ |
| /* Below offset are related to vpd_offset and assume positive. |
| * Those are used in saveFile() to write back data. */ |
| uint32_t eps_offset = 0; /* EPS's starting address. Tables[] is following. */ |
| uint32_t spd_offset = GOOGLE_SPD_OFFSET; /* SPD address .*/ |
| off_t vpd_2_0_offset = GOOGLE_VPD_2_0_OFFSET; /* VPD 2.0 data address. */ |
| |
| /* This points to the SPD data if it is availiable when loadFile(). |
| * The memory is allocated in loadFile(), will be used in saveFile(), |
| * and freed at end of main(). */ |
| uint8_t *spd_data = NULL; |
| int32_t spd_len = 256; /* max value for DDR3 */ |
| |
| /* Creates a temporary file and return the filename, or NULL for any failure. |
| */ |
| const char *myMkTemp() { |
| char tmp_file[] = "/tmp/vpd.flashrom.XXXXXX"; |
| struct TempfileNode *node; |
| int fd; |
| |
| fd = mkstemp(tmp_file); |
| if (fd < 0) { |
| fprintf(stderr, "mkstemp(%s) failed\n", tmp_file); |
| return NULL; |
| } |
| |
| close(fd); |
| node = (struct TempfileNode*)malloc(sizeof(struct TempfileNode)); |
| assert(node); |
| node->next = tempfile_list; |
| node->filename = strdup(tmp_file); |
| assert(node->filename); |
| tempfile_list = node; |
| |
| return node->filename; |
| } |
| |
| |
| /* Erases all files created by myMkTemp |
| */ |
| void cleanTempFiles() { |
| struct TempfileNode *node; |
| |
| while (tempfile_list) { |
| node = tempfile_list; |
| tempfile_list = node->next; |
| if (unlink(node->filename) < 0) { |
| fprintf(stderr, "warning: failed removing temporary file: %s\n", |
| node->filename); |
| } |
| free(node->filename); |
| free(node); |
| } |
| } |
| |
| |
| /* Given the offset of blob block (related to the first byte of EPS) and |
| * the size of blob, the is function generates an SMBIOS ESP. |
| */ |
| vpd_err_t buildEpsAndTables( |
| const int size_blob, |
| const int max_buf_len, |
| unsigned char *buf, |
| int *generated) { |
| struct vpd_entry *eps; |
| unsigned char *table = NULL; /* the structure table */ |
| int table_len = 0; |
| int num_structures = 0; |
| vpd_err_t retval = VPD_OK; |
| |
| assert(buf); |
| assert(generated); |
| assert(eps_base != UNKNOWN_EPS_BASE); |
| |
| buf += *generated; |
| |
| /* Generate type 241 - SPD data */ |
| table_len = vpd_append_type241(0, &table, table_len, |
| GOOGLE_SPD_UUID, |
| eps_base + GOOGLE_SPD_OFFSET, |
| spd_len, /* Max length for DDR3 */ |
| GOOGLE_SPD_VENDOR, |
| GOOGLE_SPD_DESCRIPTION, |
| GOOGLE_SPD_VARIANT); |
| if (table_len < 0) { |
| retval = VPD_FAIL; |
| goto error_1; |
| } |
| num_structures++; |
| |
| /* |
| * TODO(hungte) Once most systems have been updated to support VPD_INFO |
| * record, we can remove the +sizeof(google_vpd_info) hack. |
| */ |
| |
| /* Generate type 241 - VPD 2.0 */ |
| table_len = vpd_append_type241(1, &table, table_len, |
| GOOGLE_VPD_2_0_UUID, |
| (eps_base + GOOGLE_VPD_2_0_OFFSET + |
| sizeof(struct google_vpd_info)), |
| size_blob, |
| GOOGLE_VPD_2_0_VENDOR, |
| GOOGLE_VPD_2_0_DESCRIPTION, |
| GOOGLE_VPD_2_0_VARIANT); |
| if (table_len < 0) { |
| retval = VPD_FAIL; |
| goto error_1; |
| } |
| num_structures++; |
| |
| /* Generate type 127 */ |
| table_len = vpd_append_type127(2, &table, table_len); |
| if (table_len < 0) { |
| retval = VPD_FAIL; |
| goto error_1; |
| } |
| num_structures++; |
| |
| /* Generate EPS */ |
| eps = vpd_create_eps(table_len, num_structures, eps_base); |
| if ((*generated + eps->entry_length) > max_buf_len) { |
| retval = VPD_FAIL; |
| goto error_2; |
| } |
| |
| /* Copy EPS back to buf */ |
| memcpy(buf, eps, eps->entry_length); |
| buf += eps->entry_length; |
| *generated += eps->entry_length; |
| |
| /* Copy tables back to buf */ |
| if ((*generated + table_len) > max_buf_len) { |
| retval = VPD_FAIL; |
| goto error_2; |
| } |
| memcpy(buf, table, table_len); |
| buf += table_len; |
| *generated += table_len; |
| |
| error_2: |
| free(eps); |
| error_1: |
| free(table); |
| |
| return retval; |
| } |
| |
| static int isbase64(uint8_t c) |
| { |
| return isalnum(c) || (c == '+') || (c == '/') || (c == '='); |
| } |
| |
| static uint8_t *read_string_from_file(const char *file_name) |
| { |
| |
| uint32_t i, j, file_size; |
| uint8_t *file_buffer; |
| |
| file_buffer = readFileContent(file_name, &file_size); |
| |
| if (!file_buffer) |
| return NULL; /* The error has been reported already. */ |
| |
| /* |
| * Is the contents a proper base64 blob? Verify it and drop EOL characters |
| * along the way, this will help when displaying the contents. |
| */ |
| for (i = 0, j = 0; i < file_size; i++) { |
| uint8_t c = file_buffer[i]; |
| |
| if ((c == 0xa) || (c == 0xd)) |
| continue; /* Skip EOL characters */ |
| |
| if (!isbase64(c)) { |
| fprintf(stderr, "[ERROR] file %s is not in base64 format (%c at %d)\n", |
| file_name, c, i); |
| free(file_buffer); |
| return NULL; |
| } |
| file_buffer[j++] = c; |
| } |
| file_buffer[j] = '\0'; |
| |
| return (uint8_t *)file_buffer; |
| } |
| |
| |
| /* |
| * Given a key=value string, this function parses it and adds to arugument |
| * pair container. The 'value' can be stored in a base64 format file, in this |
| * case the value field is the file name. |
| */ |
| vpd_err_t parseString(const uint8_t *string, int read_from_file) { |
| uint8_t *key; |
| uint8_t *value; |
| uint8_t *file_contents = NULL; |
| vpd_err_t retval = VPD_OK; |
| |
| key = (uint8_t*)strdup((char*)string); |
| if (!key || key[0] == '\0' || key[0] == '=') { |
| if (key) free(key); |
| return VPD_ERR_SYNTAX; |
| } |
| |
| /* |
| * Goes through the key string, and stops at the first '='. |
| * If '=' is not found, the whole string is the key and |
| * the value points to the end of string ('\0'). |
| */ |
| for (value = key; *value && *value != '='; value++); |
| if (*value == '=') { |
| *(value++) = '\0'; |
| |
| if (read_from_file) { |
| /* 'value' in fact is a file name */ |
| file_contents = read_string_from_file((const char *)value); |
| if (!file_contents) { |
| free(key); |
| return VPD_ERR_SYNTAX; |
| } |
| value = file_contents; |
| } |
| } |
| |
| setString(&set_argument, key, value, pad_value_len); |
| |
| free(key); |
| if (file_contents) |
| free(file_contents); |
| |
| return retval; |
| } |
| |
| |
| /* Given an address, compare if it is SMBIOS signature ("_SM_"). */ |
| int isEps(const void* ptr) { |
| return !memcmp(VPD_ENTRY_MAGIC, ptr, sizeof(VPD_ENTRY_MAGIC) - 1); |
| /* TODO(yjlou): need more EPS sanity checks here. */ |
| } |
| |
| |
| /* There are two possible file content appearng here: |
| * 1. a full and complete BIOS file |
| * 2. a full but only VPD partition area is valid. (no fmap) |
| * 3. a full BIOS, but VPD partition is blank. |
| * |
| * The first case is easy. Just lookup the fmap and find out the VPD partition. |
| * The second is harder. We try to search the SMBIOS signature (since others |
| * are blank). For the third, we just return and leave caller to read full |
| * content, including fmap info. |
| * |
| * If found, vpd_offset and vpd_size are updated. |
| */ |
| vpd_err_t findVpdPartition(const uint8_t* read_buf, const uint32_t file_size, |
| uint32_t* vpd_offset, uint32_t* vpd_size) { |
| off_t sig_offset; |
| struct fmap *fmap; |
| int i; |
| |
| assert(read_buf); |
| assert(vpd_offset); |
| assert(vpd_size); |
| |
| /* scan the file and find out the VPD partition. */ |
| sig_offset = fmapFind(read_buf, file_size); |
| if (-1 != sig_offset) { |
| /* FMAP signature is found, try to search the partition name in table. */ |
| fmap = (struct fmap *)&read_buf[sig_offset]; |
| for(i = 0; i < fmap->nareas; i++) { |
| fmapNormalizeAreaName(fmap->areas[i].name); |
| } |
| |
| if (FMAP_OK == fmapGetArea(fmap_vpd_area_name, fmap, |
| vpd_offset, vpd_size)) { |
| found_vpd = 1; /* Mark found here then saveFile() knows where to |
| * write back (vpd_offset, vpd_size). */ |
| return VPD_OK; |
| } else { |
| fprintf(stderr, "[ERROR] The VPD partition [%s] is not found.\n", |
| fmap_vpd_area_name); |
| return VPD_ERR_NOT_FOUND; |
| } |
| } |
| |
| /* The signature must be aligned to 16-byte. */ |
| for (i = 0; i < file_size; i += 16) { |
| if (isEps(&read_buf[i])) { |
| found_vpd = 1; /* Mark found here then saveFile() knows where to |
| * write back (vpd_offset, vpd_size). */ |
| *vpd_offset = i; |
| /* FIXME: We don't know the VPD partition size in this case. |
| * However, return 4K should be safe enough now. |
| * In the long term, this code block will be obscured. */ |
| *vpd_size = 4096; |
| return VPD_OK; |
| } |
| } |
| return VPD_ERR_NOT_FOUND; |
| } |
| |
| |
| /* Load file content into memory. |
| * Returns: NULL if file opens error or read error. |
| * Others, pointer to the memory. The filesize is also returned. |
| * |
| * Note: it's caller's responsbility to free the memory. |
| */ |
| static uint8_t *readFileContent(const char* filename, uint32_t *filesize) { |
| FILE *fp; |
| uint8_t *read_buf; |
| |
| assert(filename); |
| assert(filesize); |
| |
| if (!(fp = fopen(filename, "r"))) { |
| return NULL; |
| } |
| /* get file size */ |
| fseek(fp, 0, SEEK_END); |
| *filesize = ftell(fp); |
| |
| /* read file content */ |
| fseek(fp, 0, SEEK_SET); |
| read_buf = malloc(*filesize + 1); /* Might need room for a \0. */ |
| assert(read_buf); |
| if (*filesize != fread(read_buf, 1, *filesize, fp)) { |
| fprintf(stderr, "[ERROR] Reading file [%s] failed.\n", filename); |
| return NULL; |
| } |
| fclose(fp); |
| |
| return read_buf; |
| } |
| |
| |
| vpd_err_t getVpdPartitionFromFullBios(uint32_t* offset, uint32_t* size) { |
| const char *filename; |
| uint8_t *buf; |
| uint32_t buf_size; |
| vpd_err_t retval; |
| |
| filename = myMkTemp(); |
| if (!filename) { |
| return VPD_ERR_SYSTEM; |
| } |
| |
| if (FLASHROM_OK != flashromFullRead(filename)) { |
| fprintf(stderr, "[WARN] Cannot read full BIOS.\n"); |
| return VPD_ERR_ROM_READ; |
| } |
| assert(buf = readFileContent(filename, &buf_size)); |
| if (findVpdPartition(buf, buf_size, offset, size)) { |
| fprintf(stderr, "[WARN] Cannot get eps_base from full BIOS.\n"); |
| retval = VPD_ERR_INVALID; |
| } else { |
| retval = VPD_OK; |
| } |
| free(buf); |
| return retval; |
| } |
| |
| |
| /* Below 2 functions are the helper functions for extract data from VPD 1.x |
| * binary-encoded structure. |
| * Note that the returning pointer is a static buffer. Thus the later call will |
| * destroy the former call's result. |
| */ |
| static uint8_t* extractString(const uint8_t* value, const int max_len) { |
| static uint8_t buf[128]; |
| int copy_len; |
| |
| /* not longer than the buffer size */ |
| copy_len = (max_len > sizeof(buf) - 1) ? sizeof(buf) - 1 : max_len; |
| memcpy(buf, value, copy_len); |
| buf[copy_len] = '\0'; |
| |
| return buf; |
| } |
| |
| static uint8_t* extractHex(const uint8_t* value, const int len) { |
| char tmp[4]; /* for a hex string */ |
| static uint8_t buf[128]; |
| int in, out; /* in points to value[], while out points to buf[]. */ |
| |
| for (in = 0, out = 0;; ++in) { |
| if (out + 3 > sizeof(buf) - 1) { |
| goto end_of_func; /* no more buffer */ |
| } |
| if (in >= len) { /* no more input */ |
| if (out) --out; /* remove the tailing colon */ |
| goto end_of_func; |
| } |
| sprintf(tmp, "%02x:", value[in]); |
| memcpy(&buf[out], tmp, strlen(tmp)); |
| out += strlen(tmp); |
| } |
| |
| end_of_func: |
| buf[out] = '\0'; |
| |
| return buf; |
| } |
| |
| |
| vpd_err_t loadFile(const char *filename, struct PairContainer *container, |
| int overwrite_it) { |
| uint32_t file_size; |
| uint8_t *read_buf; |
| uint8_t *vpd_buf; |
| struct vpd_entry *eps; |
| uint32_t related_eps_base; |
| struct vpd_header *header; |
| struct vpd_table_binary_blob_pointer *data; |
| uint8_t spd_uuid[16], vpd_2_0_uuid[16], vpd_1_2_uuid[16]; |
| int expected_handle; |
| int table_len; |
| int index; |
| vpd_err_t retval = VPD_OK; |
| |
| if (!(read_buf = readFileContent(filename, &file_size))) { |
| fprintf(stderr, "[WARN] Cannot LoadFile('%s'), that's fine.\n", filename); |
| return VPD_OK; |
| } |
| |
| if (0 == findVpdPartition(read_buf, file_size, &vpd_offset, &vpd_size)) { |
| if (!eps_base_force_specified) { |
| eps_base = vpd_offset; |
| } |
| } else { |
| /* We cannot parse out the VPD partition address from given file. |
| * Then, try to read the whole BIOS chip. */ |
| uint32_t offset, size; |
| if (!eps_base_force_specified) { |
| retval = getVpdPartitionFromFullBios(&offset, &size); |
| if (VPD_OK == retval) { |
| eps_base = offset; |
| vpd_size = size; |
| } else { |
| if (overwrite_it) { |
| retval = VPD_OK; |
| } else { |
| fprintf(stderr, "[ERROR] getVpdPartitionFromFullBios() failed."); |
| } |
| goto teardown; |
| } |
| } |
| } |
| |
| /* Update the following variables: |
| * eps_base: integer, the VPD EPS address in ROM. |
| * vpd_offset: integer, the VPD partition offset in file (read_buf[]). |
| * vpd_buf: uint8_t*, points to the VPD partition. |
| * eps: vpd_entry*, points to the EPS structure. |
| * eps_offset: integer, the offset of EPS related to vpd_buf[]. |
| */ |
| vpd_buf = &read_buf[vpd_offset]; |
| /* eps and eps_offset will be set slightly later. */ |
| |
| if (eps_base == UNKNOWN_EPS_BASE) { |
| fprintf(stderr, "[ERROR] Cannot determine eps_base. Cannot go on.\n" |
| " You may use -E to specify the value.\n"); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| /* In overwrite mode, we don't care the content inside. Stop parsing. */ |
| if (overwrite_it) { |
| retval = VPD_OK; |
| goto teardown; |
| } |
| |
| if (vpd_size < sizeof(struct vpd_entry)) { |
| fprintf(stderr, "[ERROR] vpd_size:%d is too small to be compared.\n", |
| vpd_size); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| /* try to search the EPS if it is not aligned to the begin of partition. */ |
| for (index = 0; index < vpd_size; index += 16) { |
| if (isEps(&vpd_buf[index])) { |
| eps = (struct vpd_entry *)&vpd_buf[index]; |
| eps_offset = index; |
| break; |
| } |
| } |
| /* jump if the VPD partition is not recognized. */ |
| if (index >= vpd_size) { |
| /* But OKAY if the VPD partition starts with FF, which might be un-used. */ |
| if (!memcmp("\xff\xff\xff\xff", vpd_buf, sizeof(VPD_ENTRY_MAGIC) - 1)) { |
| fprintf(stderr, "[WARN] VPD partition not formatted. It's fine.\n"); |
| retval = VPD_OK; |
| goto teardown; |
| } else { |
| fprintf(stderr, "SMBIOS signature is not matched.\n"); |
| fprintf(stderr, "You may use -O to overwrite the data.\n"); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| } |
| |
| /* adjust the eps_base for data->offset field below. */ |
| if (!eps_base_force_specified) { |
| related_eps_base = eps->table_address - sizeof(*eps); |
| } else { |
| related_eps_base = eps_base; |
| } |
| |
| /* EPS is done above. Parse structure tables below. */ |
| /* Get the first type 241 blob, at the tail of EPS. */ |
| header = (struct vpd_header*)(((uint8_t*)eps) + eps->entry_length); |
| data = (struct vpd_table_binary_blob_pointer *) |
| ((uint8_t *)header + sizeof(*header)); |
| |
| |
| /* prepare data structure to compare */ |
| uuid_parse(GOOGLE_SPD_UUID, spd_uuid); |
| uuid_parse(GOOGLE_VPD_2_0_UUID, vpd_2_0_uuid); |
| uuid_parse(GOOGLE_VPD_1_2_UUID, vpd_1_2_uuid); |
| |
| /* Iterate all tables */ |
| for (expected_handle = 0; |
| header->type != VPD_TYPE_END; |
| ++expected_handle) { |
| /* make sure we haven't have too much handle already. */ |
| if (expected_handle > 65535) { |
| fprintf(stderr, "[ERROR] too many handles. Terminate parsing.\n"); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| /* check type */ |
| if (header->type != VPD_TYPE_BINARY_BLOB_POINTER) { |
| fprintf(stderr, "[ERROR] We now only support Binary Blob Pointer (241). " |
| "But the %dth handle is type %d. Terminate parsing.\n", |
| header->handle, header->type); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| /* make sure handle is increasing as expected */ |
| if (header->handle != expected_handle) { |
| fprintf(stderr, "[ERROR] The handle value must be %d, but is %d.\n" |
| " Use -O option to re-format.\n", |
| expected_handle, header->handle); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| /* point to the table 241 data part */ |
| index = data->offset - related_eps_base; |
| if (index >= file_size) { |
| fprintf(stderr, "[ERROR] the table offset looks suspicious. " |
| "index=0x%x, data->offset=0x%x, related_eps_base=0x%x\n", |
| index, data->offset, related_eps_base); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| /* |
| * The main switch case |
| */ |
| if (!memcmp(data->uuid, spd_uuid, sizeof(data->uuid))) { |
| /* SPD */ |
| spd_offset = index; |
| spd_len = data->size; |
| if (vpd_offset + spd_offset + spd_len >= file_size) { |
| fprintf(stderr, "[ERROR] SPD offset in BBP is not correct.\n" |
| " vpd=0x%x spd=0x%x len=0x%x file_size=0x%x\n" |
| " If this file is VPD partition only, try to\n" |
| " use -E to adjust offset values.\n", |
| (uint32_t)vpd_offset, (uint32_t)spd_offset, |
| spd_len, file_size); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| if (!(spd_data = malloc(spd_len))) { |
| fprintf(stderr, "spd_data: malloc(%d bytes) failed.\n", spd_len); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| memcpy(spd_data, &read_buf[vpd_offset + spd_offset], spd_len); |
| file_flag |= HAS_SPD; |
| |
| } else if (!memcmp(data->uuid, vpd_2_0_uuid, sizeof(data->uuid))) { |
| /* VPD 2.0 */ |
| /* iterate all pairs */ |
| for (; |
| vpd_buf[index] != VPD_TYPE_TERMINATOR && |
| vpd_buf[index] != VPD_TYPE_IMPLICIT_TERMINATOR;) { |
| retval = decodeToContainer(container, file_size, vpd_buf, &index); |
| if (VPD_OK != retval) |
| { |
| fprintf(stderr, "decodeToContainer() error.\n"); |
| goto teardown; |
| } |
| } |
| file_flag |= HAS_VPD_2_0; |
| |
| } else if (!memcmp(data->uuid, vpd_1_2_uuid, sizeof(data->uuid))) { |
| /* VPD 1_2: please refer to "Google VPD Type 241 Format v1.2" */ |
| struct V12 { |
| uint8_t prod_sn[0x20]; |
| uint8_t sku[0x10]; |
| uint8_t uuid[0x10]; |
| uint8_t mb_sn[0x10]; |
| uint8_t imei[0x10]; |
| uint8_t ssd_sn[0x10]; |
| uint8_t mem_sn[0x10]; |
| uint8_t wlan_mac[0x06]; |
| } *v12 = (void*)&vpd_buf[index]; |
| setString(container, (uint8_t*)"Product_SN", |
| extractString(v12->prod_sn, sizeof(v12->prod_sn)), |
| VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"SKU", |
| extractString(v12->sku, sizeof(v12->sku)), VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"UUID", |
| extractHex(v12->uuid, sizeof(v12->uuid)), |
| VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"MotherBoard_SN", |
| extractString(v12->mb_sn, sizeof(v12->mb_sn)), |
| VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"IMEI", |
| extractString(v12->imei, sizeof(v12->imei)), |
| VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"SSD_SN", |
| extractString(v12->ssd_sn, sizeof(v12->ssd_sn)), |
| VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"Memory_SN", |
| extractString(v12->mem_sn, sizeof(v12->mem_sn)), |
| VPD_AS_LONG_AS); |
| setString(container, (uint8_t*)"WLAN_MAC", |
| extractHex(v12->wlan_mac, sizeof(v12->wlan_mac)), |
| VPD_AS_LONG_AS); |
| file_flag |= HAS_VPD_1_2; |
| |
| } else { |
| /* un-supported UUID */ |
| char outstr[37]; /* 36-char + 1 null terminator */ |
| |
| uuid_unparse(data->uuid, outstr); |
| fprintf(stderr, "[ERROR] un-supported UUID: %s\n", outstr); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| /* move to next table */ |
| if ((table_len = vpd_type241_size(header)) < 0) { |
| fprintf(stderr, "[ERROR] Cannot get type 241 structure table length.\n"); |
| retval = VPD_ERR_INVALID; |
| goto teardown; |
| } |
| |
| header = (struct vpd_header*)((uint8_t*)header + table_len); |
| data = (struct vpd_table_binary_blob_pointer *) |
| ((uint8_t *)header + sizeof(*header)); |
| } |
| |
| teardown: |
| free(read_buf); |
| |
| return retval; |
| } |
| |
| |
| vpd_err_t saveFile(const struct PairContainer *container, const char *filename, |
| int write_back_to_flash) { |
| FILE *fp; |
| unsigned char eps[1024]; |
| int eps_len = 0; |
| vpd_err_t retval = VPD_OK; |
| uint32_t file_seek; |
| struct google_vpd_info *info = (struct google_vpd_info *)buf; |
| |
| memset(eps, 0xff, sizeof(eps)); |
| buf_len = sizeof(*info); |
| |
| /* prepare info */ |
| memset(info, 0, sizeof(*info)); |
| memcpy(info->header.magic, VPD_INFO_MAGIC, sizeof(info->header.magic)); |
| |
| /* encode into buffer */ |
| retval = encodeContainer(&file, max_buf_len, buf, &buf_len); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "encodeContainer() error.\n"); |
| goto teardown; |
| } |
| retval = encodeVpdTerminator(max_buf_len, buf, &buf_len); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "Out of space for terminator.\n"); |
| goto teardown; |
| } |
| info->size = buf_len - sizeof(*info); |
| |
| retval = buildEpsAndTables(buf_len, sizeof(eps), eps, &eps_len); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "Cannot build EPS.\n"); |
| goto teardown; |
| } |
| assert(eps_len <= GOOGLE_SPD_OFFSET); |
| |
| /* Write data in the following order: |
| * 1. EPS |
| * 2. SPD |
| * 3. VPD 2.0 |
| */ |
| if (found_vpd) { |
| /* We found VPD partition in -f file, which means file is existed. |
| * Instead of truncating the whole file, open to write partial. */ |
| if (!(fp = fopen(filename, "r+"))) { |
| fprintf(stderr, "File [%s] cannot be opened for write.\n", filename); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| } else { |
| /* VPD is not found, which means the file is pure VPD data. |
| * Always creates the new file and overwrites the original content. */ |
| if (!(fp = fopen(filename, "w+"))) { |
| fprintf(stderr, "File [%s] cannot be opened for write.\n", filename); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| } |
| |
| if (write_back_to_flash) { |
| file_seek = 0; |
| } else { |
| file_seek = vpd_offset; |
| } |
| |
| /* write EPS */ |
| fseek(fp, file_seek + eps_offset, SEEK_SET); |
| if (fwrite(eps, eps_len, 1, fp) != 1) { |
| fprintf(stderr, "fwrite(EPS) error (%s)\n", strerror(errno)); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| |
| /* write SPD */ |
| if (spd_data) { |
| fseek(fp, file_seek + spd_offset, SEEK_SET); |
| if (fwrite(spd_data, spd_len, 1, fp) != 1) { |
| fprintf(stderr, "fwrite(SPD) error (%s)\n", strerror(errno)); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| } |
| |
| /* write VPD 2.0 */ |
| fseek(fp, file_seek + vpd_2_0_offset, SEEK_SET); |
| if (fwrite(buf, buf_len, 1, fp) != 1) { |
| fprintf(stderr, "fwrite(VPD 2.0) error (%s)\n", strerror(errno)); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| fclose(fp); |
| |
| teardown: |
| return retval; |
| } |
| |
| static void usage(const char *progname) { |
| printf("Chrome OS VPD 2.0 utility --\n"); |
| #ifdef VPD_VERSION |
| printf("%s\n", VPD_VERSION); |
| #endif |
| printf("\n"); |
| printf("Usage: %s [OPTION] ...\n", progname); |
| printf(" OPTIONs include:\n"); |
| printf(" -h This help page and version.\n"); |
| printf(" -f <filename> The output file name.\n"); |
| printf(" -E <address> EPS base address (default:0x240000).\n"); |
| printf(" -S <key=file> To add/change a string value, reading its\n"); |
| printf(" base64 contents from a file.\n"); |
| printf(" -s <key=value> To add/change a string value.\n"); |
| printf(" -p <pad length> Pad if length is shorter.\n"); |
| printf(" -i <partition> Specify VPD partition name in fmap.\n"); |
| printf(" -l List content in the file.\n"); |
| printf(" --sh Dump content for shell script.\n"); |
| printf(" -O Overwrite and re-format VPD partition.\n"); |
| printf(" -g <key> Print value string only.\n"); |
| printf(" -d <key> Delete a key.\n"); |
| printf("\n"); |
| printf(" Notes:\n"); |
| printf(" You can specify multiple -s and -d. However, vpd always\n"); |
| printf(" applies -s first, then -d.\n"); |
| printf(" -g and -l must be mutually exclusive.\n"); |
| printf("\n"); |
| } |
| |
| int main(int argc, char *argv[]) { |
| int opt; |
| int option_index = 0; |
| vpd_err_t retval = VPD_OK; |
| const char *optstring = "hf:E:s:S:p:i:lOg:d:"; |
| static struct option long_options[] = { |
| {"help", 0, 0, 'h'}, |
| {"file", 0, 0, 'f'}, |
| {"epsbase", 0, 0, 'E'}, |
| {"string", 0, 0, 's'}, |
| {"base64file", 0, 0, 'S'}, |
| {"pad", required_argument, 0, 'p'}, |
| {"partition", 0, 0, 'i'}, |
| {"list", 0, 0, 'l'}, |
| {"overwrite", 0, 0, 'O'}, |
| {"filter", 0, 0, 'g'}, |
| {"sh", 0, &export_type, VPD_EXPORT_AS_PARAMETER}, |
| {"delete", 0, 0, 'd'}, |
| {0, 0, 0, 0} |
| }; |
| char *filename = NULL; |
| const char *load_file = NULL; |
| const char *save_file = NULL; |
| const char *tmp_part_file = NULL; |
| const char *tmp_full_file = NULL; |
| uint8_t *filter_str = NULL; |
| int write_back_to_flash = 0; |
| int list_it = 0; |
| int overwrite_it = 0; |
| int modified = 0; |
| int num_to_delete; |
| int read_from_file = 0; |
| |
| initContainer(&file); |
| initContainer(&set_argument); |
| initContainer(&del_argument); |
| |
| while ((opt = getopt_long(argc, argv, optstring, |
| long_options, &option_index)) != EOF) { |
| switch (opt) { |
| case 'h': |
| usage(argv[0]); |
| goto teardown; |
| break; |
| |
| case 'f': |
| filename = strdup(optarg); |
| break; |
| |
| case 'E': |
| errno = 0; |
| eps_base = strtoul(optarg, (char **) NULL, 0); |
| eps_base_force_specified = 1; |
| |
| /* FIXME: this is not a stable way to detect error because |
| * implementation may (or may not) assign errno. */ |
| if (!eps_base && errno == EINVAL) { |
| fprintf(stderr, "Not a number for EPS base address: %s\n", optarg); |
| retval = VPD_ERR_SYNTAX; |
| goto teardown; |
| } |
| break; |
| |
| case 'S': |
| read_from_file = 1; |
| /* Fall through into the next case */ |
| case 's': |
| retval = parseString((uint8_t*)optarg, read_from_file); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "The string [%s] cannot be parsed.\n\n", optarg); |
| goto teardown; |
| } |
| read_from_file = 0; |
| break; |
| |
| case 'p': |
| errno = 0; |
| pad_value_len = strtol(optarg, (char **) NULL, 0); |
| |
| /* FIXME: this is not a stable way to detect error because |
| * implementation may (or may not) assign errno. */ |
| if (!pad_value_len && errno == EINVAL) { |
| fprintf(stderr, "Not a number for pad length: %s\n", optarg); |
| retval = VPD_ERR_SYNTAX; |
| goto teardown; |
| } |
| break; |
| |
| case 'i': |
| snprintf(fmap_vpd_area_name, sizeof(fmap_vpd_area_name), "%s", optarg); |
| break; |
| |
| case 'l': |
| list_it = 1; |
| break; |
| |
| case 'O': |
| overwrite_it = 1; |
| modified = 1; /* This option forces to write empty data back even |
| * no new pair is given. */ |
| break; |
| |
| case 'g': |
| filter_str = (uint8_t*)strdup(optarg); |
| break; |
| |
| case 'd': |
| /* Add key into container for delete. Since value is non-sense, |
| * keep it empty. */ |
| setString(&del_argument, (const uint8_t *)optarg, |
| (const uint8_t *)"", 0); |
| break; |
| |
| case 0: |
| break; |
| |
| default: |
| fprintf(stderr, "Invalid option (%s), use --help for usage.\n", optarg); |
| retval = VPD_ERR_SYNTAX; |
| goto teardown; |
| break; |
| } |
| } |
| |
| if (optind < argc) { |
| fprintf(stderr, "[ERROR] unexpected argument: %s\n\n", argv[optind]); |
| usage(argv[0]); |
| retval = VPD_ERR_SYNTAX; |
| goto teardown; |
| } |
| |
| tmp_part_file = myMkTemp(); |
| tmp_full_file = myMkTemp(); |
| if (!tmp_part_file || !tmp_full_file) { |
| fprintf(stderr, "[ERROR] Failed creating temporary files.\n"); |
| retval = VPD_ERR_SYSTEM; |
| goto teardown; |
| } |
| |
| if (list_it && filter_str) { |
| fprintf(stderr, "[ERROR] -l and -g must be mutually exclusive.\n"); |
| retval = VPD_ERR_SYNTAX; |
| goto teardown; |
| } |
| |
| /* to avoid malicious attack, we replace suspicious chars. */ |
| fmapNormalizeAreaName(fmap_vpd_area_name); |
| |
| /* if no filename is specified, call flashrom to read from flash. */ |
| if (!filename) { |
| if (FLASHROM_OK != flashromPartialRead(tmp_part_file, tmp_full_file, |
| fmap_vpd_area_name)) { |
| fprintf(stderr, "[WARN] flashromPartialRead() failed, try full read.\n"); |
| /* Try to read whole file */ |
| if (FLASHROM_OK != flashromFullRead(tmp_full_file)) { |
| fprintf(stderr, "[ERROR] flashromFullRead() error!\n"); |
| retval = VPD_ERR_ROM_READ; |
| goto teardown; |
| } else { |
| /* Success! Then the fmap_vpd_area_name is changed for later use. */ |
| } |
| } |
| |
| write_back_to_flash = 1; |
| load_file = tmp_full_file; |
| save_file = tmp_part_file; |
| } else { |
| load_file = filename; |
| save_file = filename; |
| } |
| |
| retval = loadFile(load_file, &file, overwrite_it); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "loadFile('%s') error.\n", load_file); |
| goto teardown; |
| } |
| |
| /* Do -s */ |
| if (lenOfContainer(&set_argument) > 0) { |
| mergeContainer(&file, &set_argument); |
| modified++; |
| } |
| |
| /* Do -d */ |
| num_to_delete = lenOfContainer(&del_argument); |
| if (subtractContainer(&file, &del_argument) != |
| num_to_delete) { |
| fprintf(stderr, "[ERROR] At least one of the keys to delete" |
| " does not exist. Command ignored.\n"); |
| retval = VPD_ERR_PARAM; |
| goto teardown; |
| } else if (num_to_delete > 0) { |
| modified++; |
| } |
| |
| if (list_it || filter_str) { |
| /* Reserve larger size because the exporting generates longer string than |
| * the encoded data. */ |
| uint8_t list_buf[BUF_LEN * 2]; |
| char para_buf[BUF_LEN] = ""; |
| int list_len = 0; |
| |
| if (filter_str) { |
| export_type = VPD_EXPORT_VALUE; |
| setContainerFilter(&file, filter_str); |
| } |
| |
| retval = exportContainer(export_type, &file, sizeof(list_buf), |
| list_buf, &list_len); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "exportContainer(): Cannot generate string.\n"); |
| goto teardown; |
| } |
| |
| /* Export necessary program parameters */ |
| if (export_type == VPD_EXPORT_AS_PARAMETER) { |
| char tmp_filename[BUF_LEN] = ""; |
| char tmp_filter[BUF_LEN] = ""; |
| |
| if (filename) |
| snprintf(tmp_filename, sizeof(tmp_filename), "-f %s", filename); |
| if (filter_str) |
| snprintf(tmp_filter, sizeof(tmp_filter), "-g %s", filter_str); |
| |
| snprintf(para_buf, sizeof(para_buf), "%s%s -i %s %s %s \\\n", |
| SH_COMMENT, argv[0], fmap_vpd_area_name, |
| tmp_filename, tmp_filter); |
| } |
| |
| printf("%s%s", para_buf, list_buf); |
| } |
| |
| if (modified) { |
| if (file_flag & HAS_VPD_1_2) { |
| fprintf(stderr, "[ERROR] Writing VPD 1.2 not supported yet.\n"); |
| retval = VPD_FAIL; |
| goto teardown; |
| } |
| |
| retval = saveFile(&file, save_file, write_back_to_flash); |
| if (VPD_OK != retval) { |
| fprintf(stderr, "saveFile('%s') error: %d\n", filename, retval); |
| goto teardown; |
| } |
| |
| if (write_back_to_flash) { |
| if (FLASHROM_OK != flashromPartialWrite(save_file, tmp_full_file, |
| fmap_vpd_area_name)) { |
| fprintf(stderr, "flashromPartialWrite() error.\n"); |
| retval = VPD_ERR_ROM_WRITE; |
| goto teardown; |
| } |
| } |
| } |
| |
| teardown: |
| if (spd_data) free(spd_data); |
| if (filename) free(filename); |
| if (filter_str) free(filter_str); |
| destroyContainer(&file); |
| destroyContainer(&set_argument); |
| destroyContainer(&del_argument); |
| cleanTempFiles(); |
| |
| return retval; |
| } |