summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chip/g/upgrade_fw.c210
-rw-r--r--chip/g/upgrade_fw.h49
-rw-r--r--chip/g/usb_upgrade.c83
3 files changed, 216 insertions, 126 deletions
diff --git a/chip/g/upgrade_fw.c b/chip/g/upgrade_fw.c
index ccb69dfb57..299c355e79 100644
--- a/chip/g/upgrade_fw.c
+++ b/chip/g/upgrade_fw.c
@@ -11,6 +11,8 @@
#include "hooks.h"
#include "include/compile_time_macros.h"
#include "memory.h"
+#include "system.h"
+#include "registers.h"
#include "uart.h"
#include "upgrade_fw.h"
@@ -18,59 +20,77 @@
#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,
-};
-
/*
- * 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.
+ * This structure defines flash offset ranges of the RO and RW images which
+ * are not currently active and as such could be overwritten with an update.
*/
-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)
+struct {
+ uint32_t ro_base_offset;
+ uint32_t ro_top_offset;
+ uint32_t rw_base_offset;
+ uint32_t rw_top_offset;
+} valid_sections;
+
+/* Pick sections where updates can go to based on current code addresses. */
+static void set_valid_sections(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;
+ switch (system_get_ro_image_copy()) {
+ case SYSTEM_IMAGE_RO:
+ valid_sections.ro_base_offset = CHIP_RO_B_MEM_OFF;
+ break;
+ case SYSTEM_IMAGE_RO_B:
+ valid_sections.ro_base_offset = CONFIG_RO_MEM_OFF;
+ break;
+ default:
+ CPRINTF("Failed to set RO image offsets\n");
+ return;
+ }
+
+ switch (system_get_image_copy()) {
+ case SYSTEM_IMAGE_RW:
+ valid_sections.rw_base_offset = CONFIG_RW_B_MEM_OFF;
+ break;
+ case SYSTEM_IMAGE_RW_B:
+ valid_sections.rw_base_offset = CONFIG_RW_MEM_OFF;
break;
+ default:
+ CPRINTF("Failed to set RW image offsets\n");
+ return;
}
+
+ valid_sections.ro_top_offset = valid_sections.ro_base_offset +
+ CONFIG_RO_SIZE - 0x800; /* 2K for certs! */
+
+ valid_sections.rw_top_offset = valid_sections.rw_base_offset +
+ CONFIG_RW_SIZE;
}
/* 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))
+ /* Is this an RW chunk? */
+ if (valid_sections.rw_top_offset &&
+ (block_offset >= valid_sections.rw_base_offset) &&
+ ((block_offset + body_size) <= valid_sections.rw_top_offset))
return 1;
- return 0;
+ /* Is this an RO chunk? */
+ if (valid_sections.ro_top_offset &&
+ (block_offset >= valid_sections.ro_base_offset) &&
+ ((block_offset + body_size) <= valid_sections.ro_top_offset))
+ return 1;
+ return 0;
+}
+
+/* Enable write access to the backup RO section. */
+static void open_ro_window(uint32_t offset, size_t size_b)
+{
+ GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) =
+ offset + CONFIG_PROGRAM_MEMORY_BASE;
+ GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = size_b - 1;
+ GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, EN, 1);
+ GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, RD_EN, 1);
+ GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, WR_EN, 1);
}
void fw_upgrade_command_handler(void *body,
@@ -79,67 +99,94 @@ void fw_upgrade_command_handler(void *body,
{
struct upgrade_command *cmd_body = body;
void *upgrade_data;
- uint8_t *rv = body;
+ uint8_t *error_code = body; /* Cache the address for code clarity. */
uint8_t sha1_digest[SHA_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);
+ *response_size = 1; /* One byte response unless this is a start PDU. */
if (cmd_size < sizeof(struct upgrade_command)) {
CPRINTF("%s:%d\n", __func__, __LINE__);
- *rv = UPGRADE_GEN_ERROR;
+ *error_code = UPGRADE_GEN_ERROR;
return;
}
body_size = cmd_size - sizeof(struct upgrade_command);
if (!cmd_body->block_base && !body_size) {
+ struct first_response_pdu *rpdu = body;
+ uint32_t base;
+ uint32_t size;
+
/*
- * This is the first message of the upgrade process, let's
- * determine the valid upgrade section and erase its contents.
+ * This is the connection establishment request, the response
+ * allows the server to decide what sections of the image to
+ * send to program into the flash.
*/
- 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;
- }
+ /* First, prepare the response structure. */
+ memset(rpdu, 0, sizeof(*rpdu));
+ *response_size = sizeof(*rpdu);
+ rpdu->protocol_version = htobe32(UPGRADE_PROTOCOL_VERSION);
+
+ /*
+ * Determine the valid upgrade sections.
+ */
+ set_valid_sections();
/*
- crosbug.com/p/54916
- wipe_nvram(); Do not keep any state around.
- */
+ * If there have been any problems when determining the valid
+ * secitons offsets/sizes - return an error code.
+ */
+ if (!valid_sections.ro_top_offset ||
+ !valid_sections.rw_top_offset) {
+ rpdu->return_value = htobe32(UPGRADE_GEN_ERROR);
+ return;
+ }
/*
- * Successful erase means that we need to return the base
- * address of the section to be programmed with the upgrade.
+ * No problems - let's erase the backup sections and return
+ * their descriptions to the server.
*/
- *(uint32_t *)body = htobe32(valid_section->sect_base_offset +
- CONFIG_PROGRAM_MEMORY_BASE);
- *response_size = sizeof(uint32_t);
+ base = valid_sections.ro_base_offset;
+ size = valid_sections.ro_top_offset - base;
+
+ /* backup RO write access needs to be enabled. */
+ open_ro_window(base, size);
+ if (flash_erase(base, size) != EC_SUCCESS) {
+ CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
+ __func__, __LINE__, base, size);
+ rpdu->return_value = htobe32(UPGRADE_ERASE_FAILURE);
+ return;
+ }
+
+ /* Now the RW backup section. */
+ base = valid_sections.rw_base_offset;
+ size = valid_sections.rw_top_offset - base;
+ if (flash_erase(base, size) != EC_SUCCESS) {
+ CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
+ __func__, __LINE__, base, size);
+ rpdu->return_value = htobe32(UPGRADE_ERASE_FAILURE);
+ return;
+ }
+
+ rpdu->vers3.backup_ro_offset =
+ htobe32(valid_sections.ro_base_offset);
+
+ rpdu->vers3.backup_rw_offset =
+ htobe32(valid_sections.rw_base_offset);
+
return;
}
/* Check if the block will fit into the valid area. */
- block_offset = be32toh(cmd_body->block_base) -
- CONFIG_PROGRAM_MEMORY_BASE;
+ block_offset = be32toh(cmd_body->block_base);
if (!valid_upgrade_chunk(block_offset, body_size)) {
- *rv = UPGRADE_BAD_ADDR;
+ *error_code = 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);
+ valid_sections.rw_base_offset,
+ valid_sections.rw_top_offset);
return;
}
@@ -149,7 +196,7 @@ void fw_upgrade_command_handler(void *body,
sha1_digest);
if (memcmp(sha1_digest, &cmd_body->block_digest,
sizeof(cmd_body->block_digest))) {
- *rv = UPGRADE_DATA_ERROR;
+ *error_code = 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,
@@ -162,21 +209,20 @@ void fw_upgrade_command_handler(void *body,
upgrade_data = cmd_body + 1;
if (flash_physical_write(block_offset, body_size, upgrade_data)
!= EC_SUCCESS) {
- *rv = UPGRADE_WRITE_FAILURE;
- CPRINTF("%s:%d upgrade write error\n",
- __func__, __LINE__);
+ *error_code = UPGRADE_WRITE_FAILURE;
+ CPRINTF("%s:%d upgrade write error\n", __func__, __LINE__);
return;
}
- /* Werify that data was written properly. */
+ /* Verify that data was written properly. */
if (memcmp(upgrade_data, (void *)
(block_offset + CONFIG_PROGRAM_MEMORY_BASE),
body_size)) {
- *rv = UPGRADE_VERIFY_ERROR;
+ *error_code = UPGRADE_VERIFY_ERROR;
CPRINTF("%s:%d upgrade verification error\n",
__func__, __LINE__);
return;
}
- *rv = UPGRADE_SUCCESS;
+ *error_code = UPGRADE_SUCCESS;
}
diff --git a/chip/g/upgrade_fw.h b/chip/g/upgrade_fw.h
index 8eee3dbdb3..19aa5d9e5c 100644
--- a/chip/g/upgrade_fw.h
+++ b/chip/g/upgrade_fw.h
@@ -8,14 +8,40 @@
#include <stddef.h>
-#define UPGRADE_PROTOCOL_VERSION 2
-/* This is the format of the header the flash update function expects. */
+/*
+ * This file contains structures used to facilitate cr50 firmware updates,
+ * which can be used on any g chip.
+ *
+ * The firmware update protocol consists of two phases: connection
+ * establishment and actual image transfer.
+ *
+ * Image transfer is done in 1K blocks. The host supplying the image
+ * encapsulates blocks in frames by prepending a header including the flash
+ * offset where the block is destined and its digest.
+ *
+ * The CR50 device responds to each frame with a confirmation which is 1 byte
+ * response. Zero value means success, non zero value is the error code
+ * reported by CR50.
+ *
+ * To establish the connection, the host sends a different frame, which
+ * contains no data and is destined to offset 0. Receiving such a frame
+ * signals the CR50 that the host intends to transfer a new image.
+ *
+ * Version 3 connection establishment response is 16 bytes in size, all values
+ * in network byte order. The first 4 bytes are the error code (if any), the
+ * second 4 bytes are the protocol version (set to 3) and then 4 byte offset
+ * of the RO section followed by the 4 byte offset of the RW section.
+ */
+
+#define UPGRADE_PROTOCOL_VERSION 3
+
+/* This is the format of the update frame header. */
struct upgrade_command {
uint32_t block_digest; /* first 4 bytes of sha1 of the rest of the
- * block.
+ * frame.
*/
- uint32_t block_base; /* Offset of this block into the flash SPI. */
+ uint32_t block_base; /* Offset of this frame into the flash SPI. */
/* The actual payload goes here. */
} __packed;
@@ -37,7 +63,7 @@ struct update_frame_header {
};
/*
- * Response to the message initiating the update sequence.
+ * Response to the connection establishment request.
*
* When responding to the very first packet of the upgrade sequence, the
* original USB update implementation was responding with a four byte value,
@@ -74,4 +100,17 @@ void fw_upgrade_command_handler(void *body,
size_t cmd_size,
size_t *response_size);
+
+/* Various upgrade 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,
+ UPGRADE_MALLOC_ERROR = 7,
+};
+
#endif /* ! __EC_CHIP_G_UPGRADE_FW_H */
diff --git a/chip/g/usb_upgrade.c b/chip/g/usb_upgrade.c
index f7d548ecc2..5c213668d1 100644
--- a/chip/g/usb_upgrade.c
+++ b/chip/g/usb_upgrade.c
@@ -26,12 +26,13 @@
* 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.
+ * The programmer reports results by putting the return value into the same
+ * buffer where the block was passed in. This wrapper retrieves the
+ * programmer's return value, and sends it back to the host. The return value
+ * is usually one byte in size, the only exception is the connection
+ * establishment phase where the return value is 16 bytes in size.
*
- * In the end of the successful image transfer and programming, the host send
+ * In the end of the successful image transfer and programming, the host sends
* the reset command, and the device reboots itself.
*/
@@ -117,7 +118,7 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
{
struct update_frame_header upfr;
size_t resp_size;
- uint32_t resp_value;
+ uint8_t resp_value;
uint64_t delta_time;
/* How much time since the previous USB callback? */
@@ -139,44 +140,43 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
}
if (rx_state_ == rx_idle) {
- struct first_response_pdu *startup_resp;
-
- if (!valid_transfer_start(consumer, count, &upfr))
+ /*
+ * The payload must be an update initiating PDU.
+ *
+ * The size of the response returned in the same buffer will
+ * exceed the received frame size; Let's make sure there is
+ * enough room for the response in the buffer.
+ */
+ union {
+ struct update_frame_header upfr;
+ struct {
+ uint32_t unused;
+ struct first_response_pdu startup_resp;
+ };
+ } u;
+
+ if (!valid_transfer_start(consumer, count, &u.upfr)) {
+ /*
+ * Someting is wrong, this payload is not a valid
+ * update start PDU. Let'w indicate this by returning
+ * a single byte error code.
+ */
+ resp_value = UPGRADE_GEN_ERROR;
+ QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, 1);
return;
+ }
CPRINTS("FW update: starting...");
-
- fw_upgrade_command_handler(&upfr.cmd, count -
+ fw_upgrade_command_handler(&u.upfr.cmd, count -
offsetof(struct update_frame_header,
cmd),
&resp_size);
- /*
- * The handler reuses receive buffer to return the result
- * value.
- */
- startup_resp = (struct first_response_pdu *)&upfr.cmd;
- if (resp_size == 4) {
- /*
- * The handler is happy, returned a 4 byte base
- * offset, it is in startup_resp->return_value now in
- * the proper byte order.
- */
- rx_state_ = rx_outside_block;
- } else {
- /*
- * This must be a single byte error code, convert it
- * into a 4 byte network order representation.
- */
- startup_resp->return_value = htobe32
- (*((uint8_t *)&startup_resp->return_value));
- }
- startup_resp->protocol_version =
- htobe32(UPGRADE_PROTOCOL_VERSION);
+ if (!u.startup_resp.return_value)
+ rx_state_ = rx_outside_block; /* We're in business. */
/* Let the host know what upgrader had to say. */
- QUEUE_ADD_UNITS(&upgrade_to_usb, startup_resp,
- sizeof(*startup_resp));
+ QUEUE_ADD_UNITS(&upgrade_to_usb, &u.startup_resp, resp_size);
return;
}
@@ -205,9 +205,10 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
command = be32toh(command);
if (command == UPGRADE_DONE) {
CPRINTS("FW update: done");
+
resp_value = 0;
- QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value,
- sizeof(resp_value));
+ QUEUE_ADD_UNITS(&upgrade_to_usb,
+ &resp_value, 1);
rx_state_ = rx_awaiting_reset;
return;
}
@@ -220,22 +221,26 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
* field of all zeros.
*/
if (valid_transfer_start(consumer, count, &upfr) ||
- (count != sizeof(upfr)))
+ (count != sizeof(upfr))) {
/*
* Instead of a block start message we received either
* a transfer start message or a chunk. We must have
* gotten out of sync with the host.
*/
+ resp_value = UPGRADE_GEN_ERROR;
+ QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, 1);
return;
+ }
/* Let's allocate a large enough buffer. */
block_size = be32toh(upfr.block_size) -
offsetof(struct update_frame_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.",
block_size);
+ resp_value = UPGRADE_MALLOC_ERROR;
+ QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, 1);
return;
}