diff options
Diffstat (limited to 'common/usb_update.c')
-rw-r--r-- | common/usb_update.c | 594 |
1 files changed, 0 insertions, 594 deletions
diff --git a/common/usb_update.c b/common/usb_update.c deleted file mode 100644 index 64875fd798..0000000000 --- a/common/usb_update.c +++ /dev/null @@ -1,594 +0,0 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "byteorder.h" -#include "common.h" -#include "console.h" -#include "consumer.h" -#include "curve25519.h" -#include "extension.h" -#include "flash.h" -#include "queue_policies.h" -#include "host_command.h" -#include "rollback.h" -#include "rwsig.h" -#include "sha256.h" -#include "system.h" -#include "uart.h" -#include "update_fw.h" -#include "usb-stream.h" -#include "util.h" - -#define CPRINTS(format, args...) cprints(CC_USB, format, ## args) -#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args) - -/* - * This file is an adaptation layer between the USB interface and the firmware - * update engine. The engine expects to receive long blocks of data, 1K or so - * in size, prepended by the offset where the data needs to be programmed into - * the flash and a 4 byte integrity check value. - * - * The USB transfer, on the other hand, operates on much shorter chunks of - * data, typically 64 bytes in this case. This module reassembles firmware - * programming blocks from the USB chunks, and invokes the programmer passing - * it the full block. - * - * 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 sends - * the reset command, and the device reboots itself. - */ - -struct consumer const update_consumer; -struct usb_stream_config const usb_update; - -static struct queue const update_to_usb = QUEUE_DIRECT(64, uint8_t, - null_producer, - usb_update.consumer); -static struct queue const usb_to_update = QUEUE_DIRECT(64, uint8_t, - usb_update.producer, - update_consumer); - -USB_STREAM_CONFIG_FULL(usb_update, - USB_IFACE_UPDATE, - USB_CLASS_VENDOR_SPEC, - USB_SUBCLASS_GOOGLE_UPDATE, - USB_PROTOCOL_GOOGLE_UPDATE, - USB_STR_UPDATE_NAME, - USB_EP_UPDATE, - USB_MAX_PACKET_SIZE, - USB_MAX_PACKET_SIZE, - usb_to_update, - update_to_usb) - - -/* The receiver can be in one of the states below. */ -enum rx_state { - rx_idle, /* Nothing happened yet. */ - 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. */ -}; - -enum rx_state rx_state_ = rx_idle; -static uint8_t block_buffer[sizeof(struct update_command) + - CONFIG_UPDATE_PDU_SIZE]; -static uint32_t block_size; -static uint32_t block_index; - -#ifdef CONFIG_USB_PAIRING -#define KEY_CONTEXT "device-identity" - -static int pair_challenge(struct pair_challenge *challenge) -{ - uint8_t response; - - /* Scratchpad for device secret and x25519 public/shared key. */ - uint8_t tmp[32]; - BUILD_ASSERT(sizeof(tmp) >= X25519_PUBLIC_VALUE_LEN); - BUILD_ASSERT(sizeof(tmp) >= X25519_PRIVATE_KEY_LEN); - BUILD_ASSERT(sizeof(tmp) >= CONFIG_ROLLBACK_SECRET_SIZE); - - /* Scratchpad for device_private and authenticator. */ - uint8_t tmp2[32]; - BUILD_ASSERT(sizeof(tmp2) >= X25519_PRIVATE_KEY_LEN); - BUILD_ASSERT(sizeof(tmp2) >= SHA256_DIGEST_SIZE); - - /* tmp = device_secret */ - if (rollback_get_secret(tmp) != EC_SUCCESS) { - response = EC_RES_UNAVAILABLE; - QUEUE_ADD_UNITS(&update_to_usb, &response, sizeof(response)); - return 1; - } - - /* - * Nothing can fail from now on, let's push data to the queue as soon as - * possible to save some temporary variables. - */ - response = EC_RES_SUCCESS; - QUEUE_ADD_UNITS(&update_to_usb, &response, sizeof(response)); - - /* - * tmp2 = device_private - * = HMAC_SHA256(device_secret, "device-identity") - */ - hmac_SHA256(tmp2, tmp, CONFIG_ROLLBACK_SECRET_SIZE, - KEY_CONTEXT, sizeof(KEY_CONTEXT) - 1); - - /* tmp = device_public = x25519(device_private, x25519_base_point) */ - X25519_public_from_private(tmp, tmp2); - QUEUE_ADD_UNITS(&update_to_usb, tmp, sizeof(tmp)); - - /* tmp = shared_secret = x25519(device_private, host_public) */ - X25519(tmp, tmp2, challenge->host_public); - - /* tmp2 = authenticator = HMAC_SHA256(shared_secret, nonce) */ - hmac_SHA256(tmp2, tmp, sizeof(tmp), - challenge->nonce, sizeof(challenge->nonce)); - QUEUE_ADD_UNITS(&update_to_usb, tmp2, - member_size(struct pair_challenge_response, authenticator)); - return 1; -} -#endif - -/* - * 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 fetch_transfer_start(struct consumer const *consumer, size_t count, - struct update_frame_header *pupfr) -{ - int i; - - /* - * 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, pupfr, - MIN(i, sizeof(*pupfr))); - i -= sizeof(*pupfr); - } - - if (count != sizeof(struct update_frame_header)) { - CPRINTS("FW update: wrong first block, size %d", count); - return 0; - } - - return 1; -} - -static int try_vendor_command(struct consumer const *consumer, size_t count) -{ - char buffer[USB_MAX_PACKET_SIZE]; - struct update_frame_header *cmd_buffer = (void *)buffer; - int rv = 0; - - /* Validate count (too short, or too long). */ - if (count < sizeof(*cmd_buffer) || count > sizeof(buffer)) - return 0; - - /* - * Let's copy off the queue the update frame header, to see if this - * is a channeled vendor command. - */ - queue_peek_units(consumer->queue, cmd_buffer, 0, sizeof(*cmd_buffer)); - if (be32toh(cmd_buffer->cmd.block_base) != UPDATE_EXTRA_CMD) - return 0; - - if (be32toh(cmd_buffer->block_size) != count) { - CPRINTS("%s: problem: block size and count mismatch (%d != %d)", - __func__, be32toh(cmd_buffer->block_size), 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 (update_pdu_valid(&cmd_buffer->cmd, - count - offsetof(struct update_frame_header, cmd))) { - enum update_extra_command subcommand; - uint8_t response; - size_t response_size = sizeof(response); - int __attribute__((unused)) header_size; - int __attribute__((unused)) data_count; - - /* looks good, let's process it. */ - rv = 1; - - /* Now remove it from the queue. */ - queue_advance_head(consumer->queue, count); - - subcommand = be16toh(*((uint16_t *)(cmd_buffer + 1))); - - /* - * header size: update frame header + 2 bytes for subcommand - * data_count: Some commands take in extra data as parameter - */ - header_size = sizeof(*cmd_buffer) + sizeof(uint16_t); - data_count = count - header_size; - - switch (subcommand) { - case UPDATE_EXTRA_CMD_IMMEDIATE_RESET: - CPRINTS("Rebooting!"); - CPRINTF("\n\n"); - cflush(); - system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED); - /* Unreachable, unless something bad happens. */ - response = EC_RES_ERROR; - break; - case UPDATE_EXTRA_CMD_JUMP_TO_RW: -#ifdef CONFIG_RWSIG - /* - * Tell rwsig task to jump to RW. This does nothing if - * verification failed, and will only jump later on if - * verification is still in progress. - */ - rwsig_continue(); - - switch (rwsig_get_status()) { - case RWSIG_VALID: - response = EC_RES_SUCCESS; - break; - case RWSIG_INVALID: - response = EC_RES_INVALID_CHECKSUM; - break; - case RWSIG_IN_PROGRESS: - response = EC_RES_IN_PROGRESS; - break; - default: - response = EC_RES_ERROR; - } -#else - system_run_image_copy(SYSTEM_IMAGE_RW); -#endif - break; -#ifdef CONFIG_RWSIG - case UPDATE_EXTRA_CMD_STAY_IN_RO: - rwsig_abort(); - response = EC_RES_SUCCESS; - break; -#endif - case UPDATE_EXTRA_CMD_UNLOCK_RW: - flash_set_protect(EC_FLASH_PROTECT_RW_AT_BOOT, 0); - response = EC_RES_SUCCESS; - break; -#ifdef CONFIG_ROLLBACK - case UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK: - flash_set_protect(EC_FLASH_PROTECT_ROLLBACK_AT_BOOT, 0); - response = EC_RES_SUCCESS; - break; -#ifdef CONFIG_ROLLBACK_SECRET_SIZE -#ifdef CONFIG_ROLLBACK_UPDATE - case UPDATE_EXTRA_CMD_INJECT_ENTROPY: { - if (data_count < CONFIG_ROLLBACK_SECRET_SIZE) { - CPRINTS("Entropy too short"); - response = EC_RES_INVALID_PARAM; - break; - } - - CPRINTS("Adding %db of entropy", data_count); - /* Add the entropy to secret. */ - rollback_add_entropy(buffer + header_size, data_count); - break; - } -#endif /* CONFIG_ROLLBACK_UPDATE */ -#ifdef CONFIG_USB_PAIRING - case UPDATE_EXTRA_CMD_PAIR_CHALLENGE: { - if (data_count < sizeof(struct pair_challenge)) { - CPRINTS("Challenge data too short"); - response = EC_RES_INVALID_PARAM; - break; - } - - /* pair_challenge takes care of answering */ - return pair_challenge((struct pair_challenge *) - (buffer + header_size)); - } -#endif -#endif /* CONFIG_ROLLBACK_SECRET_SIZE */ -#endif /* CONFIG_ROLLBACK */ -#ifdef CONFIG_TOUCHPAD - case UPDATE_EXTRA_CMD_TOUCHPAD_INFO: { - struct touchpad_info tp = { 0 }; - - if (data_count != 0) { - response = EC_RES_INVALID_PARAM; - break; - } - - response_size = touchpad_get_info(&tp); - if (response_size < 1) { - response = EC_RES_ERROR; - break; - } - -#ifdef CONFIG_TOUCHPAD_VIRTUAL_OFF - tp.fw_address = CONFIG_TOUCHPAD_VIRTUAL_OFF; - tp.fw_size = CONFIG_TOUCHPAD_VIRTUAL_SIZE; - -#ifdef CONFIG_TOUCHPAD_HASH_FW - memcpy(tp.allowed_fw_hash, touchpad_fw_full_hash, - sizeof(tp.allowed_fw_hash)); -#endif -#endif /* CONFIG_TOUCHPAD_VIRTUAL_OFF */ - QUEUE_ADD_UNITS(&update_to_usb, - &tp, response_size); - return 1; - } - case UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG: { - uint8_t *data = NULL; - unsigned int write_count = 0; - - /* - * Let the touchpad driver decide what it wants to do - * with the payload data, and put the response in data. - */ - response = touchpad_debug(buffer + header_size, - data_count, &data, &write_count); - - /* - * On error, or if there is no data to write back, just - * write back response. - */ - if (response != EC_RES_SUCCESS || write_count == 0) - break; - - /* Check that we can write all the data to the queue. */ - if (write_count > queue_space(&update_to_usb)) - return EC_RES_BUSY; - - QUEUE_ADD_UNITS(&update_to_usb, data, write_count); - return 1; - } -#endif -#ifdef CONFIG_USB_CONSOLE_READ - /* - * TODO(b/112877237): move this to a new interface, so we can - * support reading log and other commands at the same time? - */ - case UPDATE_EXTRA_CMD_CONSOLE_READ_INIT: - response = uart_console_read_buffer_init(); - break; - case UPDATE_EXTRA_CMD_CONSOLE_READ_NEXT: { - uint8_t *data = buffer + header_size; - uint8_t output[64]; - uint16_t write_count = 0; - - if (data_count != 1) { - response = EC_RES_INVALID_PARAM; - break; - } - - response = uart_console_read_buffer( - data[0], - (char *)output, - MIN(sizeof(output), - queue_space(&update_to_usb)), - &write_count); - if (response != EC_RES_SUCCESS || write_count == 0) - break; - - QUEUE_ADD_UNITS(&update_to_usb, output, write_count); - return 1; - } -#endif - default: - response = EC_RES_INVALID_COMMAND; - } - - QUEUE_ADD_UNITS(&update_to_usb, &response, response_size); - } - - return rv; -} - -/* - * When was last time a USB callback was called, in microseconds, free running - * timer. - */ -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; - -/* 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; -} - -/* Called to deal with data from the host */ -static void update_out_handler(struct consumer const *consumer, size_t count) -{ - struct update_frame_header upfr; - size_t resp_size; - uint8_t resp_value; - uint64_t delta_time; - - /* How much time since the previous USB callback? */ - delta_time = get_time().val - prev_activity_timestamp; - prev_activity_timestamp += delta_time; - - /* If timeout exceeds 5 seconds - let's start over. */ - if ((delta_time > 5000000) && (rx_state_ != rx_idle)) { - rx_state_ = rx_idle; - CPRINTS("FW update: recovering after timeout"); - } - - if (rx_state_ == rx_idle) { - /* - * 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; - - /* Check is this is a channeled TPM extension command. */ - if (try_vendor_command(consumer, count)) - return; - - /* - * 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(&u.upfr.cmd, count - - offsetof(struct update_frame_header, - cmd), - &resp_size); - - 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 updater had to say. */ - QUEUE_ADD_UNITS(&update_to_usb, &u.startup_resp, resp_size); - return; - } - - if (rx_state_ == rx_outside_block) { - /* - * Expecting to receive the beginning of the block or the - * reset command if all data blocks have been processed. - */ - if (count == 4) { - uint32_t command; - - QUEUE_REMOVE_UNITS(consumer->queue, &command, - sizeof(command)); - 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, 1); - rx_state_ = rx_idle; - return; - } - } - - /* - * At this point we expect a block start message. It is - * sizeof(upfr) bytes in size. - */ - 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(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 > sizeof(block_buffer)) { - CPRINTS("Invalid block size (%d).", block_size); - send_error_reset(UPDATE_GEN_ERROR); - return; - } - - /* - * Copy the rest of the message into the block buffer to pass - * to the updater. - */ - 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; - } - - /* Must be inside block. */ - QUEUE_REMOVE_UNITS(consumer->queue, block_buffer + block_index, count); - block_index += count; - block_size -= count; - - if (block_size) { - if (count <= sizeof(upfr)) { - /* - * A block header size instead of chunk size message - * has been received, let's abort the transfer. - */ - CPRINTS("Unexpected header"); - send_error_reset(UPDATE_GEN_ERROR); - return; - } - return; /* More to come. */ - } - - /* - * Ok, the entire block has been received and reassembled, pass it to - * the updater for verification and programming. - */ - 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; -} - -struct consumer const update_consumer = { - .queue = &usb_to_update, - .ops = &((struct consumer_ops const) { - .written = update_out_handler, - }), -}; |