summaryrefslogtreecommitdiff
path: root/chip/g/usb_upgrade.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/g/usb_upgrade.c')
-rw-r--r--chip/g/usb_upgrade.c121
1 files changed, 103 insertions, 18 deletions
diff --git a/chip/g/usb_upgrade.c b/chip/g/usb_upgrade.c
index 46e88949fb..acbec230b7 100644
--- a/chip/g/usb_upgrade.c
+++ b/chip/g/usb_upgrade.c
@@ -7,6 +7,7 @@
#include "common.h"
#include "console.h"
#include "consumer.h"
+#include "extension.h"
#include "queue_policies.h"
#include "shared_mem.h"
#include "system.h"
@@ -65,7 +66,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. */
};
enum rx_state rx_state_ = rx_idle;
@@ -74,7 +74,7 @@ static uint32_t block_size;
static uint32_t block_index;
/*
- * Verify that the contens of the USB rx queue is a valid transfer start
+ * Verify that the contents of the USB rx queue is a valid transfer start
* message from host, and if so - save its contents in the passed in
* update_frame_header structure.
*/
@@ -106,6 +106,82 @@ static int valid_transfer_start(struct consumer const *consumer, size_t count,
return 0;
return 1;
}
+static int try_vendor_command(struct consumer const *consumer, size_t count)
+{
+ struct update_frame_header ufh;
+ struct update_frame_header *cmd_buffer;
+ int rv = 0;
+
+ if (count < sizeof(ufh))
+ return 0; /* Too short to be a valid vendor command. */
+
+ /*
+ * Let's copy off the queue the upgrade frame header, to see if this
+ * is a channeled vendor command.
+ */
+ queue_peek_units(consumer->queue, &ufh, 0, sizeof(ufh));
+ if (be32toh(ufh.cmd.block_base) != CONFIG_EXTENSION_COMMAND)
+ return 0;
+
+ if (be32toh(ufh.block_size) != count) {
+ CPRINTS("%s: problem: block size and count mismatch (%d != %d)",
+ __func__, be32toh(ufh.block_size), count);
+ return 0;
+ }
+
+ if (shared_mem_acquire(count, (char **)&cmd_buffer)
+ != EC_SUCCESS) {
+ CPRINTS("%s: problem: failed to allocate block of %d",
+ __func__, count);
+ return 0;
+ }
+
+ /* Get the entire command, don't remove it from the queue just yet. */
+ queue_peek_units(consumer->queue, cmd_buffer, 0, count);
+
+ /* Looks like this is a vendor command, let's verify it. */
+ if (usb_pdu_valid(&cmd_buffer->cmd,
+ count - offsetof(struct update_frame_header, cmd))) {
+ uint16_t *subcommand;
+ size_t response_size;
+ size_t request_size;
+ /*
+ * Should be enough for any vendor command/response. We'll
+ * generate an error if it is not.
+ */
+ uint8_t subcommand_body[32];
+
+ /* looks good, let's process it. */
+ rv = 1;
+
+ /* Now remove if from the queue. */
+ queue_advance_head(consumer->queue, count);
+
+ subcommand = (uint16_t *)(cmd_buffer + 1);
+ request_size = count - sizeof(struct update_frame_header) -
+ sizeof(*subcommand);
+
+ if (request_size > sizeof(subcommand_body)) {
+ CPRINTS("%s: vendor command payload too big (%d)",
+ __func__, request_size);
+ subcommand_body[0] = VENDOR_RC_REQUEST_TOO_BIG;
+ response_size = 1;
+ } else {
+ memcpy(subcommand_body, subcommand + 1, request_size);
+ response_size = sizeof(subcommand_body);
+ usb_extension_route_command(be16toh(*subcommand),
+ subcommand_body,
+ request_size,
+ &response_size);
+ }
+
+ QUEUE_ADD_UNITS(&upgrade_to_usb,
+ subcommand_body, response_size);
+ }
+ shared_mem_release(cmd_buffer);
+
+ return rv;
+}
/*
* When was last time a USB callback was called, in microseconds, free running
@@ -113,6 +189,12 @@ static int valid_transfer_start(struct consumer const *consumer, size_t count,
*/
static uint64_t prev_activity_timestamp;
+/*
+ * 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;
+
/* Called to deal with data from the host */
static void upgrade_out_handler(struct consumer const *consumer, size_t count)
{
@@ -155,9 +237,13 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
};
} u;
+ /* Check is this is a channeled TPM extension command. */
+ if (try_vendor_command(consumer, count))
+ return;
+
if (!valid_transfer_start(consumer, count, &u.upfr)) {
/*
- * Someting is wrong, this payload is not a valid
+ * Something is wrong, this payload is not a valid
* update start PDU. Let'w indicate this by returning
* a single byte error code.
*/
@@ -173,26 +259,16 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
cmd),
&resp_size);
- if (!u.startup_resp.return_value)
+ if (!u.startup_resp.return_value) {
rx_state_ = rx_outside_block; /* We're in business. */
+ data_was_transferred = 0; /* No data received yet. */
+ }
/* Let the host know what upgrader had to say. */
QUEUE_ADD_UNITS(&upgrade_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
@@ -207,11 +283,15 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
if (command == UPGRADE_DONE) {
CPRINTS("FW update: done");
- fw_upgrade_complete();
+ if (data_was_transferred) {
+ fw_upgrade_complete();
+ data_was_transferred = 0;
+ }
+
resp_value = 0;
QUEUE_ADD_UNITS(&upgrade_to_usb,
&resp_value, 1);
- rx_state_ = rx_awaiting_reset;
+ rx_state_ = rx_idle;
return;
}
}
@@ -308,6 +388,11 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
*/
fw_upgrade_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(&upgrade_to_usb, &resp_value, sizeof(resp_value));
rx_state_ = rx_outside_block;