mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-31 02:51:26 +00:00
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>
This commit is contained in:
committed by
chrome-bot
parent
20c439be20
commit
2f5e46cef4
@@ -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,
|
||||
};
|
||||
|
||||
/*
|
||||
* The payload of the update 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 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. */
|
||||
/*
|
||||
* Pick the section where updates can go to based on current code address.
|
||||
*
|
||||
* TODO(b/36375666): Each board/chip should be able to re-define this.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
void fw_update_command_handler(void *body,
|
||||
size_t cmd_size,
|
||||
size_t *response_size)
|
||||
static void new_chunk_written(uint32_t block_offset)
|
||||
{
|
||||
struct update_command *cmd_body = body;
|
||||
uint8_t *rv = body;
|
||||
size_t body_size;
|
||||
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();
|
||||
|
||||
/*
|
||||
* A single byte response, unless this is the first message in the
|
||||
* programming sequence.
|
||||
* If there have been any problems when determining the valid
|
||||
* section offsets/sizes - return an error code.
|
||||
*/
|
||||
*response_size = sizeof(*rv);
|
||||
|
||||
body_size = cmd_size - offsetof(struct update_command, block_body);
|
||||
if (body_size < 0) {
|
||||
if (!valid_section) {
|
||||
CPRINTF("%s:%d\n", __func__, __LINE__);
|
||||
*rv = UPDATE_GEN_ERROR;
|
||||
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)
|
||||
{
|
||||
struct update_command *cmd_body = body;
|
||||
void *update_data;
|
||||
uint8_t *error_code = body; /* Cache the address for code clarity. */
|
||||
size_t body_size;
|
||||
uint32_t block_offset;
|
||||
|
||||
*response_size = 1; /* One byte response unless this is a start PDU. */
|
||||
|
||||
if (cmd_size < sizeof(struct update_command)) {
|
||||
CPRINTF("%s:%d\n", __func__, __LINE__);
|
||||
*error_code = UPDATE_GEN_ERROR;
|
||||
return;
|
||||
}
|
||||
body_size = cmd_size - sizeof(struct update_command);
|
||||
|
||||
if (!cmd_body->block_base && !body_size) {
|
||||
int ret;
|
||||
/*
|
||||
* This is the first message of the update process, let's
|
||||
* determine the valid update section and erase its contents.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
struct first_response_pdu *rpdu = body;
|
||||
|
||||
/*
|
||||
* Successful erase means that we need to return the base
|
||||
* address of the section to be programmed with the update.
|
||||
* 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.
|
||||
*/
|
||||
*(uint32_t *)body = htobe32(valid_section->sect_base_offset +
|
||||
CONFIG_PROGRAM_MEMORY_BASE);
|
||||
*response_size = sizeof(uint32_t);
|
||||
|
||||
/* First, prepare the response structure. */
|
||||
memset(rpdu, 0, sizeof(*rpdu));
|
||||
*response_size = sizeof(*rpdu);
|
||||
rpdu->protocol_version = htobe32(UPDATE_PROTOCOL_VERSION);
|
||||
|
||||
/* 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)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* The payload must be an update initiating PDU.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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;
|
||||
|
||||
if (!valid_transfer_start(consumer, count, &updu))
|
||||
/* Check is this is a channeled TPM extension command. */
|
||||
if (try_vendor_command(consumer, count))
|
||||
return;
|
||||
|
||||
CPRINTS("FW update: starting...");
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
fw_update_command_handler(&updu.cmd, count -
|
||||
offsetof(struct update_pdu_header,
|
||||
CPRINTS("FW update: starting...");
|
||||
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.
|
||||
* has been received, let's abort the transfer.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user