summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2021-06-18 11:27:23 -0700
committerCommit Bot <commit-bot@chromium.org>2021-08-02 22:03:32 +0000
commit049538cb663e8d8f1f9fbeed94f7b823e0eafabe (patch)
tree85ffaa4cb94c884f9acb9349f0e04245a8b0fc12
parenta5efd47c6e326d9586d370b454b4fca864d25f23 (diff)
downloadchrome-ec-049538cb663e8d8f1f9fbeed94f7b823e0eafabe.tar.gz
usb_spi: move to Raiden V2 implementation
This patch introduces an alternative USB SPI protocol implementation to be used by Cr50: Raiden V2. The SPI USB endpoint descriptor is modified to advertise the new version in the bInterfaceProtocol, which allows the flashrom utility to use the new protocol version. Protocol version 2 implements segmentation and reassembly where longer flash read and write PDUs can be transferred split into shorter fixed size USB packets. The comment section in usb_spi_v2.c describes the protocol in detail. Each time a USB packet is received from the host, the packet header is examined to determine the command. The command could be a DUT configuration query OR a request to read and or write some data from/to the SPI flash chip, OR a request to retransmit the last PDU from the beginning. This patch implementation does not process the retransmittion request command yet, in case a packet is dropped flashrom would need to be re-run. This is a pretty rare condition, but if deemed necessary support can be added later. H1 SPI controller supports multibuffer transactions where the CS signal is kept asserted while the controller clocks the bus when the next portion of data to write becomes available or there is more room to read data to send back to the host. This allows to support arbitrary length read and write transactions. There is no need to support write transactions longer than 256 bytes of data, as this is a typical SPI flash chip page size. For read direction the size of 2040 was chosen, which is close to 2K and takes full payload of 34 USB packets on top of 2 byte headers. The protocol state machine on the device sideOB can be in one of two states, IDLE or WRITING. Many of host requests do not require the device to change state: configuration requests, or writes of short blocks of data (fitting into one USB packet) can be executed immediately. Requests to read long blocks of data can still be executed without leaving the IDLE state, the device starts the SPI transaction and then iterates reading one packet worth of data at a time and sends it back to the host. Once the entire PDU is read, the CS is deasserted. In case the host requests to write a block of data which does not fit into a USB packet the device asserts the CS, sends the first received block to the SPI flash chip and then enters the WRITING state, expecting the controller to send the rest of the PDU in following packets. Once the entire PDU is transferred the CS is deasserted and state is changed back to IDLE. BUG=b:79492818 TEST=performed numerous flash read/write operations with 16M SPI flash chip on the Atlas device. Timing results comparison of various operations: Raiden V1 Raiden V2 Reading entire chip: 3m 16s 0m 52s Vanilla writing of new image: 16m 22s 5m 48s Writing of AP firmware into an erased flash chip (no read before or after writing) 4m 12s 1m 38s Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Change-Id: I374f3caab7146fc84b62274e9e713430d7d31de0 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2977965 Reviewed-by: Brian Nemec <bnemec@chromium.org> Reviewed-by: Andrey Pronin <apronin@chromium.org>
-rw-r--r--board/cr50/board.h2
-rw-r--r--board/cr50/build.mk2
-rw-r--r--chip/g/build.mk1
-rw-r--r--chip/g/usb_spi.c6
-rw-r--r--chip/g/usb_spi.h36
-rw-r--r--chip/g/usb_spi_v2.c693
-rw-r--r--include/config.h4
-rw-r--r--include/usb_descriptor.h6
8 files changed, 740 insertions, 10 deletions
diff --git a/board/cr50/board.h b/board/cr50/board.h
index 679646c861..9519306be4 100644
--- a/board/cr50/board.h
+++ b/board/cr50/board.h
@@ -103,7 +103,7 @@
#define CONFIG_USB_CONSOLE_TX_BUF_SIZE 4096
#define CONFIG_USB_I2C
#define CONFIG_USB_INHIBIT_INIT
-#define CONFIG_USB_SPI
+#define CONFIG_USB_SPI_V2
#define CONFIG_USB_SERIALNO
#define DEFAULT_SERIALNO "0"
diff --git a/board/cr50/build.mk b/board/cr50/build.mk
index 07f74f8686..5ff5894cb8 100644
--- a/board/cr50/build.mk
+++ b/board/cr50/build.mk
@@ -49,7 +49,7 @@ board-y += servo_state.o
board-y += ap_uart_state.o
board-y += factory_mode.o
board-${CONFIG_RDD} += rdd.o
-board-${CONFIG_USB_SPI} += usb_spi.o
+board-${CONFIG_USB_SPI_V2} += usb_spi.o
board-${CONFIG_USB_I2C} += usb_i2c.o
board-y += recovery_button.o
diff --git a/chip/g/build.mk b/chip/g/build.mk
index 2fb82e4f4d..80bc997b59 100644
--- a/chip/g/build.mk
+++ b/chip/g/build.mk
@@ -79,6 +79,7 @@ chip-$(CONFIG_USB)+=usb.o usb_endpoints.o
chip-$(CONFIG_USB_CONSOLE)+=usb_console.o
chip-$(CONFIG_USB_BLOB)+=blob.o
chip-$(CONFIG_USB_SPI)+=usb_spi.o
+chip-$(CONFIG_USB_SPI_V2)+=usb_spi_v2.o
chip-$(CONFIG_RDD)+=rdd.o
chip-$(CONFIG_RBOX)+=rbox.o
chip-$(CONFIG_STREAM_USB)+=usb-stream.o
diff --git a/chip/g/usb_spi.c b/chip/g/usb_spi.c
index 5d436a1b83..df30202f2c 100644
--- a/chip/g/usb_spi.c
+++ b/chip/g/usb_spi.c
@@ -19,6 +19,12 @@
#include "signing.h"
#endif
+#define USB_SPI_MAX_WRITE_COUNT 62
+#define USB_SPI_MAX_READ_COUNT 62
+
+BUILD_ASSERT(USB_MAX_PACKET_SIZE == (1 + 1 + USB_SPI_MAX_WRITE_COUNT));
+BUILD_ASSERT(USB_MAX_PACKET_SIZE == (2 + USB_SPI_MAX_READ_COUNT));
+
#define CPUTS(outstr) cputs(CC_USB, outstr)
#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
diff --git a/chip/g/usb_spi.h b/chip/g/usb_spi.h
index 65be060616..2432dc2cd9 100644
--- a/chip/g/usb_spi.h
+++ b/chip/g/usb_spi.h
@@ -59,6 +59,11 @@ enum usb_spi_error {
USB_SPI_WRITE_COUNT_INVALID = 0x0003,
USB_SPI_READ_COUNT_INVALID = 0x0004,
USB_SPI_DISABLED = 0x0005,
+ USB_SPI_RX_BAD_DATA_INDEX = 0x0006,
+ USB_SPI_RX_DATA_OVERFLOW = 0x0007,
+ USB_SPI_RX_UNEXPECTED_PACKET = 0x0008,
+ USB_SPI_UNSUPPORTED_FULL_DUPLEX = 0x0009,
+ USB_SPI_RUNT_PACKET = 0x000a,
USB_SPI_UNKNOWN_ERROR = 0x8000,
};
@@ -86,11 +91,22 @@ enum usb_spi {
};
-#define USB_SPI_MAX_WRITE_COUNT 62
-#define USB_SPI_MAX_READ_COUNT 62
-
-BUILD_ASSERT(USB_MAX_PACKET_SIZE == (1 + 1 + USB_SPI_MAX_WRITE_COUNT));
-BUILD_ASSERT(USB_MAX_PACKET_SIZE == (2 + USB_SPI_MAX_READ_COUNT));
+#ifdef CONFIG_USB_SPI_V2
+/*
+ * Raiden client can be in one of two states, IDLE, or WRITING.
+ *
+ * When in IDLE state the client accepts commands from the host and can either
+ * perform the required action (report the configuration, or write a full
+ * write transaction (fitting into one USB packet), or write the received
+ * chunk and then repeatedly read the SPI flash and send the contents back in
+ * USB packets until the full required read transaction is completed.
+ *
+ * In case the received chunk is less than the total write transaction size
+ * the client moves into RAIDEN_WRITING state and expects all following
+ * received USB packets to be continuation of the write transaction.
+ */
+enum raiden_state { RAIDEN_IDLE, RAIDEN_WRITING };
+#endif
struct usb_spi_state {
/*
@@ -114,6 +130,13 @@ struct usb_spi_state {
* callback.
*/
int enabled;
+
+#ifdef CONFIG_USB_SPI_V2
+ /* Variable helping to keep track of multi packet write PDUs. */
+ uint16_t total_write_count;
+ uint16_t wrote_so_far;
+ enum raiden_state raiden_state;
+#endif
};
/*
@@ -167,7 +190,8 @@ extern struct consumer_ops const usb_spi_consumer_ops;
INTERFACE, \
ENDPOINT) \
\
- static uint8_t CONCAT2(NAME, _buffer_)[USB_MAX_PACKET_SIZE]; \
+ static uint8_t CONCAT2(NAME, _buffer_)[USB_MAX_PACKET_SIZE] \
+ __aligned(2); \
static void CONCAT2(NAME, _deferred_)(void); \
DECLARE_DEFERRED(CONCAT2(NAME, _deferred_)); \
static struct queue const CONCAT2(NAME, _to_usb_); \
diff --git a/chip/g/usb_spi_v2.c b/chip/g/usb_spi_v2.c
new file mode 100644
index 0000000000..89a492c0c1
--- /dev/null
+++ b/chip/g/usb_spi_v2.c
@@ -0,0 +1,693 @@
+/* Copyright 2021 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 "ccd_config.h"
+#include "common.h"
+#include "link_defs.h"
+#include "gpio.h"
+#include "registers.h"
+#include "spi.h"
+#include "spi_flash.h"
+#include "timer.h"
+#include "usb_descriptor.h"
+#include "usb_spi.h"
+#include "util.h"
+
+/*
+ * This module implements support for SPI programming driven by the flashrom
+ * utility over USB.
+ *
+ * Flashrom connects to the specific SPI USB endpoint and sends commands to
+ * this driver to control the external SPI flash chip attached to the H1's SPI
+ * host port.
+ *
+ * Some configuration actions might have to be performed before SPI
+ * programming is possible (say asserting the AP reset, enabling a
+ * multiplexer, etc.), these actions are not controlled/performed by this
+ * module, it expects the 'H2<=>SPI flash' connection provisioned and the
+ * flash chip ready to be programmed.
+ *
+ * The below protocol specification was copied from raiden_debug_spi.c in the
+ * flashrom tree with addition of some clarifications.
+ *
+ * Note that this implementation imposes additional limits on SPI transactions
+ * which include both read and write portions: the write size of such
+ * transactions case can not exceed 60, the size of the payload of the first
+ * USB packet carrying the transaction PDU.
+ *
+ * USB SPI Version 2:
+ *
+ * USB SPI version 2 adds support for larger SPI transfers which reduces
+ * the number of USB packets transferred. This improves performance when
+ * writing or reading large chunks of memory from a device. Larger
+ * quantities are transferred in PDUs split into USB packets.
+ *
+ * A packet ID field is used to distinguish the different USB packet
+ * types. Additional packets have been included to query the device for
+ * its configuration allowing the interface to be used on platforms with
+ * different SPI limitations. It includes validation and a packet to
+ * recover from the situations where USB packets are lost.
+ *
+ * The device advertises the supported protocol version in the
+ * bInterfaceProtocol field of the USB descriptor, this allows the USB SPI
+ * hosts to be backwards compatible with version 1.
+ *
+ *
+ * Example: USB SPI request with 128 byte write and 0 byte read.
+ *
+ * Packet #1 Host to Device:
+ * packet id = USB_SPI_PKT_ID_CMD_TRANSFER_START
+ * write count = 128
+ * read count = 0
+ * payload = First 58 bytes from the write buffer,
+ * starting at byte 0 in the buffer
+ * packet size = 64 bytes
+ *
+ * Packet #2 Host to Device:
+ * packet id = USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE
+ * data index = 58
+ * payload = Next 60 bytes from the write buffer,
+ * starting at byte 58 in the buffer
+ * packet size = 64 bytes
+ *
+ * Packet #3 Host to Device:
+ * packet id = USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE
+ * data index = 118
+ * payload = Next 10 bytes from the write buffer,
+ * starting at byte 118 in the buffer
+ * packet size = 14 bytes
+ *
+ * Packet #4 Device to Host:
+ * packet id = USB_SPI_PKT_ID_RSP_TRANSFER_START
+ * status code = status code from device
+ * payload = 0 bytes
+ * packet size = 4 bytes
+ *
+ * Example: USB SPI request with 2 byte write and 100 byte read.
+ *
+ * Packet #1 Host to Device:
+ * packet id = USB_SPI_PKT_ID_CMD_TRANSFER_START
+ * write count = 2
+ * read count = 100
+ * payload = The 2 byte write buffer
+ * packet size = 8 bytes
+ *
+ * Packet #2 Device to Host:
+ * packet id = USB_SPI_PKT_ID_RSP_TRANSFER_START
+ * status code = status code from device
+ * payload = First 60 bytes from the read buffer,
+ * starting at byte 0 in the buffer
+ * packet size = 64 bytes
+ *
+ * Packet #3 Device to Host:
+ * packet id = USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE
+ * data index = 60
+ * payload = Next 40 bytes from the read buffer,
+ * starting at byte 60 in the buffer
+ * packet size = 44 bytes
+ *
+ *
+ * Message Packets:
+ *
+ * Note that all 16 bit fields are transferred in little endian mode.
+ *
+ * Command Start Packet (Host to Device):
+ *
+ * Start of the USB SPI command, contains the number of bytes to write
+ * and read on SPI and up to the first 58 bytes of write payload.
+ * Longer writes will use the continue packets with packet id
+ * USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE to transmit the remaining data.
+ *
+ * The write part of the transaction is executed first; in case the
+ * packet includes both read and write requests, the write portion is
+ * required to fit into the first USB packet.
+ *
+ * +----------------+------------------+-----------------+---------------+
+ * | packet id : 2B | write count : 2B | read count : 2B | w.p. : <= 58B |
+ * +----------------+------------------+-----------------+---------------+
+ *
+ * packet id: 2 byte enum defined by packet_id_type
+ * Valid values packet id = USB_SPI_PKT_ID_CMD_TRANSFER_START
+ *
+ * write count: 2 byte, zero based count of bytes to write
+ *
+ * read count: 2 byte, zero based count of bytes to read
+ * UINT16_MAX indicates full duplex mode with a read count
+ * equal to the write count.
+ *
+ * write payload: Up to 58 bytes of data to write to SPI, the total
+ * length of all TX packets must match write count.
+ * Due to data alignment constraints, this must be an
+ * even number of bytes unless this is the final packet.
+ *
+ *
+ * Response Start Packet (Device to Host):
+ *
+ * Start of the USB SPI response, contains the status code and up to
+ * the first 60 bytes of read payload. Longer reads will use the
+ * continue packets with packet id USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE
+ * to transmit the remaining data.
+ *
+ * +----------------+------------------+-----------------------+
+ * | packet id : 2B | status code : 2B | read payload : <= 60B |
+ * +----------------+------------------+-----------------------+
+ *
+ * packet id: 2 byte enum defined by packet_id_type
+ * Valid values packet id = USB_SPI_PKT_ID_RSP_TRANSFER_START
+ *
+ * status code: 2 byte status code
+ * 0x0000: Success
+ * 0x0001: SPI timeout
+ * 0x0002: Busy, try again
+ * This can happen if someone else has acquired the shared memory
+ * buffer that the SPI driver uses as /dev/null
+ * 0x0003: Write count invalid. The byte limit is platform specific
+ * and is set during the configure USB SPI response.
+ * 0x0004: Read count invalid. The byte limit is platform specific
+ * and is set during the configure USB SPI response.
+ * 0x0005: The SPI bridge is disabled.
+ * 0x0006: The RX continue packet's data index is invalid. This
+ * can indicate a USB transfer failure to the device.
+ * 0x0007: The RX endpoint has received more data than write count.
+ * This can indicate a USB transfer failure to the device.
+ * 0x0008: An unexpected packet arrived that the device could not
+ * process.
+ * 0x0009: The device does not support full duplex mode.
+ * 0x8000: Unknown error mask
+ * The bottom 15 bits will contain the bottom 15 bits from the EC
+ * error code.
+ *
+ * read payload: Up to 60 bytes of data read from SPI, the total
+ * length of all RX packets must match read count
+ * unless an error status was returned. Due to data
+ * alignment constraints, this must be a even number
+ * of bytes unless this is the final packet.
+ *
+ *
+ * Continue Packet (Bidirectional):
+ *
+ * Continuation packet for the writes and read buffers. Both packets
+ * follow the same format, a data index counts the number of bytes
+ * previously transferred in the USB SPI transfer and a payload of bytes.
+ *
+ * +----------------+-----------------+-------------------------------+
+ * | packet id : 2B | data index : 2B | write / read payload : <= 60B |
+ * +----------------+-----------------+-------------------------------+
+ *
+ * packet id: 2 byte enum defined by packet_id_type
+ * The packet id has 2 values depending on direction:
+ * packet id = USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE
+ * indicates the packet is being transmitted from the host
+ * to the device and contains SPI write payload.
+ * packet id = USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE
+ * indicates the packet is being transmitted from the device
+ * to the host and contains SPI read payload.
+ *
+ * data index: The data index indicates the number of bytes in the
+ * read or write buffers that have already been transmitted.
+ * It is used to validate that no packets have been dropped
+ * and that the prior packets have been correctly decoded.
+ * This value corresponds to the offset bytes in the buffer
+ * to start copying the payload into.
+ *
+ * read and write payload:
+ * Contains up to 60 bytes of payload data to transfer to
+ * the SPI write buffer or from the SPI read buffer.
+ *
+ *
+ * Command Get Configuration Packet (Host to Device):
+ *
+ * Query the device to request its USB SPI configuration indicating
+ * the number of bytes it can write and read.
+ *
+ * +----------------+
+ * | packet id : 2B |
+ * +----------------+
+ *
+ * packet id: 2 byte enum USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG
+ *
+ * Response Configuration Packet (Device to Host):
+ *
+ * Response packet form the device to report the maximum write and
+ * read size supported by the device.
+ *
+ * +----------------+----------------+---------------+----------------+
+ * | packet id : 2B | max write : 2B | max read : 2B | feature bitmap |
+ * +----------------+----------------+---------------+----------------+
+ *
+ * packet id: 2 byte enum USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG
+ *
+ * max write count: 2 byte count of the maximum number of bytes
+ * the device can write to SPI in one transaction.
+ *
+ * max read count: 2 byte count of the maximum number of bytes
+ * the device can read from SPI in one transaction.
+ *
+ * feature bitmap: Bitmap of supported features.
+ * BIT(0): Full duplex SPI mode is supported
+ * BIT(1:15): Reserved for future use
+ *
+ * Command Restart Response Packet (Host to Device):
+ *
+ * Command to restart the response transfer from the device. This enables
+ * the host to recover from a lost packet when reading the response
+ * without restarting the SPI transfer.
+ *
+ * +----------------+
+ * | packet id : 2B |
+ * +----------------+
+ *
+ * packet id: 2 byte enum USB_SPI_PKT_ID_CMD_RESTART_RESPONSE
+ *
+ * USB Error Codes:
+ *
+ * send_command return codes have the following format:
+ *
+ * 0x00000: Status code success.
+ * 0x00001-0x0FFFF: Error code returned by the USB SPI device.
+ * 0x10001-0x1FFFF: Error code returned by the USB SPI host.
+ * 0x20001-0x20063 Lower bits store the positive value representation
+ * of the libusb_error enum. See the libusb documentation:
+ * http://libusb.sourceforge.net/api-1.0/group__misc.html
+ *
+ */
+
+
+#define CPUTS(outstr) cputs(CC_USB, outstr)
+#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
+
+static int16_t usb_spi_map_error(int error)
+{
+ switch (error) {
+ case EC_SUCCESS:
+ return USB_SPI_SUCCESS;
+ case EC_ERROR_TIMEOUT:
+ return USB_SPI_TIMEOUT;
+ case EC_ERROR_BUSY:
+ return USB_SPI_BUSY;
+ default:
+ return USB_SPI_UNKNOWN_ERROR | (error & 0x7fff);
+ }
+}
+
+static uint16_t usb_spi_read_packet(struct usb_spi_config const *config)
+{
+ return QUEUE_REMOVE_UNITS(config->consumer.queue, config->buffer,
+ queue_count(config->consumer.queue));
+}
+
+static void usb_spi_write_packet(struct usb_spi_config const *config,
+ uint8_t count)
+{
+ /*
+ * Experiments show that while reading a 16M flash time spent waiting
+ * is less than 30 ms, which is negligible in this case, as the AP is
+ * held in reset.
+ */
+ while (queue_space(config->tx_queue) < count)
+ msleep(1);
+
+ QUEUE_ADD_UNITS(config->tx_queue, config->buffer, count);
+}
+
+enum packet_id_type {
+ /* Request USB SPI configuration data from device. */
+ USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG = 0,
+ /* USB SPI configuration data from device. */
+ USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG = 1,
+ /*
+ * Start a USB SPI transfer specifying number of bytes to write,
+ * read and deliver first packet of data to write.
+ */
+ USB_SPI_PKT_ID_CMD_TRANSFER_START = 2,
+ /* Additional packets containing write payload. */
+ USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE = 3,
+ /*
+ * Request the device restart the response enabling us to recover
+ * from packet loss without another SPI transfer.
+ */
+ USB_SPI_PKT_ID_CMD_RESTART_RESPONSE = 4,
+ /*
+ * First packet of USB SPI response with the status code
+ * and read payload if it was successful.
+ */
+ USB_SPI_PKT_ID_RSP_TRANSFER_START = 5,
+ /* Additional packets containing read payload. */
+ USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE = 6,
+ /* Inform the device that transfer is over. */
+ USB_SPI_PKT_ID_CMD_HOST_DONE = 7,
+ /* Inform the host that processing is finished. */
+ USB_SPI_PKT_ID_RSP_DEVICE_DONE = 8,
+};
+
+enum feature_bitmap {
+ /* Indicates the platform supports full duplex mode. */
+ USB_SPI_FEATURE_FULL_DUPLEX_SUPPORTED = BIT(0)
+};
+
+/* Helper structures defining various PDU headers. */
+struct raiden_response_hdr {
+ uint16_t packet_id;
+ uint16_t value; /* Either offset into the PDU or error code. */
+};
+
+struct raiden_cmd_start_hdr {
+ uint16_t packet_id;
+ uint16_t write_count;
+ uint16_t read_count;
+};
+
+struct raiden_cmd_continue_hdr {
+ uint16_t packet_id;
+ uint16_t offset;
+};
+
+union raiden_cmd_hdr {
+ struct raiden_cmd_start_hdr s;
+ struct raiden_cmd_continue_hdr c;
+};
+
+#define START_PAYLOAD_SIZE \
+ (USB_MAX_PACKET_SIZE - sizeof(struct raiden_cmd_start_hdr))
+#define CONT_PAYLOAD_SIZE \
+ (USB_MAX_PACKET_SIZE - sizeof(struct raiden_cmd_continue_hdr))
+#define RESP_PAYLOAD_SIZE \
+ (USB_MAX_PACKET_SIZE - sizeof(struct raiden_response_hdr))
+
+/*
+ * Hardcoded values used to communicate to the host maximum sizes of read and
+ * write transactions.
+ */
+/*
+ * Six bytes header, one byte command, up to four byte addr, 256 bytes data
+ * page and then some in case there is a chip which uses a multibyte command.
+ */
+#define USB_SPI_MAX_WRITE_COUNT 270
+/*
+ * Flashrom deducts 5 from this value, remaining 2040 bytes size results in 34
+ * USB packets of 4 byte header and 60 bytes data.
+ */
+#define USB_SPI_MAX_READ_COUNT (5 + 34 * RESP_PAYLOAD_SIZE)
+
+/* Hadrdcoded response to the config request packet. */
+static const struct {
+ uint16_t rsp_id;
+ uint16_t max_write;
+ uint16_t max_read;
+ uint16_t features;
+} config_rsp = { USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG, USB_SPI_MAX_WRITE_COUNT,
+ USB_SPI_MAX_READ_COUNT, 0 };
+
+/*
+ * Prepare an error message to be sent back to the host and switch to
+ * RAIDEN_IDLE state.
+ *
+ * The return value is the number of bytes to send to the controller.
+ */
+static uint8_t report_error(void *packet, uint16_t error,
+ struct usb_spi_state *state)
+{
+ /* Packet is guaranteed to be 2 bytes aligned. */
+ struct raiden_response_hdr *rsp = (struct raiden_response_hdr *)packet;
+
+ rsp->packet_id = USB_SPI_PKT_ID_RSP_TRANSFER_START;
+ rsp->value = error;
+
+ state->raiden_state = RAIDEN_IDLE;
+ return sizeof(*rsp);
+}
+
+/* Return length of the last response packet to send back to the host. */
+static uint8_t process_raiden_packet(const struct usb_spi_config *config,
+ size_t count)
+{
+ /* Buffer is guaranteed to be 2 bytes aligned. */
+ union raiden_cmd_hdr *packet = (union raiden_cmd_hdr *)config->buffer;
+ struct raiden_response_hdr *rsp =
+ (struct raiden_response_hdr *)config->buffer;
+ uint16_t total_read_count;
+ uint16_t read_so_far;
+ uint16_t this_read_count = 0;
+ uint16_t this_write_count = 0;
+ bool last_sub_transaction;
+ uint16_t res;
+ uint16_t packet_id;
+ struct usb_spi_state *state = config->state;
+
+ if (count < sizeof(packet->s.packet_id))
+ return report_error(config->buffer, USB_SPI_UNKNOWN_ERROR,
+ state);
+
+ packet_id = packet->s.packet_id;
+ switch (state->raiden_state) {
+ case RAIDEN_IDLE:
+ if ((packet_id == USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG) &&
+ (count == sizeof(uint16_t))) {
+ memcpy(config->buffer, &config_rsp, sizeof(config_rsp));
+ return sizeof(config_rsp);
+ }
+
+ if ((packet_id != USB_SPI_PKT_ID_CMD_TRANSFER_START) ||
+ (count < sizeof(struct raiden_cmd_start_hdr)))
+ return report_error(config->buffer,
+ USB_SPI_RUNT_PACKET, state);
+
+ /* This is request to start a new transaction. */
+ state->total_write_count = packet->s.write_count;
+ total_read_count = packet->s.read_count;
+
+ /* Some basic validity checks. */
+ if (total_read_count > config_rsp.max_read)
+ return report_error(config->buffer,
+ USB_SPI_READ_COUNT_INVALID, state);
+
+ if (state->total_write_count > config_rsp.max_write)
+ return report_error(config->buffer,
+ USB_SPI_WRITE_COUNT_INVALID, state);
+
+ /*
+ * If write count fits into one USB packet, the size of packet
+ * must match.
+ */
+ if ((state->total_write_count <=
+ (USB_MAX_PACKET_SIZE -
+ sizeof(struct raiden_cmd_start_hdr))) &&
+ (state->total_write_count !=
+ (count - sizeof(struct raiden_cmd_start_hdr))))
+ return report_error(config->buffer,
+ USB_SPI_WRITE_COUNT_INVALID, state);
+
+ /*
+ * Transactions with write count exceeding one USB packet AND
+ * requiring a read are not supported.
+ */
+ if (total_read_count &&
+ (state->total_write_count > START_PAYLOAD_SIZE))
+ return report_error(config->buffer,
+ USB_SPI_WRITE_COUNT_INVALID, state);
+
+ read_so_far = 0;
+ state->wrote_so_far = 0;
+
+ if (state->total_write_count > START_PAYLOAD_SIZE)
+ this_write_count = START_PAYLOAD_SIZE;
+ else
+ this_write_count = state->total_write_count;
+
+ while (this_write_count || (read_so_far != total_read_count)) {
+ this_read_count = total_read_count - read_so_far;
+
+ /*
+ * Need to decide if CS needs to be deasserted in this
+ * iteration. It would be the case if both final read
+ * and write counts fit into USB packet.
+ */
+ if ((this_read_count > RESP_PAYLOAD_SIZE) ||
+ (state->total_write_count > START_PAYLOAD_SIZE)) {
+ last_sub_transaction = false;
+
+ /* Limit read count by USB packet capacity. */
+ if (this_read_count > RESP_PAYLOAD_SIZE)
+ this_read_count = RESP_PAYLOAD_SIZE;
+ } else {
+ last_sub_transaction = true;
+ }
+
+ res = usb_spi_map_error(spi_sub_transaction(
+ SPI_FLASH_DEVICE,
+ config->buffer +
+ sizeof(struct raiden_cmd_start_hdr),
+ this_write_count,
+ config->buffer +
+ sizeof(struct raiden_response_hdr),
+ this_read_count, last_sub_transaction));
+ if (res) {
+ rsp->value = res;
+ rsp->packet_id =
+ USB_SPI_PKT_ID_RSP_TRANSFER_START;
+ return sizeof(*rsp);
+ }
+
+ rsp->packet_id =
+ read_so_far ?
+ USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE :
+ USB_SPI_PKT_ID_RSP_TRANSFER_START;
+ rsp->value = read_so_far;
+
+ state->wrote_so_far += this_write_count;
+ read_so_far += this_read_count;
+
+ if (state->total_write_count > state->wrote_so_far) {
+ state->raiden_state = RAIDEN_WRITING;
+ return 0;
+ }
+
+ if (last_sub_transaction)
+ break;
+
+ usb_spi_write_packet(config,
+ this_read_count + sizeof(*rsp));
+
+ /*
+ * Make sure this does not keep the loop going, we
+ * wrote all there was to write.
+ */
+ this_write_count = 0;
+ }
+
+ if (this_read_count)
+ return this_read_count + sizeof(*rsp);
+
+ if (state->total_write_count &&
+ (state->total_write_count == this_write_count))
+ return sizeof(*rsp);
+ return 0;
+
+ case RAIDEN_WRITING:
+ if ((packet_id != USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE) ||
+ (count <= sizeof(struct raiden_cmd_continue_hdr)))
+ return report_error(config->buffer,
+ USB_SPI_RX_UNEXPECTED_PACKET,
+ state);
+
+ if (packet->c.offset != state->wrote_so_far)
+ return report_error(config->buffer,
+ USB_SPI_RX_UNEXPECTED_PACKET,
+ state);
+
+ this_write_count =
+ count - sizeof(struct raiden_cmd_continue_hdr);
+
+ /*
+ * This is the last sub transaction if the remainder of data
+ * to write fits into USB packet, CS will have to be
+ * deasserted.
+ */
+ last_sub_transaction =
+ (this_write_count + state->wrote_so_far) ==
+ state->total_write_count;
+
+ res = usb_spi_map_error(spi_sub_transaction(
+ SPI_FLASH_DEVICE,
+ config->buffer + sizeof(struct raiden_response_hdr),
+ this_write_count, config->buffer, 0,
+ last_sub_transaction));
+ if (res) {
+ rsp->value = res;
+ rsp->packet_id = USB_SPI_PKT_ID_RSP_TRANSFER_START;
+ state->raiden_state = RAIDEN_IDLE;
+ return sizeof(*rsp);
+ }
+
+ if (last_sub_transaction) {
+ state->raiden_state = RAIDEN_IDLE;
+ rsp->value = 0;
+ rsp->packet_id = USB_SPI_PKT_ID_RSP_TRANSFER_START;
+ return sizeof(*rsp);
+ }
+ state->wrote_so_far += this_write_count;
+ return 0;
+ }
+
+ return 0;
+}
+
+void usb_spi_deferred(struct usb_spi_config const *config)
+{
+ uint16_t count;
+ int rv = EC_SUCCESS;
+
+ /*
+ * If our overall enabled state has changed we call the board specific
+ * enable or disable routines and save our new state.
+ */
+ int enabled = !!(config->state->enabled_host &
+ config->state->enabled_device);
+
+ if (enabled ^ config->state->enabled) {
+ if (enabled)
+ rv = usb_spi_board_enable(config->state->enabled_host);
+
+ else
+ usb_spi_board_disable();
+
+ /* Only update our state if we were successful. */
+ if (rv == EC_SUCCESS)
+ config->state->enabled = enabled;
+ }
+
+ /*
+ * And if there is a USB packet waiting we process it and generate a
+ * response.
+ */
+ count = usb_spi_read_packet(config);
+ if (count == 0)
+ return;
+
+ if (!config->state->enabled || usb_spi_shortcut_active()) {
+ struct raiden_response_hdr rsp_hdr = {
+ USB_SPI_PKT_ID_RSP_TRANSFER_START, USB_SPI_DISABLED
+ };
+ memcpy(config->buffer, &rsp_hdr, sizeof(rsp_hdr));
+ usb_spi_write_packet(config, sizeof(rsp_hdr));
+ return;
+ }
+
+ /*
+ * Call the protocol handler and send back the last message generated
+ * by the handler.
+ */
+ usb_spi_write_packet(config, process_raiden_packet(config, count));
+}
+
+static void usb_spi_written(struct consumer const *consumer, size_t count)
+{
+ struct usb_spi_config const *config =
+ DOWNCAST(consumer, struct usb_spi_config, consumer);
+
+ hook_call_deferred(config->deferred, 0);
+}
+
+struct consumer_ops const usb_spi_consumer_ops = {
+ .written = usb_spi_written,
+};
+
+void usb_spi_enable(struct usb_spi_config const *config, int enabled)
+{
+ config->state->enabled_device = 0;
+ if (enabled) {
+#ifdef CONFIG_CASE_CLOSED_DEBUG_V1
+ if (ccd_is_cap_enabled(CCD_CAP_AP_FLASH))
+ config->state->enabled_device |= USB_SPI_AP;
+ if (ccd_is_cap_enabled(CCD_CAP_EC_FLASH))
+ config->state->enabled_device |= USB_SPI_EC;
+#else
+ config->state->enabled_device = USB_SPI_ALL;
+#endif
+ }
+
+ hook_call_deferred(config->deferred, 0);
+}
+
diff --git a/include/config.h b/include/config.h
index 5bab6a1c08..7f334bd5dc 100644
--- a/include/config.h
+++ b/include/config.h
@@ -4066,8 +4066,10 @@
#undef CONFIG_USB_GPIO
/*****************************************************************************/
-/* USB SPI config */
+/* USB SPI legacy protocol (V1). */
#undef CONFIG_USB_SPI
+/* USB_SPI protocol V2. */
+#undef CONFIG_USB_SPI_V2
/*****************************************************************************/
/* USB I2C config */
diff --git a/include/usb_descriptor.h b/include/usb_descriptor.h
index 4b7184a311..153c648085 100644
--- a/include/usb_descriptor.h
+++ b/include/usb_descriptor.h
@@ -191,7 +191,11 @@ struct usb_endpoint_descriptor {
#define USB_PROTOCOL_GOOGLE_SERIAL 0x01
#define USB_SUBCLASS_GOOGLE_SPI 0x51
-#define USB_PROTOCOL_GOOGLE_SPI 0x01
+#ifdef CONFIG_USB_SPI_V2
+#define USB_PROTOCOL_GOOGLE_SPI 0x02
+#else
+#define USB_PROTOCOL_GOOGLE_SPI 0x01
+#endif
#define USB_SUBCLASS_GOOGLE_I2C 0x52
#define USB_PROTOCOL_GOOGLE_I2C 0x01