diff --git a/board/cr50/board.c b/board/cr50/board.c index 702e35f84d..acb30c94c5 100644 --- a/board/cr50/board.c +++ b/board/cr50/board.c @@ -118,6 +118,7 @@ const void * const usb_strings[] = { [USB_STR_HID_NAME] = USB_STRING_DESC("PokeyPokey"), [USB_STR_AP_NAME] = USB_STRING_DESC("AP"), [USB_STR_EC_NAME] = USB_STRING_DESC("EC"), + [USB_STR_UPGRADE_NAME] = USB_STRING_DESC("Firmware upgrade"), }; BUILD_ASSERT(ARRAY_SIZE(usb_strings) == USB_STR_COUNT); #endif diff --git a/board/cr50/board.h b/board/cr50/board.h index 07cc8cc49e..54726c9227 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -73,6 +73,7 @@ enum usb_strings { USB_STR_HID_NAME, USB_STR_AP_NAME, USB_STR_EC_NAME, + USB_STR_UPGRADE_NAME, USB_STR_COUNT }; @@ -119,4 +120,8 @@ enum usb_strings { */ #define CONFIG_DEBUG_STACK_OVERFLOW #define CONFIG_RW_B + +/* Firmware upgrade options. */ +#define CONFIG_NON_HC_FW_UPDATE + #endif /* __CROS_EC_BOARD_H */ diff --git a/board/cr50/tpm2/upgrade.c b/board/cr50/tpm2/upgrade.c index dcc7acf631..b5d3692467 100644 --- a/board/cr50/tpm2/upgrade.c +++ b/board/cr50/tpm2/upgrade.c @@ -3,185 +3,7 @@ * found in the LICENSE file. */ -#include "byteorder.h" -#include "console.h" -#include "dcrypto/dcrypto.h" #include "extension.h" -#include "flash.h" -#include "hooks.h" -#include "include/compile_time_macros.h" -#include "sha1.h" -#include "uart.h" - -#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args) - -/* Various upgrade extension command return values. */ -enum return_value { - UPGRADE_SUCCESS = 0, - UPGRADE_BAD_ADDR = 1, - UPGRADE_ERASE_FAILURE = 2, - UPGRADE_DATA_ERROR = 3, - UPGRADE_WRITE_FAILURE = 4, - UPGRADE_VERIFY_ERROR = 5, - UPGRADE_GEN_ERROR = 6, -}; - -/* - * The payload of the upgrade command. (Integer values in network byte order). - * - * block digest: the first four bytes of the sha1 digest of the rest of the - * structure. - * block_base: address where this block needs to be written to. - * block_body: variable size data to written at address 'block_base'. - */ -struct upgrade_command { - uint32_t block_digest; - uint32_t block_base; - uint8_t block_body[0]; -} __packed; - -/* - * This array defines two possibe sections available for the firmare update. - * The section whcih does not map the current execting code is picked as the - * valid update area. The values are offsets into the flash space. - */ -const struct section_descriptor { - uint32_t sect_base_offset; - uint32_t sect_top_offset; -} rw_sections[] = { - {CONFIG_RW_MEM_OFF, - CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE}, - {CONFIG_RW_B_MEM_OFF, - CONFIG_RW_B_MEM_OFF + CONFIG_RW_SIZE} -}; - -const struct section_descriptor *valid_section; - -/* Pick the section where updates can go to based on current code address. */ -static void set_valid_section(void) -{ - int i; - uint32_t run_time_offs = (uint32_t) set_valid_section - - CONFIG_PROGRAM_MEMORY_BASE; - - for (i = 0; i < ARRAY_SIZE(rw_sections); i++) { - if ((run_time_offs > rw_sections[i].sect_base_offset) && - (run_time_offs < rw_sections[i].sect_top_offset)) - continue; - valid_section = rw_sections + i; - break; - } -} - -/* Verify that the passed in block fits into the valid area. */ -static int valid_upgrade_chunk(uint32_t block_offset, size_t body_size) -{ - if (valid_section && - (block_offset >= valid_section->sect_base_offset) && - ((block_offset + body_size) < valid_section->sect_top_offset)) - return 1; - - return 0; -} - -static void fw_upgrade_command_handler(void *body, - size_t cmd_size, - size_t *response_size) -{ - struct upgrade_command *cmd_body = body; - uint8_t *rv = body; - uint8_t sha1_digest[SHA1_DIGEST_SIZE]; - size_t body_size; - uint32_t block_offset; - - /* - * A single byte response, unless this is the first message in the - * programming sequence. - */ - *response_size = sizeof(*rv); - - body_size = cmd_size - offsetof(struct upgrade_command, block_body); - if (body_size < 0) { - CPRINTF("%s:%d\n", __func__, __LINE__); - *rv = UPGRADE_GEN_ERROR; - return; - } - - if (!cmd_body->block_base && !body_size) { - /* - * This is the first message of the upgrade process, let's - * determine the valid upgrade section and erase its contents. - */ - set_valid_section(); - - if (flash_physical_erase(valid_section->sect_base_offset, - valid_section->sect_top_offset - - valid_section->sect_base_offset)) { - CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n", - __func__, __LINE__, - valid_section->sect_base_offset, - valid_section->sect_top_offset - - valid_section->sect_base_offset); - *rv = UPGRADE_ERASE_FAILURE; - return; - } - /* - * Successful erase means that we need to return the base - * address of the section to be programmed with the upgrade. - */ - *(uint32_t *)body = htobe32(valid_section->sect_base_offset + - CONFIG_PROGRAM_MEMORY_BASE); - *response_size = sizeof(uint32_t); - return; - } - - /* Check if the block will fit into the valid area. */ - block_offset = be32toh(cmd_body->block_base) - - CONFIG_PROGRAM_MEMORY_BASE; - if (!valid_upgrade_chunk(block_offset, body_size)) { - *rv = UPGRADE_BAD_ADDR; - CPRINTF("%s:%d %x, %d base %x top %x\n", __func__, __LINE__, - block_offset, body_size, - valid_section->sect_base_offset, - valid_section->sect_top_offset); - return; - } - - /* Check if the block was received properly. */ - DCRYPTO_SHA1_hash((uint8_t *)&cmd_body->block_base, - body_size + sizeof(cmd_body->block_base), - sha1_digest); - if (memcmp(sha1_digest, &cmd_body->block_digest, - sizeof(cmd_body->block_digest))) { - *rv = UPGRADE_DATA_ERROR; - CPRINTF("%s:%d sha1 %x not equal received %x at offs. 0x%x\n", - __func__, __LINE__, - *(uint32_t *)sha1_digest, cmd_body->block_digest, - block_offset); - return; - } - - CPRINTF("%s: programming at address 0x%x\n", __func__, - block_offset + CONFIG_PROGRAM_MEMORY_BASE); - if (flash_physical_write(block_offset, body_size, - cmd_body->block_body) != EC_SUCCESS) { - *rv = UPGRADE_WRITE_FAILURE; - CPRINTF("%s:%d upgrade write error\n", - __func__, __LINE__); - return; - } - - /* Werify that data was written properly. */ - if (memcmp(cmd_body->block_body, (void *) - (block_offset + CONFIG_PROGRAM_MEMORY_BASE), - body_size)) { - *rv = UPGRADE_VERIFY_ERROR; - CPRINTF("%s:%d upgrade verification error\n", - __func__, __LINE__); - return; - } - - *rv = UPGRADE_SUCCESS; -} +#include "upgrade_fw.h" DECLARE_EXTENSION_COMMAND(EXTENSION_FW_UPGRADE, fw_upgrade_command_handler); diff --git a/chip/g/build.mk b/chip/g/build.mk index 959a0f51cf..e4a7d60fa8 100644 --- a/chip/g/build.mk +++ b/chip/g/build.mk @@ -40,6 +40,7 @@ chip-$(CONFIG_SPI_MASTER)+=spi_master.o chip-y+= pmu.o chip-y+= trng.o +chip-$(CONFIG_NON_HC_FW_UPDATE)+= upgrade_fw.o chip-$(CONFIG_SPS)+= sps.o chip-$(CONFIG_TPM_SPS)+=sps_tpm.o chip-$(CONFIG_WATCHDOG)+=watchdog.o diff --git a/chip/g/upgrade_fw.c b/chip/g/upgrade_fw.c new file mode 100644 index 0000000000..fc6ca50850 --- /dev/null +++ b/chip/g/upgrade_fw.c @@ -0,0 +1,185 @@ +/* Copyright 2016 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 "byteorder.h" +#include "console.h" +#include "dcrypto/dcrypto.h" +#include "extension.h" +#include "flash.h" +#include "hooks.h" +#include "include/compile_time_macros.h" +#include "sha1.h" +#include "uart.h" + +#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args) + +/* Various upgrade extension command return values. */ +enum return_value { + UPGRADE_SUCCESS = 0, + UPGRADE_BAD_ADDR = 1, + UPGRADE_ERASE_FAILURE = 2, + UPGRADE_DATA_ERROR = 3, + UPGRADE_WRITE_FAILURE = 4, + UPGRADE_VERIFY_ERROR = 5, + UPGRADE_GEN_ERROR = 6, +}; + +/* + * The payload of the upgrade command. (Integer values in network byte order). + * + * block digest: the first four bytes of the sha1 digest of the rest of the + * structure. + * block_base: address where this block needs to be written to. + * block_body: variable size data to written at address 'block_base'. + */ +struct upgrade_command { + uint32_t block_digest; + uint32_t block_base; + uint8_t block_body[0]; +} __packed; + +/* + * This array defines two possibe sections available for the firmare update. + * The section whcih does not map the current execting code is picked as the + * valid update area. The values are offsets into the flash space. + */ +const struct section_descriptor { + uint32_t sect_base_offset; + uint32_t sect_top_offset; +} rw_sections[] = { + {CONFIG_RW_MEM_OFF, + CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE}, + {CONFIG_RW_B_MEM_OFF, + CONFIG_RW_B_MEM_OFF + CONFIG_RW_SIZE} +}; + +const struct section_descriptor *valid_section; + +/* Pick the section where updates can go to based on current code address. */ +static void set_valid_section(void) +{ + int i; + uint32_t run_time_offs = (uint32_t) set_valid_section - + CONFIG_PROGRAM_MEMORY_BASE; + + for (i = 0; i < ARRAY_SIZE(rw_sections); i++) { + if ((run_time_offs > rw_sections[i].sect_base_offset) && + (run_time_offs < rw_sections[i].sect_top_offset)) + continue; + valid_section = rw_sections + i; + break; + } +} + +/* Verify that the passed in block fits into the valid area. */ +static int valid_upgrade_chunk(uint32_t block_offset, size_t body_size) +{ + if (valid_section && + (block_offset >= valid_section->sect_base_offset) && + ((block_offset + body_size) < valid_section->sect_top_offset)) + return 1; + + return 0; +} + +void fw_upgrade_command_handler(void *body, + size_t cmd_size, + size_t *response_size) +{ + struct upgrade_command *cmd_body = body; + uint8_t *rv = body; + uint8_t sha1_digest[SHA1_DIGEST_SIZE]; + size_t body_size; + uint32_t block_offset; + + /* + * A single byte response, unless this is the first message in the + * programming sequence. + */ + *response_size = sizeof(*rv); + + body_size = cmd_size - offsetof(struct upgrade_command, block_body); + if (body_size < 0) { + CPRINTF("%s:%d\n", __func__, __LINE__); + *rv = UPGRADE_GEN_ERROR; + return; + } + + if (!cmd_body->block_base && !body_size) { + /* + * This is the first message of the upgrade process, let's + * determine the valid upgrade section and erase its contents. + */ + set_valid_section(); + + if (flash_physical_erase(valid_section->sect_base_offset, + valid_section->sect_top_offset - + valid_section->sect_base_offset)) { + CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n", + __func__, __LINE__, + valid_section->sect_base_offset, + valid_section->sect_top_offset - + valid_section->sect_base_offset); + *rv = UPGRADE_ERASE_FAILURE; + return; + } + /* + * Successful erase means that we need to return the base + * address of the section to be programmed with the upgrade. + */ + *(uint32_t *)body = htobe32(valid_section->sect_base_offset + + CONFIG_PROGRAM_MEMORY_BASE); + *response_size = sizeof(uint32_t); + return; + } + + /* Check if the block will fit into the valid area. */ + block_offset = be32toh(cmd_body->block_base) - + CONFIG_PROGRAM_MEMORY_BASE; + if (!valid_upgrade_chunk(block_offset, body_size)) { + *rv = UPGRADE_BAD_ADDR; + CPRINTF("%s:%d %x, %d base %x top %x\n", __func__, __LINE__, + block_offset, body_size, + valid_section->sect_base_offset, + valid_section->sect_top_offset); + return; + } + + /* Check if the block was received properly. */ + DCRYPTO_SHA1_hash((uint8_t *)&cmd_body->block_base, + body_size + sizeof(cmd_body->block_base), + sha1_digest); + if (memcmp(sha1_digest, &cmd_body->block_digest, + sizeof(cmd_body->block_digest))) { + *rv = UPGRADE_DATA_ERROR; + CPRINTF("%s:%d sha1 %x not equal received %x at offs. 0x%x\n", + __func__, __LINE__, + *(uint32_t *)sha1_digest, cmd_body->block_digest, + block_offset); + return; + } + + CPRINTF("%s: programming at address 0x%x\n", __func__, + block_offset + CONFIG_PROGRAM_MEMORY_BASE); + if (flash_physical_write(block_offset, body_size, + cmd_body->block_body) != EC_SUCCESS) { + *rv = UPGRADE_WRITE_FAILURE; + CPRINTF("%s:%d upgrade write error\n", + __func__, __LINE__); + return; + } + + /* Werify that data was written properly. */ + if (memcmp(cmd_body->block_body, (void *) + (block_offset + CONFIG_PROGRAM_MEMORY_BASE), + body_size)) { + *rv = UPGRADE_VERIFY_ERROR; + CPRINTF("%s:%d upgrade verification error\n", + __func__, __LINE__); + return; + } + + *rv = UPGRADE_SUCCESS; +} diff --git a/chip/g/upgrade_fw.h b/chip/g/upgrade_fw.h new file mode 100644 index 0000000000..1e6c595fb7 --- /dev/null +++ b/chip/g/upgrade_fw.h @@ -0,0 +1,15 @@ +/* Copyright 2016 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 __EC_CHIP_G_UPGRADE_FW_H +#define __EC_CHIP_G_UPGRADE_FW_H + +#include + +void fw_upgrade_command_handler(void *body, + size_t cmd_size, + size_t *response_size); + +#endif /* ! __EC_CHIP_G_UPGRADE_FW_H */ diff --git a/chip/g/usb_upgrade.c b/chip/g/usb_upgrade.c new file mode 100644 index 0000000000..54e4fb6d1c --- /dev/null +++ b/chip/g/usb_upgrade.c @@ -0,0 +1,230 @@ +/* Copyright 2016 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 "byteorder.h" +#include "common.h" +#include "console.h" +#include "consumer.h" +#include "queue_policies.h" +#include "shared_mem.h" +#include "system.h" +#include "upgrade_fw.h" +#include "usb-stream.h" +#include "usb_upgrade.h" + +#define CPRINTS(format, args...) cprints(CC_USB, format, ## args) + +/* + * This file is an adaptation layer between the USB interface and the firmware + * update engine. The engine expects to receive long blocks of data, 1K or so + * in size, prepended by the offset where the data needs to be programmed into + * the flash and a 4 byte integrity check value. + * + * The USB transfer, on the other hand, operates on much shorter chunks of + * data, typically 64 bytes in this case. This module reassembles firmware + * programming blocks from the USB chunks, and invokes the programmer passing + * it the full block. + * + * The programmer reports results by putting the return value of one or four + * bytes into the same buffer where the block was passed in. This wrapper + * retrieves the programmer's return value, normalizes it to 4 bytes and sends + * it back to the host. + * + * In the end of the successful image transfer and programming, the host send + * the reset command, and the device reboots itself. + */ + +struct consumer const upgrade_consumer; +struct usb_stream_config const usb_upgrade; + +static struct queue const upgrade_to_usb = QUEUE_DIRECT(64, uint8_t, + null_producer, + usb_upgrade.consumer); +static struct queue const usb_to_upgrade = QUEUE_DIRECT(64, uint8_t, + usb_upgrade.producer, + upgrade_consumer); + +USB_STREAM_CONFIG_FULL(usb_upgrade, + USB_IFACE_UPGRADE, + USB_CLASS_VENDOR_SPEC, + UNOFFICIAL_USB_SUBCLASS_GOOGLE_CR50, + 0xff, /* vendor-specific protocol */ + USB_STR_UPGRADE_NAME, + USB_EP_UPGRADE, + USB_MAX_PACKET_SIZE, + USB_MAX_PACKET_SIZE, + usb_to_upgrade, + upgrade_to_usb) + + +/* The receiver can be in one of the states below. */ +enum rx_state { + rx_idle, /* Nothing happened yet. */ + rx_inside_block, /* Assembling a block to pass to the programmer. */ + rx_outside_block, /* Waiting for the next block to start or for the + reset command. */ + rx_awaiting_reset /* Waiting for reset confirmation. */ +}; + +/* This is the format of the header the programmer expects. */ +struct upgrade_command { + uint32_t block_digest; /* first 4 bytes of sha1 of the rest of the + block. */ + uint32_t block_base; /* Offset of this block into the flash SPI. */ +}; + +/* This is the format of the header the host uses. */ +struct update_pdu_header { + uint32_t block_size; /* Total size of the block, including this + field. */ + union { + struct upgrade_command cmd; + uint32_t resp; /* The programmer puts response to the same + buffer where the command was. */ + }; + /* The actual payload goes here. */ +}; + +enum rx_state rx_state_ = rx_idle; +static uint8_t *block_buffer; +static uint32_t block_size; +static uint32_t block_index; + +/* Called to deal with data from the host */ +static void upgrade_out_handler(struct consumer const *consumer, size_t count) +{ + struct update_pdu_header updu; + size_t resp_size; + uint32_t resp_value; + + if (rx_state_ == rx_idle) { + /* This better be the first block, of zero size. */ + if (count != sizeof(struct update_pdu_header)) { + CPRINTS("FW update: wrong first block size %d\n", + count); + return; + } + QUEUE_REMOVE_UNITS(consumer->queue, &updu, count); + + CPRINTS("FW update: starting...\n"); + + fw_upgrade_command_handler(&updu.cmd, count - + offsetof(struct update_pdu_header, + cmd), + &resp_size); + + if (resp_size == 4) { + resp_value = updu.resp; /* Already in network order. */ + rx_state_ = rx_outside_block; + } else { + /* This must be a single byte error code. */ + resp_value = htobe32(*((uint8_t *)&updu.resp)); + } + /* Let the host know what upgrader had to say. */ + QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, + sizeof(resp_value)); + return; + } + + if (rx_state_ == rx_awaiting_reset) { + /* + * Any USB data received in this state triggers reset, no + * response required. + */ + CPRINTS("reboot hard"); + cflush(); + system_reset(SYSTEM_RESET_HARD); + while (1) + ; + } + + if (rx_state_ == rx_outside_block) { + /* + * Expecting to receive the beginning of the block or the + * reset command if all data blocks have been processed. + */ + if (count == 4) { + uint32_t command; + + QUEUE_REMOVE_UNITS(consumer->queue, &command, + sizeof(command)); + command = be32toh(command); + if (command == UPGRADE_DONE) { + CPRINTS("FW update: done\n"); + resp_value = 0; + QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, + sizeof(resp_value)); + rx_state_ = rx_awaiting_reset; + return; + } + } + + if (count < sizeof(updu)) { + CPRINTS("FW update: error: first chunk of %d bytes\n", + count); + rx_state_ = rx_idle; + return; + } + + QUEUE_REMOVE_UNITS(consumer->queue, &updu, sizeof(updu)); + + /* Let's allocate a large enough buffer. */ + block_size = be32toh(updu.block_size) - + offsetof(struct update_pdu_header, cmd); + if (shared_mem_acquire(block_size, (char **)&block_buffer) + != EC_SUCCESS) { + /* TODO:(vbendeb) report out of memory here. */ + CPRINTS("FW update: error: failed to alloc %d bytes.\n", + block_size); + return; + } + + /* + * Copy the rest of the message into the block buffer to pass + * to the upgrader. + */ + block_index = sizeof(updu) - + offsetof(struct update_pdu_header, cmd); + memcpy(block_buffer, &updu.cmd, block_index); + QUEUE_REMOVE_UNITS(consumer->queue, + block_buffer + block_index, + count - sizeof(updu)); + block_index += count - sizeof(updu); + block_size -= block_index; + rx_state_ = rx_inside_block; + return; + } + + /* Must be inside block. */ + QUEUE_REMOVE_UNITS(consumer->queue, block_buffer + block_index, count); + block_index += count; + block_size -= count; + + if (block_size) + return; /* More to come. */ + + /* + * Ok, the enter block has been received and reassembled, pass it to + * the updater for verification and programming. + */ + fw_upgrade_command_handler(block_buffer, block_index, &resp_size); + + shared_mem_release(block_buffer); + resp_value = block_buffer[0]; + QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, sizeof(resp_value)); + rx_state_ = rx_outside_block; +} + +static void upgrade_flush(struct consumer const *consumer) +{ +} + +struct consumer const upgrade_consumer = { + .queue = &usb_to_upgrade, + .ops = &((struct consumer_ops const) { + .written = upgrade_out_handler, + .flush = upgrade_flush, + }), +}; diff --git a/chip/g/usb_upgrade.h b/chip/g/usb_upgrade.h new file mode 100644 index 0000000000..7fe25b5895 --- /dev/null +++ b/chip/g/usb_upgrade.h @@ -0,0 +1,14 @@ +/* Copyright 2016 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 __CROS_EC_USB_UPGRADE_H +#define __CROS_EC_USB_UPGRADE_H + +#define UNOFFICIAL_USB_SUBCLASS_GOOGLE_CR50 0x53 + +/* Commands from host */ +#define UPGRADE_DONE 0xB007AB1E + +#endif /* __CROS_EC_USB_UPGRADE_H */ diff --git a/include/config.h b/include/config.h index d31270008f..2d5cac6189 100644 --- a/include/config.h +++ b/include/config.h @@ -2085,6 +2085,10 @@ #undef CONFIG_RO_HEAD_ROOM #undef CONFIG_RW_HEAD_ROOM +/* Firmware upgrade options. */ +/* Firmware updates using other than HC channel(s). */ +#undef CONFIG_NON_HC_FW_UPDATE + /*****************************************************************************/ /* * Include board and core configs, since those hold the CONFIG_ constants for a