diff options
-rw-r--r-- | chip/g/upgrade_fw.c | 210 | ||||
-rw-r--r-- | chip/g/upgrade_fw.h | 49 | ||||
-rw-r--r-- | chip/g/usb_upgrade.c | 83 |
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; } |