mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-01 04:43:50 +00:00
Let's split the usb headers in 3 different parts, instead of having usb_descriptor.h pull in usb_hw.h and usb_api.h. - usb_api.h: EC functions related to usb (e.g. connect/disconnect) - usb_descriptor.h: common USB names and structures - usb_hw.h: Functions required for interactive with EC's USB HW BRANCH=none BUG=b:35587171 TEST=make buildall -j Change-Id: I37ead61e3be5e7ae464f1c9137cf02eaab0ff92e Reviewed-on: https://chromium-review.googlesource.com/454861 Commit-Ready: Nicolas Boichat <drinkcat@chromium.org> Tested-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
358 lines
10 KiB
C
358 lines
10 KiB
C
/* 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_api.h"
|
|
#include "usb_hw.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,
|
|
}),
|
|
};
|