diff options
Diffstat (limited to 'chip/g/usb_spi_v2.c')
-rw-r--r-- | chip/g/usb_spi_v2.c | 693 |
1 files changed, 693 insertions, 0 deletions
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); +} + |