/* 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 "include/compile_time_macros.h" #include "queue_policies.h" #include "shared_mem.h" #include "system.h" #include "update_fw.h" #include "usb-stream.h" #include "util.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 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. * * In the end of the successful image transfer and programming, the host send * 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. */ 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; static uint8_t *block_buffer; 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. */ static int valid_transfer_start(struct consumer const *consumer, size_t count, struct update_pdu_header *pupdu) { 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, pupdu, MIN(i, sizeof(*pupdu))); i -= sizeof(*pupdu); } if (count != sizeof(struct update_pdu_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; } /* * 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 /* 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; size_t resp_size; uint32_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) { /* * 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. * * 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. */ struct { uint32_t value; uint32_t version; } startup_resp; if (!valid_transfer_start(consumer, count, &updu)) return; CPRINTS("FW update: starting..."); fw_update_command_handler(&updu.cmd, count - offsetof(struct update_pdu_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)); } 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)); 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 * 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"); resp_value = 0; QUEUE_ADD_UNITS(&update_to_usb, &resp_value, sizeof(resp_value)); rx_state_ = rx_awaiting_reset; 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. */ 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. */ return; /* Let's allocate a large enough 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; 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(updu)) { /* * 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. */ block_index = sizeof(updu) - offsetof(struct update_pdu_header, cmd); memcpy(block_buffer, &updu.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_update_command_handler(block_buffer, block_index, &resp_size); resp_value = block_buffer[0]; QUEUE_ADD_UNITS(&update_to_usb, &resp_value, sizeof(resp_value)); rx_state_ = rx_outside_block; shared_mem_release(block_buffer); block_buffer = NULL; } static void update_flush(struct consumer const *consumer) { } struct consumer const update_consumer = { .queue = &usb_to_update, .ops = &((struct consumer_ops const) { .written = update_out_handler, .flush = update_flush, }), };