diff options
-rw-r--r-- | extra/usb_updater/.gitignore | 1 | ||||
-rw-r--r-- | extra/usb_updater/Makefile | 7 | ||||
-rw-r--r-- | extra/usb_updater/desc_parser.c | 377 | ||||
-rw-r--r-- | extra/usb_updater/desc_parser.h | 58 | ||||
-rw-r--r-- | extra/usb_updater/sample_descriptor | 87 |
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 fedb959356..86632903c8 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 |