/* 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;
}

struct consumer const update_consumer = {
	.queue = &usb_to_update,
	.ops   = &((struct consumer_ops const) {
		.written = update_out_handler,
	}),
};
