summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@google.com>2018-02-15 21:05:34 -0800
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2018-03-14 01:19:47 +0000
commit421e55e6fd6af248f3937159bf8a0c5762cbcf00 (patch)
treec1f039786b95cdd5a20b47a973b2b84c87ede34a
parent4456981b16b156260e718e086e19a7d0a8614b0b (diff)
downloadchrome-ec-421e55e6fd6af248f3937159bf8a0c5762cbcf00.tar.gz
gsctool: add RO descriptor parser
This patch adds code which would parse the RO hash/dump descriptor file including a database listing expected values of hashes for various ranges of target SPI flash devices, or areas which need to be printed out (hex dumped) for operator inspection. Lines starting with '#' are completely ignored. The rest of the logical lines could actually split into multiple text lines in the file, so to separate one logical line from another at least one empty line is required. Hash descriptor database file consists of sections for various Chrome OS boards. Each board description section starts with a logical line of 4 characters which is the board ID (the same as the board's RLZ code). Each board description section includes variable number of range descriptor entries, each entry being a logical line, potentially split into multiple text lines. Each entry consists of semicolon separated fields: {a|e|g}:{h|d}:base_addr:size[:value[:value[:value...]]]] Where - the first sindgle character field defines the way the range is accessed: a - AP flash e - EC flash g - EC flash requiring gang programming mode - the second single character field defines the range type h - Cr50 returns the hash of the range d - Cr50 returns actual contents of the range (hex dump) - the third and and forth fields are base address and size of the range - ranges of type 'h' include one or more values for the hash of the range, each hash is a 64 byte hex string. Ranges of type 'd' do not include any data. All values are expressed in hex. The parser API provides functions to open the passed in hash descriptor file and find there the section for a particular board, a function to advance to the next entry in the board's section, and a function to close the file when board entries scanning is completed. When scanning the entries, the parser verifies their sanity, i.e. conformance with the above described format, that all hashes are of the right size, that there are no hashes attached to 'dump' entries and there is at least one hash attached to the 'hash' entries, and that there are no invalid characters in the hashes and address range definitions. The parser is not yet used by the gsctool, but when the new module is compiled stand alone with -DTEST_PARSER passed to the compiler, it becomes an executable which can be given the test hash database (the new file, sample_descriptor) to interpret and report success or failure. BRANCH=none BUG=chromium:812880 TEST=ran the following commands: $ gcc -DTEST_PARSER desc_parser.c -o dp $ ./dp sample_descriptor Section 1, rv 0 Section 2, rv 0 Section 3, rv 0 Unexpected data in section 4 Section 4, rv -22 Invalid hash 1 size 0 in section 5 Section 5, rv -22 Invalid hash 1 size 0 in section 6 Section 6, rv -22 Invalid hash 1 size 63 in section 7 Section 7, rv -22 Invalid hash 1 size 65 in section 8 Section 8, rv -22 Invalid hash 1 value in section 9 Section 9, rv -22 Unexpected number of variants in section 10 Section 10, rv -22 Invalid hex value 10x in section 11 Section 11, rv -22 Section 12, rv -61 $ Change-Id: I14b2754a5f6ba26b3c56ddc26d45cb4574514b69 Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/923419 Reviewed-by: Andrey Pronin <apronin@chromium.org> (cherry picked from commit 9a6de75ebf220effe1e428f4e253178f36360ec1) Reviewed-on: https://chromium-review.googlesource.com/961611
-rw-r--r--extra/usb_updater/.gitignore1
-rw-r--r--extra/usb_updater/Makefile7
-rw-r--r--extra/usb_updater/desc_parser.c377
-rw-r--r--extra/usb_updater/desc_parser.h58
-rw-r--r--extra/usb_updater/sample_descriptor87
5 files changed, 528 insertions, 2 deletions
diff --git a/extra/usb_updater/.gitignore b/extra/usb_updater/.gitignore
index 870b0817e5..2e16c2ff2a 100644
--- a/extra/usb_updater/.gitignore
+++ b/extra/usb_updater/.gitignore
@@ -2,3 +2,4 @@ gsctool
usb_updater2
*.d
*.o
+dp \ No newline at end of file
diff --git a/extra/usb_updater/Makefile b/extra/usb_updater/Makefile
index a12fcff75e..ef5d432384 100644
--- a/extra/usb_updater/Makefile
+++ b/extra/usb_updater/Makefile
@@ -41,7 +41,7 @@ LIBS_common = -lfmap
all: $(PROGRAMS)
-GSCTOOL_SOURCES := gsctool.c
+GSCTOOL_SOURCES := gsctool.c desc_parser.c
GSCTOOL_OBJS := $(patsubst %.c,%.o,$(GSCTOOL_SOURCES))
DEPS := $(patsubst %.c,%.d,$(GSCTOOL_SOURCES))
@@ -59,6 +59,9 @@ usb_updater2: usb_updater2.c Makefile
.PHONY: clean
clean:
- rm -rf $(PROGRAMS) *~ *.o *.d
+ rm -rf $(PROGRAMS) *~ *.o *.d dp
+
+parser_debug: desc_parser.c
+ gcc -g -O0 -DTEST_PARSER desc_parser.c -o dp
-include $(DEPS)
diff --git a/extra/usb_updater/desc_parser.c b/extra/usb_updater/desc_parser.c
new file mode 100644
index 0000000000..04f144457c
--- /dev/null
+++ b/extra/usb_updater/desc_parser.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2018 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 <ctype.h>
+#include <errno.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "desc_parser.h"
+
+static FILE *hash_file_;
+static int line_count_;
+static int section_count_;
+
+/* Size of the retrieved string or negative OS error value. */
+static ssize_t get_next_line(char *next_line, size_t line_size)
+{
+ size_t index = 0;
+
+ while (fgets(next_line + index, line_size - index, hash_file_)) {
+ line_count_++;
+
+ if (next_line[index] == '#')
+ continue; /* Skip the comment */
+
+ if (next_line[index] == '\n') {
+ /*
+ * This is an empty line, return all collected data,
+ * pontintially an array of size zero if this is a
+ * repeated empty line.
+ */
+ next_line[index] = '\0';
+ return index;
+ }
+
+ /* Make sure next string overwrites this string's newline. */
+ index += strlen(next_line + index) - 1;
+
+ if (index >= (line_size - 1)) {
+ fprintf(stderr, "%s: Input overflow in line %d\n",
+ __func__, line_count_);
+ return -EOVERFLOW;
+ }
+ }
+
+ if (index) {
+ /*
+ * This must be the last line in the file with no empty line
+ * after it. Drop the closing newline, if it is there.
+ */
+ if (next_line[index] == '\n')
+ next_line[index--] = '\0';
+
+ return index;
+ }
+ return errno ? -errno : -ENODATA;
+}
+
+static int get_next_token(char *input, size_t expected_size, char **output)
+{
+ char *next_colon;
+
+ next_colon = strchr(input, ':');
+ if (next_colon)
+ *next_colon = '\0';
+ if (!next_colon || (expected_size &&
+ strlen(input) != expected_size)) {
+ fprintf(stderr, "Invalid entry in section %d\n",
+ section_count_);
+ return -EINVAL;
+ }
+
+ *output = next_colon + 1;
+ return 0;
+}
+
+static int get_hex_value(char *input, char **output)
+{
+ char *e;
+ long int value;
+
+ if (strchr(input, ':'))
+ get_next_token(input, 0, output);
+ else
+ *output = NULL;
+
+ value = strtol(input, &e, 16);
+ if (e && *e) {
+ fprintf(stderr, "Invalid hex value %s in section %d\n",
+ input, section_count_);
+ return -EINVAL;
+ }
+
+ return value;
+}
+
+static int parse_range(char *next_line,
+ size_t line_len,
+ struct addr_range *parsed_range)
+{
+ char *line_cursor;
+ char *next_token;
+ int is_a_hash_range;
+ struct result_node *node;
+ int value;
+
+ section_count_++;
+ line_cursor = next_line;
+
+ /* Range type. */
+ if (get_next_token(line_cursor, 1, &next_token))
+ return -EINVAL;
+
+ switch (*line_cursor) {
+ case 'a':
+ parsed_range->range_type = AP_RANGE;
+ break;
+ case 'e':
+ parsed_range->range_type = EC_RANGE;
+ break;
+ case 'g':
+ parsed_range->range_type = EC_GANG_RANGE;
+ break;
+ default:
+ fprintf(stderr, "Invalid range type %c in section %d\n",
+ *line_cursor, section_count_);
+ return -EINVAL;
+ }
+ line_cursor = next_token;
+
+ /* Hash or dump? */
+ if (get_next_token(line_cursor, 1, &next_token))
+ return -EINVAL;
+
+ switch (*line_cursor) {
+ case 'd':
+ is_a_hash_range = 0;
+ break;
+ case 'h':
+ is_a_hash_range = 1;
+ break;
+ default:
+ fprintf(stderr, "Invalid entry kind %c in section %d\n",
+ *line_cursor, section_count_);
+ return -EINVAL;
+ }
+ line_cursor = next_token;
+
+ /* Range base address. */
+ value = get_hex_value(line_cursor, &next_token);
+ if (value < 0)
+ return -EINVAL;
+ parsed_range->base_addr = value;
+
+ /* Range size. */
+ line_cursor = next_token;
+ value = get_hex_value(line_cursor, &next_token);
+ if (value < 0)
+ return -EINVAL;
+ parsed_range->range_size = value;
+
+ if (!next_token && is_a_hash_range) {
+ fprintf(stderr, "Missing hash in section %d\n", section_count_);
+ return -EINVAL;
+ }
+
+ if (next_token && !is_a_hash_range) {
+ fprintf(stderr, "Unexpected data in section %d\n",
+ section_count_);
+ return -EINVAL;
+ }
+
+ parsed_range->variant_count = 0;
+ if (!is_a_hash_range)
+ return 0; /* No more input for dump ranges. */
+
+ node = parsed_range->variants;
+ do { /* While line is not over. */
+ char c;
+ int i = 0;
+
+ line_cursor = next_token;
+ next_token = strchr(line_cursor, ':');
+ if (next_token)
+ *next_token++ = '\0';
+ if (strlen(line_cursor) != (2 * sizeof(*node))) {
+ fprintf(stderr,
+ "Invalid hash %zd size %zd in section %d\n",
+ parsed_range->variant_count + 1,
+ strlen(line_cursor), section_count_);
+ return -EINVAL;
+ }
+
+ while ((c = *line_cursor++) != 0) {
+ uint8_t nibble;
+
+ if (!isxdigit(c)) {
+ fprintf(stderr,
+ "Invalid hash %zd value in section %d\n",
+ parsed_range->variant_count + 1,
+ section_count_);
+ return -EINVAL;
+ }
+
+ if (c <= '9')
+ nibble = c - '0';
+ else if (c >= 'a')
+ nibble = c - 'a' + 10;
+ else
+ nibble = c - 'A' + 10;
+
+ if (i & 1)
+ node->expected_result[i / 2] |= nibble;
+ else
+ node->expected_result[i / 2] = nibble << 4;
+
+ i++;
+ }
+
+ node++;
+ parsed_range->variant_count++;
+
+ } while (next_token);
+
+ return 0;
+}
+
+int parser_get_next_range(struct addr_range **range)
+{
+ char next_line[1000]; /* Should be enough for the largest descriptor. */
+ ssize_t entry_size;
+ struct addr_range *new_range;
+ int rv;
+
+ /*
+ * This is used to verify consistency of the description database,
+ * namely that all hash sections include the same numger of hash
+ * variants.
+ */
+ static size_t variant_count;
+
+ /*
+ * We come here after hash descriptor database file was opened and the
+ * current board's section has been found. Just in case check if the
+ * file has been opened.
+ */
+ if (!hash_file_ || !range)
+ return -EIO;
+
+ *range = NULL;
+ do {
+ entry_size = get_next_line(next_line, sizeof(next_line));
+ if (entry_size < 0)
+ return entry_size;
+ } while (!entry_size); /* Skip empty lines. */
+
+ if (entry_size == 4) /* Next board's entry must have been reached. */
+ return -ENODATA;
+
+ /* This sure will be enough to fit parsed structure contents. */
+ new_range = malloc(sizeof(*new_range) + entry_size);
+ if (!new_range) {
+ fprintf(stderr, "Failed to allocate %zd bytes\n",
+ sizeof(*new_range) + entry_size);
+ return -ENOMEM;
+ }
+
+ /* This must be a new descriptor section, lets parse it. */
+ rv = parse_range(next_line, entry_size, new_range);
+
+ if (rv) {
+ free(new_range);
+ return rv;
+ }
+
+ if (new_range->variant_count) {
+ /*
+ * A new range was found, if this is the first hash range we
+ * encountered, save its dimensions for future reference.
+ *
+ * If this is not the first one - verify that it has the same
+ * number of hash variants as all previous hash blocks.
+ */
+ if (!variant_count) {
+ variant_count = new_range->variant_count;
+ } else if (variant_count != new_range->variant_count) {
+ fprintf(stderr,
+ "Unexpected number of variants in section %d\n",
+ section_count_);
+ free(new_range);
+ return -EINVAL;
+ }
+ }
+
+ *range = new_range;
+ return 0;
+
+}
+
+int parser_find_board(const char *hash_file_name, const char *board_id)
+{
+ char next_line[1000]; /* Should be enough for the largest descriptor. */
+ ssize_t id_len = strlen(board_id);
+
+ hash_file_ = fopen(hash_file_name, "r");
+ if (!hash_file_) {
+ fprintf(stderr, "Error:%s can not open file '%s'\n",
+ strerror(errno), hash_file_name);
+ return errno;
+ }
+
+ while (1) {
+ ssize_t entry_size;
+
+ entry_size = get_next_line(next_line, sizeof(next_line));
+ if (entry_size < 0) {
+ fclose(hash_file_);
+ return entry_size;
+ }
+
+ if ((entry_size == id_len) &&
+ !memcmp(next_line, board_id, id_len))
+ return 0;
+ }
+
+ fclose(hash_file_);
+ hash_file_ = NULL;
+ return errno;
+}
+
+void parser_done(void)
+{
+ if (!hash_file_)
+ return;
+
+ fclose(hash_file_);
+ hash_file_ = NULL;
+}
+
+#ifdef TEST_PARSER
+int main(int argc, char **argv)
+{
+ const char *board_name = "QZUX";
+ char next_line[1000]; /* Should be enough for the largest descriptor. */
+ int rv;
+ int count;
+
+ if (argc < 2) {
+ fprintf(stderr, "Name of the file to parse is required.\n");
+ return -1;
+ }
+
+ if (parser_find_board(argv[1], board_name)) {
+ fprintf(stderr, "Board %s NOT found\n", board_name);
+ return -1;
+ }
+
+ count = 0;
+ do {
+ struct addr_range *range;
+
+ rv = parser_get_next_range(&range);
+ count++;
+ printf("Section %d, rv %d\n", count, rv);
+ free(range); /* Freeing NULL is OK. */
+
+ } while (rv != -ENODATA);
+
+ return 0;
+}
+#endif
diff --git a/extra/usb_updater/desc_parser.h b/extra/usb_updater/desc_parser.h
new file mode 100644
index 0000000000..faa80d1a63
--- /dev/null
+++ b/extra/usb_updater/desc_parser.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 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.
+ */
+#ifndef __EXTRA_USB_UPDATER_DESC_PARSER_H
+#define __EXTRA_USB_UPDATER_DESC_PARSER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct result_node {
+ uint8_t expected_result[32];
+};
+
+enum range_type_t {
+ NOT_A_RANGE,
+ AP_RANGE,
+ EC_RANGE,
+ EC_GANG_RANGE,
+};
+
+struct addr_range {
+ enum range_type_t range_type;
+ uint32_t base_addr;
+ uint32_t range_size;
+ size_t variant_count; /* Set to zero for dump ranges. */
+ struct result_node variants[0];
+};
+
+/* Board description retrieval API includes the following functions. */
+
+/*
+ * In the given hash database file find board by its ID. Return zero on
+ * success, or OS error of error. In particular ENODATA is returned if the
+ * section for the required board ID is not found in the file.
+ */
+int parser_find_board(const char *hash_file_name, const char board_id[4]);
+
+/*
+ * Find next range for the previousely defined board, parse it into the
+ * addr_range structure and return pointer to the parsed structure to the
+ * caller, set pointer to NULL if no more entries are available or in case of
+ * error.
+ *
+ * Caller of this function is responsible for returning memory allocated for
+ * the entry.
+ *
+ * Return value set to zero on success, or to OS error if one occurs. EIO is
+ * used if an attmept to get next range is made before hash database file was
+ * opened and board entry in it was found.
+ */
+int parser_get_next_range(struct addr_range **range);
+
+/* Close the hash database file. */
+void parser_done(void);
+
+#endif // __EXTRA_USB_UPDATER_DESC_PARSER_H
diff --git a/extra/usb_updater/sample_descriptor b/extra/usb_updater/sample_descriptor
new file mode 100644
index 0000000000..1566e9e2e1
--- /dev/null
+++ b/extra/usb_updater/sample_descriptor
@@ -0,0 +1,87 @@
+# Copyright 2018 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.
+#
+# Hash descriptor database file consists of sections for various Chrome OS
+# boards. Each board description section starts with a line of 4 characters
+# which is the board ID (the same as the board's RLZ code).
+#
+# Each board description section includes variable number of range
+# descriptor entries, each entry consisting of semicolon separated fields:
+#
+# {a|e|g}:{h|d}:base_addr:size[:value[:value[:value...]]]]
+#
+# Where
+#
+# - the first sindgle character field defines the way the range is accessed:
+# a - AP flash
+# e - EC flash
+# g - EC flash requiring gang programming mode
+# - the second single character field defines the range type
+# h - Cr50 returns the hash of the range
+# d - Cr50 returns actual contents of the range (hex dump)
+# - the third and and forth fields are base address and size of the range
+# - ranges of type 'h' include one or more values for the hash of the range.
+#
+# Descriptor entries can be split along multiple lines. Each entry is
+# terminated by an empty line. Board description section is completed when
+# another board ID or end of file is encountered.
+#
+# All values are expressed in hex. Repeating empty lines and lines starting
+# with '#' are ignored.
+#
+
+QZUX
+
+# 1: Valid hash section.
+a:h:0:10000:
+756c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503:
+336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503:
+446c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503
+
+# 2: Valid dump section.
+a:d:10:10
+
+# 3: Valid hash section.
+e:h:0:100:
+55d262badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87:
+444442badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87:
+443322badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87
+
+# 4: Invalid dump section (includes hash)
+a:d:20:10:55d262badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb87
+
+# 5: Invalid hash section (does not include hash)
+e:h:0:100:
+
+# 6: Another invalid hash section (does not include hash)
+e:h:0:100:
+
+# extra empty lines
+
+
+# 7: Invalid hash section (hash too short)
+e:h:0:100:
+55d262badc1116520a7ae1d3fda380c0382b4b87f0db10de6495053ba3aadb8
+
+# 8: Invalid hash section (hash too long)
+a:h:0:10000:
+756c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf35034:
+336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3503
+
+# 9: Invalid hash section (hash includes non-hex value)
+a:h:0:10000:
+756c41b90ac9aa23a6c98ce13549dccd7xe0a83f8537eb834d9cfc3d12bf3503:
+336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3505
+
+# 10: Invalid hash section (hash does not include 3 variants)
+a:h:0:10000:
+756c41b90ac9aa23a6c98ce13549dccd75e0a83f8537eb834d9cfc3d12bf3503:
+336c41b90ac9aa23a6c98ce13549dccd72e0a83f8537eb834d9cfc3d12bf3505
+
+# 11: Invalid dump section (size includes non hex character)
+a:d:10:10x
+
+ABCD
+
+a:d:10:10