diff options
author | Nicolas Boichat <drinkcat@google.com> | 2017-03-22 10:50:39 +0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-04-15 04:08:07 -0700 |
commit | 2f5e46cef48ec0671fd014bbdb38b060339db998 (patch) | |
tree | 147061592ddb8fa0ba504b5b1ba93dbdd1b23bd4 | |
parent | 20c439be209a9cc0bb949ad21f289c453126395f (diff) | |
download | chrome-ec-2f5e46cef48ec0671fd014bbdb38b060339db998.tar.gz |
common/update: Update common code updater to latest chip/g version
Let's move to protocol version 6, which provides most of the new features
we want to update generic EC firmware.
Note that this matches chip/g version as of commit 0e5497db6,
plus the following uncommited chip/g patches (CL:458364):
c73af7dd2 chip/g/upgrade: Clarify pdu/frame terminology
baea0a8c7 chip/g/upgrade: Rename SIGNED_TRANSFER_SIZE to UPDATE_PDU_SIZE
d6e41b75c chip/g/upgrade: Remove cr50-specific upgrade subclass and
protocol
3dc0b9a25 chip/g/upgrade: Rename upgrade to update
13436f9b9 chip/g/upgrade: Split rdpu initialization to a separate function
fab9a0936 chip/g/upgrade: Minor formatting fixups
8161ef7c0 chip/g/upgrade: Fix valid_transfer_start logic
bd6d79434 chip/g/upgrade: Fix logic for short USB packets within frames
b09e252ed chip/g/upgrade: Improve error handling
Then:
diff -u include/update_fw.h chip/g/upgrade_fw.h
diff -u common/usb_update.c chip/g/usb_upgrade.c
diff -u common/update_fw.c chip/g/upgrade_fw.c
Only shows chip/g specific differences.
BRANCH=none
BUG=b:36375666
BUG=b:35587171
TEST=make buildall -j
TEST=Can update hammer over USB using usb_updater2
Change-Id: I5b0f0281d844972dab572955d5495f808127e523
Reviewed-on: https://chromium-review.googlesource.com/458321
Commit-Ready: Nicolas Boichat <drinkcat@chromium.org>
Tested-by: Nicolas Boichat <drinkcat@chromium.org>
Reviewed-by: Nick Sanders <nsanders@chromium.org>
Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
-rw-r--r-- | common/update_fw.c | 253 | ||||
-rw-r--r-- | common/usb_update.c | 271 | ||||
-rw-r--r-- | include/update_fw.h | 145 |
3 files changed, 435 insertions, 234 deletions
diff --git a/common/update_fw.c b/common/update_fw.c index de374ec4dd..585977f774 100644 --- a/common/update_fw.c +++ b/common/update_fw.c @@ -15,35 +15,13 @@ #define CPRINTF(format, args...) cprintf(CC_USB, format, ## args) -/* Various update extension command return values. */ -enum return_value { - UPDATE_SUCCESS = 0, - UPDATE_BAD_ADDR = 1, - UPDATE_ERASE_FAILURE = 2, - UPDATE_DATA_ERROR = 3, - UPDATE_WRITE_FAILURE = 4, - UPDATE_VERIFY_ERROR = 5, - UPDATE_GEN_ERROR = 6, -}; +const struct section_descriptor *valid_section; /* - * The payload of the update command. (Integer values in network byte order). + * Pick the section where updates can go to based on current code address. * - * 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'. + * TODO(b/36375666): Each board/chip should be able to re-define this. */ -struct update_command { - uint32_t block_digest; - uint32_t block_base; - uint8_t block_body[0]; -} __packed; - - -const struct section_descriptor *valid_section; - -/* Pick the section where updates can go to based on current code address. */ static int set_valid_section(void) { int i; @@ -65,103 +43,210 @@ static int set_valid_section(void) return EC_SUCCESS; } -/* Verify that the passed in block fits into the valid area. */ -static int valid_update_chunk(uint32_t block_offset, size_t body_size) +/* + * Verify that the passed in block fits into the valid area. If it does, and + * is destined to the base address of the area - erase the area contents. + * + * Return success, or indication of an erase failure or chunk not fitting into + * valid area. + * + * TODO(b/36375666): Each board/chip should be able to re-define this. + */ +static uint8_t check_update_chunk(uint32_t block_offset, size_t body_size) { + uint32_t base; + uint32_t size; + + /* Is this an RW chunk? */ if (valid_section && (block_offset >= valid_section->sect_base_offset) && - ((block_offset + body_size) <= valid_section->sect_top_offset)) - return 1; + ((block_offset + body_size) <= valid_section->sect_top_offset)) { + base = valid_section->sect_base_offset; + size = valid_section->sect_top_offset - + valid_section->sect_base_offset; + /* + * If this is the first chunk for this section, it needs to + * be erased. + */ + if (block_offset == base) { + if (flash_physical_erase(base, size) != EC_SUCCESS) { + CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n", + __func__, __LINE__, base, size); + return UPDATE_ERASE_FAILURE; + } + } + + return UPDATE_SUCCESS; + } + + CPRINTF("%s:%d %x, %d section base %x top %x\n", + __func__, __LINE__, + block_offset, body_size, + valid_section->sect_base_offset, + valid_section->sect_top_offset); + + return UPDATE_BAD_ADDR; + +} + +/* TODO(b/36375666): These need to be overridden for chip/g. */ +int update_pdu_valid(struct update_command *cmd_body, size_t cmd_size) +{ + return 1; +} + +static int chunk_came_too_soon(uint32_t block_offset) +{ return 0; } +static void new_chunk_written(uint32_t block_offset) +{ +} + +static int contents_allowed(uint32_t block_offset, + size_t body_size, void *update_data) +{ + return 1; +} + +/* + * Setup internal state (e.g. valid sections, and fill first response). + * + * Assumes rpdu is already prefilled with 0, and that version has already + * been set. May set a return_value != 0 on error. + * + * TODO(b/36375666): Each board/chip should be able to re-define this. + */ +void fw_update_start(struct first_response_pdu *rpdu) +{ + /* Determine the valid update section. */ + set_valid_section(); + + /* + * If there have been any problems when determining the valid + * section offsets/sizes - return an error code. + */ + if (!valid_section) { + CPRINTF("%s:%d\n", __func__, __LINE__); + rpdu->return_value = htobe32(UPDATE_GEN_ERROR); + return; + } + + /* + * TODO(b/36375666): We reuse the same structure as cr50 updater, but + * there isn't a whole lot that can be shared... We should probably + * switch to a board-specific response packet (at least common vs + * cr50-specific). + */ + rpdu->backup_ro_offset = htobe32(valid_section->sect_base_offset); + rpdu->backup_rw_offset = 0x0; + + /* RO header information. */ + rpdu->shv[0].minor = 0; + rpdu->shv[0].major = 0; + rpdu->shv[0].epoch = 0; + rpdu->keyid[0] = 0; + + /* RW header information. */ + rpdu->shv[1].minor = 0; + rpdu->shv[1].major = 0; + rpdu->shv[1].epoch = 0; + + rpdu->keyid[1] = 0; +} + void fw_update_command_handler(void *body, - size_t cmd_size, - size_t *response_size) + size_t cmd_size, + size_t *response_size) { struct update_command *cmd_body = body; - uint8_t *rv = body; + void *update_data; + uint8_t *error_code = body; /* Cache the address for code clarity. */ 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. */ - body_size = cmd_size - offsetof(struct update_command, block_body); - if (body_size < 0) { + if (cmd_size < sizeof(struct update_command)) { CPRINTF("%s:%d\n", __func__, __LINE__); - *rv = UPDATE_GEN_ERROR; + *error_code = UPDATE_GEN_ERROR; return; } + body_size = cmd_size - sizeof(struct update_command); if (!cmd_body->block_base && !body_size) { - int ret; + struct first_response_pdu *rpdu = body; + /* - * This is the first message of the update process, let's - * determine the valid update 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. */ - ret = set_valid_section(); - if (ret) { - CPRINTF("%s:%d no valid section\n", __func__, __LINE__); - return; - } - 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 = UPDATE_ERASE_FAILURE; - return; - } + /* First, prepare the response structure. */ + memset(rpdu, 0, sizeof(*rpdu)); + *response_size = sizeof(*rpdu); + rpdu->protocol_version = htobe32(UPDATE_PROTOCOL_VERSION); - /* - * Successful erase means that we need to return the base - * address of the section to be programmed with the update. - */ - *(uint32_t *)body = htobe32(valid_section->sect_base_offset + - CONFIG_PROGRAM_MEMORY_BASE); - *response_size = sizeof(uint32_t); + /* Setup internal state (e.g. valid sections, and fill rpdu) */ + fw_update_start(rpdu); + return; + } + + block_offset = be32toh(cmd_body->block_base); + + if (!update_pdu_valid(cmd_body, cmd_size)) { + *error_code = UPDATE_DATA_ERROR; + return; + } + + update_data = cmd_body + 1; + if (!contents_allowed(block_offset, body_size, update_data)) { + *error_code = UPDATE_ROLLBACK_ERROR; return; } /* Check if the block will fit into the valid area. */ - block_offset = be32toh(cmd_body->block_base) - - CONFIG_PROGRAM_MEMORY_BASE; - if (!valid_update_chunk(block_offset, body_size)) { - *rv = UPDATE_BAD_ADDR; - CPRINTF("%s:%d Write out of range %x ..+%d (Window %x - %x)\n", - __func__, __LINE__, - block_offset, body_size, - valid_section->sect_base_offset, - valid_section->sect_top_offset); + *error_code = check_update_chunk(block_offset, body_size); + if (*error_code) + return; + + if (chunk_came_too_soon(block_offset)) { + *error_code = UPDATE_RATE_LIMIT_ERROR; return; } - if (flash_physical_write(block_offset, body_size, - cmd_body->block_body) != EC_SUCCESS) { - *rv = UPDATE_WRITE_FAILURE; - CPRINTF("%s:%d update write error @0x%x:%x\n", - __func__, __LINE__, block_offset, body_size); + /* + * TODO(b/36375666): chip/g code has some cr50-specific stuff right + * here, which should probably be merged into contents_allowed... + */ + + CPRINTF("update: 0x%x\n", block_offset + CONFIG_PROGRAM_MEMORY_BASE); + if (flash_physical_write(block_offset, body_size, update_data) + != EC_SUCCESS) { + *error_code = UPDATE_WRITE_FAILURE; + CPRINTF("%s:%d update write error\n", __func__, __LINE__); return; } - /* Werify that data was written properly. */ - if (memcmp(cmd_body->block_body, (void *) + new_chunk_written(block_offset); + + /* Verify that data was written properly. */ + if (memcmp(update_data, (void *) (block_offset + CONFIG_PROGRAM_MEMORY_BASE), body_size)) { - *rv = UPDATE_VERIFY_ERROR; + *error_code = UPDATE_VERIFY_ERROR; CPRINTF("%s:%d update verification error\n", __func__, __LINE__); return; } - *rv = UPDATE_SUCCESS; + *error_code = UPDATE_SUCCESS; +} + +/* TODO(b/36375666): This need to be overridden for chip/g. */ +void fw_update_complete(void) +{ } diff --git a/common/usb_update.c b/common/usb_update.c index 382677835f..6a85f19424 100644 --- a/common/usb_update.c +++ b/common/usb_update.c @@ -7,13 +7,11 @@ #include "common.h" #include "console.h" #include "consumer.h" -#include "include/compile_time_macros.h" +#include "extension.h" #include "queue_policies.h" #include "shared_mem.h" #include "system.h" #include "update_fw.h" -#include "usb_api.h" -#include "usb_hw.h" #include "usb-stream.h" #include "util.h" @@ -30,12 +28,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. */ @@ -68,26 +67,6 @@ enum rx_state { 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 update_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 update_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; @@ -96,12 +75,12 @@ static uint32_t block_size; static uint32_t block_index; /* - * Verify that the contens of the USB rx queue is a valid transfer start - * message from host, and if so - save its contents in the passed in - * update_pdu_header structure. + * Fetches a transfer start frame from the queue. This can be either an update + * start frame (block_size = 0, all of cmd = 0), or the beginning of a frame + * (block_size > 0, valid block_base in cmd). */ -static int valid_transfer_start(struct consumer const *consumer, size_t count, - struct update_pdu_header *pupdu) +static int fetch_transfer_start(struct consumer const *consumer, size_t count, + struct update_frame_header *pupfr) { int i; @@ -109,40 +88,61 @@ static int valid_transfer_start(struct consumer const *consumer, size_t count, * Let's just make sure we drain the queue no matter what the contents * are. This way they won't be in the way during next callback, even * if these contents are not what's expected. + * + * Note: If count > sizeof(*pupfr), pupfr will be corrupted. This is + * ok as we will immediately fail after this. */ i = count; while (i > 0) { - QUEUE_REMOVE_UNITS(consumer->queue, pupdu, - MIN(i, sizeof(*pupdu))); - i -= sizeof(*pupdu); + QUEUE_REMOVE_UNITS(consumer->queue, pupfr, + MIN(i, sizeof(*pupfr))); + i -= sizeof(*pupfr); } - if (count != sizeof(struct update_pdu_header)) { + if (count != sizeof(struct update_frame_header)) { CPRINTS("FW update: wrong first block, size %d", count); return 0; } - /* In the first block the payload (updu.cmd) must be all zeros. */ - for (i = 0; i < sizeof(pupdu->cmd); i++) - if (((uint8_t *)&pupdu->cmd)[i]) - return 0; return 1; } +static int try_vendor_command(struct consumer const *consumer, size_t count) +{ + /* TODO(b/35587171): Vendor commands not implemented (yet). */ + return 0; +} + /* * When was last time a USB callback was called, in microseconds, free running * timer. */ static uint64_t prev_activity_timestamp; -#define UPDATE_PROTOCOL_VERSION 2 +/* + * A flag indicating that at least one valid PDU containing flash update block + * has been received in the current transfer session. + */ +static uint8_t data_was_transferred; + +/* Reply with an error to remote side, reset state. */ +static void send_error_reset(uint8_t resp_value) +{ + QUEUE_ADD_UNITS(&update_to_usb, &resp_value, 1); + rx_state_ = rx_idle; + data_was_transferred = 0; + if (block_buffer) { + shared_mem_release(block_buffer); + block_buffer = NULL; + } +} /* Called to deal with data from the host */ static void update_out_handler(struct consumer const *consumer, size_t count) { - struct update_pdu_header updu; + 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? */ @@ -165,67 +165,59 @@ static void update_out_handler(struct consumer const *consumer, size_t count) if (rx_state_ == rx_idle) { /* - * When responding to the very first packet of the update - * sequence, the original implementation was responding with a - * four byte value, just as to any other block of the transfer - * sequence. - * - * It became clear that there is a need to be able to enhance - * the update protocol, while stayng backwards compatible. To - * achieve that we respond to the very first packet with an 8 - * byte value, the first 4 bytes the same as before, the - * second 4 bytes - the protocol version number. + * The payload must be an update initiating PDU. * - * This way if on the host side receiving of a four byte value - * in response to the first packet is an indication of the - * 'legacy' protocol, version 0. Receiving of an 8 byte - * response would communicate the protocol version in the - * second 4 bytes. + * 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. */ - struct { - uint32_t value; - uint32_t version; - } startup_resp; + union { + struct update_frame_header upfr; + struct { + uint32_t unused; + struct first_response_pdu startup_resp; + }; + } u; + + /* Check is this is a channeled TPM extension command. */ + if (try_vendor_command(consumer, count)) + return; - if (!valid_transfer_start(consumer, count, &updu)) + /* + * An update start PDU is a command without any payload, with + * digest = 0, and base = 0. + */ + if (!fetch_transfer_start(consumer, count, &u.upfr) || + be32toh(u.upfr.block_size) != + sizeof(struct update_frame_header) || + u.upfr.cmd.block_digest != 0 || + u.upfr.cmd.block_base != 0) { + /* + * Something is wrong, this payload is not a valid + * update start PDU. Let'w indicate this by returning + * a single byte error code. + */ + CPRINTS("FW update: invalid start."); + send_error_reset(UPDATE_GEN_ERROR); return; + } CPRINTS("FW update: starting..."); - - fw_update_command_handler(&updu.cmd, count - - offsetof(struct update_pdu_header, + fw_update_command_handler(&u.upfr.cmd, count - + offsetof(struct update_frame_header, cmd), &resp_size); - if (resp_size == 4) { - /* Already in network order. */ - startup_resp.value = updu.resp; - rx_state_ = rx_outside_block; - } else { - /* This must be a single byte error code. */ - startup_resp.value = htobe32(*((uint8_t *)&updu.resp)); + if (!u.startup_resp.return_value) { + rx_state_ = rx_outside_block; /* We're in business. */ + data_was_transferred = 0; /* No data received yet. */ } - startup_resp.version = htobe32(UPDATE_PROTOCOL_VERSION); - /* Let the host know what updater had to say. */ - QUEUE_ADD_UNITS(&update_to_usb, &startup_resp, - sizeof(startup_resp)); + QUEUE_ADD_UNITS(&update_to_usb, &u.startup_resp, resp_size); 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 @@ -239,40 +231,49 @@ static void update_out_handler(struct consumer const *consumer, size_t count) command = be32toh(command); if (command == UPDATE_DONE) { CPRINTS("FW update: done"); + + if (data_was_transferred) { + fw_update_complete(); + data_was_transferred = 0; + } + resp_value = 0; - QUEUE_ADD_UNITS(&update_to_usb, &resp_value, - sizeof(resp_value)); - rx_state_ = rx_awaiting_reset; + QUEUE_ADD_UNITS(&update_to_usb, + &resp_value, 1); + rx_state_ = rx_idle; return; - } else { - CPRINTS("Unexpected packet command 0x%x", - command); } } /* * At this point we expect a block start message. It is - * sizeof(updu) bytes in size, but is not the transfer start - * message, which also is of that size AND has the command - * field of all zeros. + * sizeof(upfr) bytes in size. */ - if (valid_transfer_start(consumer, count, &updu) || - (count != sizeof(updu))) - /* - * 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. - */ + if (!fetch_transfer_start(consumer, count, &upfr)) { + CPRINTS("Invalid block start."); + send_error_reset(UPDATE_GEN_ERROR); return; + } /* Let's allocate a large enough buffer. */ - block_size = be32toh(updu.block_size) - - offsetof(struct update_pdu_header, cmd); + block_size = be32toh(upfr.block_size) - + offsetof(struct update_frame_header, cmd); + + /* + * Only update start PDU is allowed to have a size 0 payload. + */ + if (block_size <= sizeof(struct update_command) || + block_size > (UPDATE_PDU_SIZE + + sizeof(struct update_command))) { + CPRINTS("Invalid block size (%d).", block_size); + send_error_reset(UPDATE_GEN_ERROR); + return; + } + 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); + != EC_SUCCESS) { + CPRINTS("Alloc error (%d).", block_size); + send_error_reset(UPDATE_MALLOC_ERROR); return; } @@ -280,9 +281,9 @@ static void update_out_handler(struct consumer const *consumer, size_t count) * Copy the rest of the message into the block buffer to pass * to the updater. */ - block_index = sizeof(updu) - - offsetof(struct update_pdu_header, cmd); - memcpy(block_buffer, &updu.cmd, block_index); + block_index = sizeof(upfr) - + offsetof(struct update_frame_header, cmd); + memcpy(block_buffer, &upfr.cmd, block_index); block_size -= block_index; rx_state_ = rx_inside_block; return; @@ -294,39 +295,14 @@ static void update_out_handler(struct consumer const *consumer, size_t count) block_size -= count; if (block_size) { - if (count == sizeof(updu)) { + if (count <= sizeof(upfr)) { /* * A block header size instead of chunk size message - * has been received. There must have been some packet - * loss and the host is restarting this block. - * - * Let's copy its contents into the header structure. - */ - memcpy(&updu, block_buffer + block_index - count, - count); - - - /* And re-allocate a large enough buffer. */ - shared_mem_release(block_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.", block_size); - return; - } - - /* - * Copy the rest of the message into the block buffer - * to pass to the updater. + * has been received, let's abort the transfer. */ - block_index = sizeof(updu) - - offsetof(struct update_pdu_header, cmd); - memcpy(block_buffer, &updu.cmd, block_index); - block_size -= block_index; + CPRINTS("Unexpected header"); + send_error_reset(UPDATE_GEN_ERROR); + return; } return; /* More to come. */ } @@ -337,6 +313,11 @@ static void update_out_handler(struct consumer const *consumer, size_t count) */ fw_update_command_handler(block_buffer, block_index, &resp_size); + /* + * There was at least an attempt to program the flash, set the + * flag. + */ + data_was_transferred = 1; resp_value = block_buffer[0]; QUEUE_ADD_UNITS(&update_to_usb, &resp_value, sizeof(resp_value)); rx_state_ = rx_outside_block; diff --git a/include/update_fw.h b/include/update_fw.h index f1b76e1033..8f7148f6ed 100644 --- a/include/update_fw.h +++ b/include/update_fw.h @@ -8,9 +8,149 @@ #include <stddef.h> + +/* + * This file contains structures used to facilitate EC firmware updates + * over USB (and over TPM for cr50). + * + * 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 PDUs by prepending a header including the flash + * offset where the block is destined and its digest. + * + * The EC device responds to each PDU with a confirmation which is 1 byte + * response. Zero value means success, non zero value is the error code + * reported by EC. + * + * To establish the connection, the host sends a different PDU, which + * contains no data and is destined to offset 0. Receiving such a PDU + * signals the EC that the host intends to transfer a new image. + * + * The connection establishment response is described by the + * first_response_pdu structure below. + */ + +#define UPDATE_PROTOCOL_VERSION 6 + +/* + * This is the format of the update PDU header. + * + * block digest: the first four bytes of the sha1 digest of the rest of the + * structure (can be 0 on boards where digest is ignored). + * block_base: offset of this PDU into the flash SPI. + */ +struct update_command { + uint32_t block_digest; + uint32_t block_base; + /* The actual payload goes here. */ +} __packed; + +/* + * This is the frame format the host uses when sending update PDUs over USB. + * + * The PDUs are up to 1K bytes in size, they are fragmented into USB chunks of + * 64 bytes each and reassembled on the receive side before being passed to + * the flash update function. + * + * The flash update function receives the unframed PDU body (starting at the + * cmd field below), and puts its reply into the same buffer the PDU was in. + */ +struct update_frame_header { + uint32_t block_size; /* Total frame size, including this field. */ + struct update_command cmd; +}; + +/* + * A convenience structure which allows to group together various revision + * fields of the header created by the signer. + * + * These fields are compared when deciding if versions of two images are the + * same or when deciding which one of the available images to run. + */ +struct signed_header_version { + uint32_t minor; + uint32_t major; + uint32_t epoch; +}; + +/* + * Response to the connection establishment request. + * + * When responding to the very first packet of the update sequence, the + * original USB update implementation was responding with a four byte value, + * just as to any other block of the transfer sequence. + * + * It became clear that there is a need to be able to enhance the update + * protocol, while staying backwards compatible. + * + * All newer protocol versions (starting with version 2) respond to the very + * first packet with an 8 byte or larger response, where the first 4 bytes are + * a version specific data, and the second 4 bytes - the protocol version + * number. + * + * This way the host receiving of a four byte value in response to the first + * packet is considered an indication of the target running the 'legacy' + * protocol, version 1. Receiving of an 8 byte or longer response would + * communicates the protocol version in the second 4 bytes. + */ +struct first_response_pdu { + uint32_t return_value; + + /* The below fields are present in versions 2 and up. */ + uint32_t protocol_version; + + /* The below fields are present in versions 3 and up. */ + uint32_t backup_ro_offset; + uint32_t backup_rw_offset; + + /* The below fields are present in versions 4 and up. */ + /* Versions of the currently active RO and RW sections. */ + struct signed_header_version shv[2]; + + /* The below fields are present in versions 5 and up */ + /* keyids of the currently active RO and RW sections. */ + uint32_t keyid[2]; +}; + /* TODO: Handle this in update_fw.c, not usb_update.c */ #define UPDATE_DONE 0xB007AB1E +void fw_update_command_handler(void *body, + size_t cmd_size, + size_t *response_size); + +/* Used to tell fw update the update ran successfully and is finished */ +void fw_update_complete(void); + +/* Verify integrity of the PDU received. */ +int update_pdu_valid(struct update_command *cmd_body, size_t cmd_size); + +/* Various update command return values. */ +enum { + UPDATE_SUCCESS = 0, + UPDATE_BAD_ADDR = 1, + UPDATE_ERASE_FAILURE = 2, + UPDATE_DATA_ERROR = 3, + UPDATE_WRITE_FAILURE = 4, + UPDATE_VERIFY_ERROR = 5, + UPDATE_GEN_ERROR = 6, + UPDATE_MALLOC_ERROR = 7, + UPDATE_ROLLBACK_ERROR = 8, + UPDATE_RATE_LIMIT_ERROR = 9, +}; + +/* + * This is the size of the update frame payload, unless this is the last chunk + * of the image. + * + * TODO(b/36375666): Some boards may not be able to allocate that much memory. + */ +#define UPDATE_PDU_SIZE 1024 + +/* TODO(b/36375666): The structures below might not belong in this file. */ + /* * This array defines possible sections available for the firmare update. * The section which does not map the current execting code is picked as the @@ -28,9 +168,4 @@ struct section_descriptor { extern const struct section_descriptor * const rw_sections; extern const int num_rw_sections; - -void fw_update_command_handler(void *body, - size_t cmd_size, - size_t *response_size); - #endif /* ! __CROS_EC_UPDATE_FW_H */ |