/* Copyright 2016 The ChromiumOS Authors * 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 "extension.h" #include "queue_policies.h" #include "shared_mem.h" #include "system.h" #include "upgrade_fw.h" #include "usb-stream.h" #define CPRINTS(format, args...) cprints(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 upgrade_consumer; struct usb_stream_config const usb_upgrade; static struct queue const upgrade_to_usb = QUEUE_DIRECT(64, uint8_t, null_producer, usb_upgrade.consumer); static struct queue const usb_to_upgrade = QUEUE_DIRECT(64, uint8_t, usb_upgrade.producer, upgrade_consumer); USB_STREAM_CONFIG_FULL(usb_upgrade, USB_IFACE_UPGRADE, USB_CLASS_VENDOR_SPEC, USB_SUBCLASS_GOOGLE_CR50, USB_PROTOCOL_GOOGLE_CR50_NON_HC_FW_UPDATE, USB_STR_UPGRADE_NAME, USB_EP_UPGRADE, USB_MAX_PACKET_SIZE, USB_MAX_PACKET_SIZE, usb_to_upgrade, upgrade_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; static uint32_t block_size; static uint32_t block_index; /* * 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. */ static int valid_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. */ 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; } /* In the first block the payload (pupfr->cmd) must be all zeros. */ for (i = 0; i < sizeof(pupfr->cmd); i++) if (((uint8_t *)&pupfr->cmd)[i]) 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; uint16_t *subcommand; 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]; 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))) { /* Didn't verify */ shared_mem_release(cmd_buffer); return 0; } /* Looks good; remove from the queue and process it. */ 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)) { const uint8_t err = VENDOR_RC_REQUEST_TOO_BIG; CPRINTS("%s: payload too big (%d)", __func__, request_size); QUEUE_ADD_UNITS(&upgrade_to_usb, &err, 1); } else { uint32_t rv; struct vendor_cmd_params p = { .code = be16toh(*subcommand), .buffer = subcommand_body, .in_size = request_size, /* * The return code normally put into the TPM response * header is not present in the USB response. Vendor * command return code is guaranteed to fit in a * byte. Let's keep space for it in the front of the * buffer. */ .out_size = sizeof(subcommand_body) - 1, .flags = VENDOR_CMD_FROM_USB }; memcpy(subcommand_body, subcommand + 1, request_size); rv = extension_route_command(&p); /* * Copy actual response, if any, one byte up, to free room for * the return code. */ if (p.out_size) memmove(subcommand_body + 1, subcommand_body, p.out_size); subcommand_body[0] = rv; /* We care about LSB only. */ QUEUE_ADD_UNITS(&upgrade_to_usb, subcommand_body, p.out_size + 1); } shared_mem_release(cmd_buffer); return 1; } /* * 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; /* Called to deal with data from the host */ static void upgrade_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)) { if (block_buffer) { /* * Previous transfer could have been aborted mid * block. */ shared_mem_release(block_buffer); block_buffer = NULL; } 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; if (!valid_transfer_start(consumer, count, &u.upfr)) { /* * Something 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; CPRINTS("%s:%d", __FILE__, __LINE__); QUEUE_ADD_UNITS(&upgrade_to_usb, &resp_value, 1); return; } CPRINTS("FW update: starting..."); fw_upgrade_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 upgrader had to say. */ QUEUE_ADD_UNITS(&upgrade_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 == UPGRADE_DONE) { CPRINTS("FW update: done"); 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_idle; return; } } /* * At this point we expect a block start message. It is * sizeof(upfr) bytes in size, but is not the transfer start * message, which also is of that size AND has the command * field of all zeros. */ if (valid_transfer_start(consumer, count, &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; CPRINTS("%s:%d", __FILE__, __LINE__); 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) { 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; } /* * Copy the rest of the message into the block buffer to pass * to the upgrader. */ 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. There must have been some packet * loss and the host is restarting this block. * * Let's copy its contents into the header structure. */ memcpy(&upfr, block_buffer + block_index - count, count); /* And re-allocate a large enough buffer. */ shared_mem_release(block_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); return; } /* * Copy the rest of the message into the block buffer * to pass to the upgrader. */ block_index = sizeof(upfr) - offsetof(struct update_frame_header, cmd); memcpy(block_buffer, &upfr.cmd, block_index); block_size -= block_index; } return; /* More to come. */ } /* * Ok, the entire block has been received and reassembled, pass it to * the updater for verification and programming. */ 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; shared_mem_release(block_buffer); block_buffer = NULL; } struct consumer const upgrade_consumer = { .queue = &usb_to_upgrade, .ops = &((struct consumer_ops const) { .written = upgrade_out_handler, }), };