summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chip/stm32/registers.h3
-rw-r--r--chip/stm32/usb_spi.c361
-rw-r--r--chip/stm32/usb_spi.h368
-rw-r--r--include/usb_descriptor.h2
4 files changed, 630 insertions, 104 deletions
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index 97f2f7d127..ea6b2db84a 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -436,8 +436,9 @@ typedef volatile struct timer_ctlr timer_ctlr_t;
#define EP_STATUS_OUT 0x0100
-#define EP_TX_RX_MASK (EP_TX_MASK | EP_RX_MASK)
+#define EP_TX_RX_MASK (EP_TX_MASK | EP_RX_MASK)
#define EP_TX_RX_VALID (EP_TX_VALID | EP_RX_VALID)
+#define EP_TX_RX_NAK (EP_TX_NAK | EP_RX_NAK)
#define STM32_TOGGLE_EP(n, mask, val, flags) \
STM32_USB_EP(n) = (((STM32_USB_EP(n) & (EP_MASK | (mask))) \
diff --git a/chip/stm32/usb_spi.c b/chip/stm32/usb_spi.c
index 5cc47743f4..54caae015e 100644
--- a/chip/stm32/usb_spi.c
+++ b/chip/stm32/usb_spi.c
@@ -14,10 +14,11 @@
/* Forward declare platform specific functions. */
static bool usb_spi_received_packet(struct usb_spi_config const *config);
+static bool usb_spi_transmitted_packet(struct usb_spi_config const *config);
static void usb_spi_read_packet(struct usb_spi_config const *config,
- struct usb_spi_packet_ctx_t *packet);
+ struct usb_spi_packet_ctx *packet);
static void usb_spi_write_packet(struct usb_spi_config const *config,
- struct usb_spi_packet_ctx_t *packet);
+ struct usb_spi_packet_ctx *packet);
/*
* Map EC error codes to USB_SPI error codes.
@@ -44,8 +45,8 @@ static int16_t usb_spi_map_error(int error)
*
* @returns USB_SPI_RX_DATA_OVERFLOW if the source packet is too large
*/
-static int usb_spi_read_usb_packet(struct usb_spi_transfer_ctx_t *dst,
- const struct usb_spi_packet_ctx_t *src)
+static int usb_spi_read_usb_packet(struct usb_spi_transfer_ctx *dst,
+ const struct usb_spi_packet_ctx *src)
{
size_t max_read_length = dst->transfer_size - dst->transfer_index;
size_t bytes_in_buffer = src->packet_size - src->header_size;
@@ -71,8 +72,8 @@ static int usb_spi_read_usb_packet(struct usb_spi_transfer_ctx_t *dst,
* @param dst Destination packet context we are writing data to.
* @param src Source transmit context we are reading data from.
*/
-static void usb_spi_fill_usb_packet(struct usb_spi_packet_ctx_t *dst,
- struct usb_spi_transfer_ctx_t *src)
+static void usb_spi_fill_usb_packet(struct usb_spi_packet_ctx *dst,
+ struct usb_spi_transfer_ctx *src)
{
size_t transfer_size = src->transfer_size - src->transfer_index;
size_t max_buffer_size = USB_MAX_PACKET_SIZE - dst->header_size;
@@ -119,7 +120,230 @@ static void usb_spi_reset_interface(struct usb_spi_config const *config)
}
/*
- * Deferred function to handle state changes, process USB SPI packets,
+ * Returns if the response transfer is in progress.
+ *
+ * @param config USB SPI config
+ *
+ * @returns True if a response transfer is in progress.
+ */
+static bool usb_spi_response_in_progress(struct usb_spi_config const *config)
+{
+ if ((config->state->mode == USB_SPI_MODE_START_RESPONSE) ||
+ (config->state->mode == USB_SPI_MODE_CONTINUE_RESPONSE)) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Prep the state to construct a new response. This sets the transfer
+ * contexts, the mode, and status code. If a non-zero status code is
+ * returned, then no payload will be transmitted.
+ *
+ * @param config USB SPI config
+ * @param status_code status code to set for the response.
+ */
+static void setup_transfer_response(struct usb_spi_config const *config,
+ uint16_t status_code)
+{
+ config->state->status_code = status_code;
+ config->state->spi_read_ctx.transfer_index = 0;
+ config->state->mode = USB_SPI_MODE_START_RESPONSE;
+
+ /* If an error occurred, transmit an empty start packet. */
+ if (status_code != USB_SPI_SUCCESS)
+ config->state->spi_read_ctx.transfer_size = 0;
+}
+
+/*
+ * Constructs the response packet containing the SPI configuration.
+ *
+ * @param config USB SPI config
+ * @param packet Packet buffer we will be transmitting.
+ */
+static void create_spi_config_response(struct usb_spi_config const *config,
+ struct usb_spi_packet_ctx *packet)
+{
+ /* Construct the response packet. */
+ packet->rsp_config.packet_id = USB_SPI_PKT_ID_RSP_USB_SPI_CONFIG;
+ packet->rsp_config.max_write_count = USB_SPI_MAX_WRITE_COUNT;
+ packet->rsp_config.max_read_count = USB_SPI_MAX_READ_COUNT;
+ /* Set the feature flags. */
+ packet->rsp_config.feature_bitmap = 0;
+#ifndef CONFIG_SPI_HALFDUPLEX
+ packet->rsp_config.feature_bitmap |=
+ USB_SPI_FEATURE_FULL_DUPLEX_SUPPORTED;
+#endif
+ packet->packet_size =
+ sizeof(struct usb_spi_response_configuration_v2);
+}
+
+/*
+ * If we have a transfer response in progress, this will construct the
+ * next entry. If no transfer is in progress or if we are unable to
+ * create the next packet, it will not modify tx_packet.
+ *
+ * @param config USB SPI config
+ * @param packet Packet buffer we will be transmitting.
+ */
+static void usb_spi_create_spi_transfer_response(
+ struct usb_spi_config const *config,
+ struct usb_spi_packet_ctx *transmit_packet)
+{
+
+ if (!usb_spi_response_in_progress(config))
+ return;
+
+ if (config->state->spi_read_ctx.transfer_index == 0) {
+
+ /* Transmit the first packet with the status code. */
+ transmit_packet->header_size =
+ offsetof(struct usb_spi_response_v2, data);
+ transmit_packet->rsp_start.packet_id =
+ USB_SPI_PKT_ID_RSP_TRANSFER_START;
+ transmit_packet->rsp_start.status_code =
+ config->state->status_code;
+
+ usb_spi_fill_usb_packet(transmit_packet,
+ &config->state->spi_read_ctx);
+ } else if (config->state->spi_read_ctx.transfer_index <
+ config->state->spi_read_ctx.transfer_size) {
+
+ /* Transmit the continue packets. */
+ transmit_packet->header_size =
+ offsetof(struct usb_spi_continue_v2, data);
+ transmit_packet->rsp_continue.packet_id =
+ USB_SPI_PKT_ID_RSP_TRANSFER_CONTINUE;
+ transmit_packet->rsp_continue.data_index =
+ config->state->spi_read_ctx.transfer_index;
+
+ usb_spi_fill_usb_packet(transmit_packet,
+ &config->state->spi_read_ctx);
+ }
+ if (config->state->spi_read_ctx.transfer_index <
+ config->state->spi_read_ctx.transfer_size) {
+ config->state->mode = USB_SPI_MODE_CONTINUE_RESPONSE;
+ } else {
+ config->state->mode = USB_SPI_MODE_IDLE;
+ }
+}
+
+/*
+ * Process the rx packet.
+ *
+ * @param config USB SPI config
+ * @param packet Received packet to process.
+ */
+static void usb_spi_process_rx_packet(struct usb_spi_config const *config,
+ struct usb_spi_packet_ctx *packet)
+{
+ if (packet->packet_size < USB_SPI_MIN_PACKET_SIZE) {
+ /* No valid packet exists smaller than the packet id. */
+ setup_transfer_response(config, USB_SPI_RX_UNEXPECTED_PACKET);
+ return;
+ }
+ /* Reset the mode until we've processed the packet. */
+ config->state->mode = USB_SPI_MODE_IDLE;
+
+ switch (packet->packet_id) {
+ case USB_SPI_PKT_ID_CMD_GET_USB_SPI_CONFIG:
+ {
+ /* The host requires the SPI configuration. */
+ config->state->mode = USB_SPI_MODE_SEND_CONFIGURATION;
+ break;
+ }
+ case USB_SPI_PKT_ID_CMD_RESTART_RESPONSE:
+ {
+ /*
+ * The host has requested the device restart the last response.
+ * This is used to recover from lost USB packets without
+ * duplicating SPI transfers.
+ */
+ setup_transfer_response(config, config->state->status_code);
+ break;
+ }
+ case USB_SPI_PKT_ID_CMD_TRANSFER_START:
+ {
+ /* The host started a new USB SPI transfer */
+ size_t write_count = packet->cmd_start.write_count;
+ size_t read_count = packet->cmd_start.read_count;
+
+ if (!config->state->enabled) {
+ setup_transfer_response(config, USB_SPI_DISABLED);
+ } else if (write_count > USB_SPI_MAX_WRITE_COUNT) {
+ setup_transfer_response(config,
+ USB_SPI_WRITE_COUNT_INVALID);
+ } else if (read_count == USB_SPI_FULL_DUPLEX_ENABLED) {
+#ifndef CONFIG_SPI_HALFDUPLEX
+ /* Full duplex mode is not supported on this device. */
+ setup_transfer_response(config,
+ USB_SPI_UNSUPPORTED_FULL_DUPLEX);
+#endif
+ } else if (read_count > USB_SPI_MAX_READ_COUNT &&
+ read_count != USB_SPI_FULL_DUPLEX_ENABLED) {
+ setup_transfer_response(config,
+ USB_SPI_READ_COUNT_INVALID);
+ } else {
+ usb_spi_setup_transfer(config, write_count, read_count);
+ packet->header_size =
+ offsetof(struct usb_spi_command_v2, data);
+ config->state->status_code = usb_spi_read_usb_packet(
+ &config->state->spi_write_ctx, packet);
+ }
+
+ /* Send responses if we encountered an error. */
+ if (config->state->status_code != USB_SPI_SUCCESS) {
+ setup_transfer_response(config,
+ config->state->status_code);
+ break;
+ }
+
+ /* Start the SPI transfer when we've read all data. */
+ if (config->state->spi_write_ctx.transfer_index ==
+ config->state->spi_write_ctx.transfer_size) {
+ config->state->mode = USB_SPI_MODE_START_SPI;
+ }
+
+ break;
+ }
+ case USB_SPI_PKT_ID_CMD_TRANSFER_CONTINUE:
+ {
+ /*
+ * The host has sent a continue packet for the SPI transfer
+ * which contains additional data payload.
+ */
+ packet->header_size =
+ offsetof(struct usb_spi_continue_v2, data);
+ if (config->state->status_code == USB_SPI_SUCCESS) {
+ config->state->status_code = usb_spi_read_usb_packet(
+ &config->state->spi_write_ctx, packet);
+ }
+
+ /* Send responses if we encountered an error. */
+ if (config->state->status_code != USB_SPI_SUCCESS) {
+ setup_transfer_response(config,
+ config->state->status_code);
+ break;
+ }
+
+ /* Start the SPI transfer when we've read all data. */
+ if (config->state->spi_write_ctx.transfer_index ==
+ config->state->spi_write_ctx.transfer_size) {
+ config->state->mode = USB_SPI_MODE_START_SPI;
+ }
+
+ break;
+ }
+ default:
+ {
+ /* An unknown USB packet was delivered. */
+ setup_transfer_response(config, USB_SPI_RX_UNEXPECTED_PACKET);
+ break;
+ }
+ }
+}
+
+/* Deferred function to handle state changes, process USB SPI packets,
* and construct responses.
*
* @param config USB SPI config
@@ -127,10 +351,11 @@ static void usb_spi_reset_interface(struct usb_spi_config const *config)
void usb_spi_deferred(struct usb_spi_config const *config)
{
int enabled;
- struct usb_spi_packet_ctx_t *receive_packet =
+ struct usb_spi_packet_ctx *receive_packet =
&config->state->receive_packet;
- struct usb_spi_packet_ctx_t *transmit_packet =
+ struct usb_spi_packet_ctx *transmit_packet =
&config->state->transmit_packet;
+ transmit_packet->packet_size = 0;
if (config->flags & USB_SPI_CONFIG_FLAGS_IGNORE_HOST_SIDE_ENABLE)
enabled = config->state->enabled_device;
@@ -149,51 +374,50 @@ void usb_spi_deferred(struct usb_spi_config const *config)
config->state->enabled = enabled;
}
- /*
- * And if there is a USB packet waiting we process it and generate a
- * response.
- */
+ /* Read any packets from the endpoint. */
+
usb_spi_read_packet(config, receive_packet);
if (receive_packet->packet_size) {
- int write_count = receive_packet->command.write_count;
- int read_count = receive_packet->command.read_count;
- int status_code = USB_SPI_SUCCESS;
-
- receive_packet->header_size =
- offsetof(struct usb_spi_command_v1_t, data);
- transmit_packet->header_size =
- offsetof(struct usb_spi_response_v1_t, data);
-
- if (!config->state->enabled) {
- status_code = USB_SPI_DISABLED;
- } else if (write_count > USB_SPI_MAX_WRITE_COUNT ||
- write_count != (receive_packet->packet_size - 2)) {
- status_code = USB_SPI_WRITE_COUNT_INVALID;
- } else if (read_count > USB_SPI_MAX_READ_COUNT) {
- status_code = USB_SPI_READ_COUNT_INVALID;
- } else {
- usb_spi_setup_transfer(config, write_count, read_count);
+ usb_spi_process_rx_packet(config, receive_packet);
+ }
- status_code = usb_spi_read_usb_packet(
- &config->state->spi_write_ctx,
- receive_packet);
- }
+ /* Need to send the USB SPI configuration */
+ if (config->state->mode == USB_SPI_MODE_SEND_CONFIGURATION) {
+ create_spi_config_response(config, transmit_packet);
+ usb_spi_write_packet(config, transmit_packet);
+ config->state->mode = USB_SPI_MODE_IDLE;
+ return;
+ }
- /* If no error codes are present, perform the transfer. */
- if (status_code == USB_SPI_SUCCESS) {
- status_code = spi_transaction(SPI_FLASH_DEVICE,
- config->state->spi_write_ctx.buffer,
- config->state->spi_write_ctx.transfer_size,
- config->state->spi_read_ctx.buffer,
- config->state->spi_read_ctx.transfer_size);
- /* Cast the EC status code to USB SPI */
- status_code = usb_spi_map_error(status_code);
- usb_spi_fill_usb_packet(transmit_packet,
- &config->state->spi_read_ctx);
+ /* Start a new SPI transfer. */
+ if (config->state->mode == USB_SPI_MODE_START_SPI) {
+ uint16_t status_code;
+ int read_count = config->state->spi_read_ctx.transfer_size;
+#ifndef CONFIG_SPI_HALFDUPLEX
+ /*
+ * Handle the full duplex mode on supported platforms.
+ * The read count is equal to the write count.
+ */
+ if (read_count == USB_SPI_FULL_DUPLEX_ENABLED) {
+ config->state->spi_read_ctx.transfer_size =
+ config->state->spi_write_ctx.transfer_size;
+ read_count = SPI_READBACK_ALL;
}
+#endif
+ status_code = spi_transaction(SPI_FLASH_DEVICE,
+ config->state->spi_write_ctx.buffer,
+ config->state->spi_write_ctx.transfer_size,
+ config->state->spi_read_ctx.buffer,
+ read_count);
+
+ /* Cast the EC status code to USB SPI and start the response. */
+ status_code = usb_spi_map_error(status_code);
+ setup_transfer_response(config, status_code);
+ }
- transmit_packet->response.status_code = status_code;
-
+ if (usb_spi_response_in_progress(config) &&
+ usb_spi_transmitted_packet(config)) {
+ usb_spi_create_spi_transfer_response(config, transmit_packet);
usb_spi_write_packet(config, transmit_packet);
}
}
@@ -211,7 +435,6 @@ void usb_spi_enable(struct usb_spi_config const *config, int enabled)
hook_call_deferred(config->deferred, 0);
}
-
/*
* STM32 Platform: Receive the data from the endpoint into the packet and
* mark the endpoint as ready to accept more data.
@@ -220,7 +443,7 @@ void usb_spi_enable(struct usb_spi_config const *config, int enabled)
* @param packet Destination packet used to store the endpoint data.
*/
static void usb_spi_read_packet(struct usb_spi_config const *config,
- struct usb_spi_packet_ctx_t *packet)
+ struct usb_spi_packet_ctx *packet)
{
size_t packet_size;
@@ -247,7 +470,7 @@ static void usb_spi_read_packet(struct usb_spi_config const *config,
* @param packet Source packet we will write to the endpoint data.
*/
static void usb_spi_write_packet(struct usb_spi_config const *config,
- struct usb_spi_packet_ctx_t *packet)
+ struct usb_spi_packet_ctx *packet)
{
if (packet->packet_size == 0)
return;
@@ -256,7 +479,11 @@ static void usb_spi_write_packet(struct usb_spi_config const *config,
memcpy_to_usbram((void *)usb_sram_addr(config->ep_tx_ram),
packet->bytes, packet->packet_size);
btable_ep[config->endpoint].tx_count = packet->packet_size;
- /* Set endpoint as valid for transmitting new packet*/
+
+ /* Mark the packet as having no data. */
+ packet->packet_size = 0;
+
+ /* Set endpoint as valid for transmitting new packet. */
STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_VALID, 0);
}
@@ -272,14 +499,36 @@ static bool usb_spi_received_packet(struct usb_spi_config const *config)
return (STM32_USB_EP(config->endpoint) & EP_RX_MASK) != EP_RX_VALID;
}
-/*
- * STM32 Platform: Handle interrupt for USB data received.
+/* STM32 Platform: Returns the TX endpoint status
+ *
+ * @param config USB SPI config
+ *
+ * @returns Returns true when the TX endpoint transmitted
+ * the packet written.
+ */
+static bool usb_spi_transmitted_packet(struct usb_spi_config const *config)
+{
+ return (STM32_USB_EP(config->endpoint) & EP_TX_MASK) != EP_TX_VALID;
+}
+
+/* STM32 Platform: Handle interrupt for USB data received.
*
* @param config USB SPI config
*/
void usb_spi_rx(struct usb_spi_config const *config)
{
- STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_NAK, 0);
+ /*
+ * We need to set both the TX and RX endpoints to NAK to prevent
+ * transfers. The protocol requires responses to follow a command, but
+ * the USB host will request the next packet from the TX endpoint
+ * before the USB SPI has updated the memory in the buffer. By setting
+ * it to NAK in the ISR, it will not perform a transfer until the
+ * next packet is ready.
+ *
+ * This has a side effect of disabling the endpoint interrupts until
+ * they are set to valid or a USB reset events occurs.
+ */
+ STM32_TOGGLE_EP(config->endpoint, EP_TX_RX_MASK, EP_TX_RX_NAK, 0);
hook_call_deferred(config->deferred, 0);
}
@@ -292,6 +541,8 @@ void usb_spi_rx(struct usb_spi_config const *config)
void usb_spi_tx(struct usb_spi_config const *config)
{
STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_NAK, 0);
+
+ hook_call_deferred(config->deferred, 0);
}
/*
diff --git a/chip/stm32/usb_spi.h b/chip/stm32/usb_spi.h
index f4c34bbd85..591975234d 100644
--- a/chip/stm32/usb_spi.h
+++ b/chip/stm32/usb_spi.h
@@ -13,22 +13,120 @@
#include "usb_hw.h"
/*
- * Command:
- * +------------------+-----------------+------------------------+
- * | write count : 1B | read count : 1B | write payload : <= 62B |
- * +------------------+-----------------+------------------------+
+ * This SPI flash programming interface is designed to talk to a Chromium OS
+ * device over a Raiden USB connection.
*
- * write count: 1 byte, zero based count of bytes to write
+ * USB SPI Version 2:
*
- * read count: 1 byte, zero based count of bytes to read
+ * USB SPI version 2 adds support for larger SPI transfers and reduces the
+ * number of USB packets transferred. This improves performance when
+ * writing or reading large chunks of memory from a device. A packet ID
+ * field is used to distinguish the different 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.
*
- * write payload: up to 62 bytes of data to write, length must match
- * write count
+ * The USB SPI hosts which support packet version 2 are backwards compatible
+ * and use the bInterfaceProtocol field to identify which type of target
+ * they are connected to.
*
- * Response:
- * +-------------+-----------------------+
- * | status : 2B | read payload : <= 62B |
- * +-------------+-----------------------+
+ *
+ * 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:
+ *
+ * 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.
+ *
+ * +----------------+------------------+-----------------+---------------+
+ * | 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
@@ -47,33 +145,184 @@
* 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 62 bytes of data read from SPI, length will match
- * requested read count
+ * 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 it's 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: USB SPI Host error codes
+ * 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 PAYLOAD_SIZE_V1 (62)
+#define USB_SPI_FULL_DUPLEX_ENABLED (UINT16_MAX)
+
+#define USB_SPI_PAYLOAD_SIZE_V2_START (58)
+
+#define USB_SPI_PAYLOAD_SIZE_V2_RESPONSE (60)
+
+#define USB_SPI_PAYLOAD_SIZE_V2_CONTINUE (60)
+
+#define USB_SPI_PAYLOAD_SIZE_V2_ERROR (60)
-struct usb_spi_command_v1_t {
- int8_t write_count;
- /* -1 Indicates readback all on halfduplex compliant devices. */
- int8_t read_count;
- uint8_t data[PAYLOAD_SIZE_V1];
+#define USB_SPI_MIN_PACKET_SIZE (2)
+
+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,
+};
+
+enum feature_bitmap {
+ /* Indicates the platform supports full duplex mode. */
+ USB_SPI_FEATURE_FULL_DUPLEX_SUPPORTED = BIT(0)
+};
+
+struct usb_spi_response_configuration_v2 {
+ uint16_t packet_id;
+ uint16_t max_write_count;
+ uint16_t max_read_count;
+ uint16_t feature_bitmap;
+} __packed;
+
+struct usb_spi_command_v2 {
+ uint16_t packet_id;
+ uint16_t write_count;
+ /* UINT16_MAX Indicates readback all on halfduplex compliant devices. */
+ uint16_t read_count;
+ uint8_t data[USB_SPI_PAYLOAD_SIZE_V2_START];
} __packed;
-struct usb_spi_response_v1_t {
+struct usb_spi_response_v2 {
+ uint16_t packet_id;
uint16_t status_code;
- uint8_t data[PAYLOAD_SIZE_V1];
+ uint8_t data[USB_SPI_PAYLOAD_SIZE_V2_RESPONSE];
} __packed;
-struct usb_spi_packet_ctx_t {
+struct usb_spi_continue_v2 {
+ uint16_t packet_id;
+ uint16_t data_index;
+ uint8_t data[USB_SPI_PAYLOAD_SIZE_V2_CONTINUE];
+} __packed;
+
+struct usb_spi_packet_ctx {
union {
uint8_t bytes[USB_MAX_PACKET_SIZE];
- struct usb_spi_command_v1_t command;
- struct usb_spi_response_v1_t response;
+ uint16_t packet_id;
+ struct usb_spi_command_v2 cmd_start;
+ struct usb_spi_continue_v2 cmd_continue;
+ struct usb_spi_response_configuration_v2 rsp_config;
+ struct usb_spi_response_v2 rsp_start;
+ struct usb_spi_continue_v2 rsp_continue;
} __packed;
/*
* By storing the number of bytes in the header and knowing that the
@@ -82,24 +331,26 @@ struct usb_spi_packet_ctx_t {
* duplicating variables that can go out of sync.
*/
size_t header_size;
- /* Number of bytes in the packet.*/
+ /* Number of bytes in the packet. */
size_t packet_size;
};
enum usb_spi_error {
- USB_SPI_SUCCESS = 0x0000,
- USB_SPI_TIMEOUT = 0x0001,
- USB_SPI_BUSY = 0x0002,
- USB_SPI_WRITE_COUNT_INVALID = 0x0003,
- USB_SPI_READ_COUNT_INVALID = 0x0004,
- USB_SPI_DISABLED = 0x0005,
+ USB_SPI_SUCCESS = 0x0000,
+ USB_SPI_TIMEOUT = 0x0001,
+ USB_SPI_BUSY = 0x0002,
+ USB_SPI_WRITE_COUNT_INVALID = 0x0003,
+ USB_SPI_READ_COUNT_INVALID = 0x0004,
+ USB_SPI_DISABLED = 0x0005,
/* The RX continue packet's data index is invalid. */
- USB_SPI_RX_BAD_DATA_INDEX = 0x0006,
+ USB_SPI_RX_BAD_DATA_INDEX = 0x0006,
/* The RX endpoint has received more data than write count. */
- USB_SPI_RX_DATA_OVERFLOW = 0x0007,
+ USB_SPI_RX_DATA_OVERFLOW = 0x0007,
/* An unexpected packet arrived on the device. */
- USB_SPI_RX_UNEXPECTED_PACKET = 0x0008,
- USB_SPI_UNKNOWN_ERROR = 0x8000,
+ USB_SPI_RX_UNEXPECTED_PACKET = 0x0008,
+ /* The device does not support full duplex mode. */
+ USB_SPI_UNSUPPORTED_FULL_DUPLEX = 0x0009,
+ USB_SPI_UNKNOWN_ERROR = 0x8000,
};
enum usb_spi_request {
@@ -107,13 +358,20 @@ enum usb_spi_request {
USB_SPI_REQ_DISABLE = 0x0001,
};
-#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));
+/*
+ * To optimize for speed, we want to fill whole packets for each transfer
+ * This is done by setting the read and write counts to the payload sizes
+ * of the smaller start packet + N * continue packets.
+ *
+ * If a platform has a small maximum SPI transfer size, it can be optimized
+ * by setting these limits to the maximum transfer size.
+ */
+#define USB_SPI_BUFFER_SIZE (USB_SPI_PAYLOAD_SIZE_V2_START + \
+ (4 * USB_SPI_PAYLOAD_SIZE_V2_CONTINUE))
+#define USB_SPI_MAX_WRITE_COUNT USB_SPI_BUFFER_SIZE
+#define USB_SPI_MAX_READ_COUNT USB_SPI_BUFFER_SIZE
-struct usb_spi_transfer_ctx_t {
+struct usb_spi_transfer_ctx {
/* Address of transfer buffer. */
uint8_t *buffer;
/* Number of bytes in the transfer. */
@@ -122,6 +380,19 @@ struct usb_spi_transfer_ctx_t {
size_t transfer_index;
};
+enum usb_spi_mode {
+ /* No tasks are required. */
+ USB_SPI_MODE_IDLE = 0,
+ /* Indicates the device needs to send it's USB SPI configuration.*/
+ USB_SPI_MODE_SEND_CONFIGURATION,
+ /* Indicates we device needs start the SPI transfer. */
+ USB_SPI_MODE_START_SPI,
+ /* Indicates we should start a transfer response. */
+ USB_SPI_MODE_START_RESPONSE,
+ /* Indicates we need to continue a transfer response. */
+ USB_SPI_MODE_CONTINUE_RESPONSE,
+};
+
struct usb_spi_state {
/*
* The SPI bridge must be enabled both locally and by the host to allow
@@ -145,6 +416,9 @@ struct usb_spi_state {
*/
int enabled;
+ /* Mark the current operating mode. */
+ enum usb_spi_mode mode;
+
/*
* Stores the status code response for the transfer, delivered in the
* header for the first response packet. Error code is cleared during
@@ -153,15 +427,15 @@ struct usb_spi_state {
uint16_t status_code;
/* Stores the content from the USB packets */
- struct usb_spi_packet_ctx_t receive_packet;
- struct usb_spi_packet_ctx_t transmit_packet;
+ struct usb_spi_packet_ctx receive_packet;
+ struct usb_spi_packet_ctx transmit_packet;
/*
* Context structures representing the progress receiving the SPI
* write data and transmitting the SPI read data.
*/
- struct usb_spi_transfer_ctx_t spi_write_ctx;
- struct usb_spi_transfer_ctx_t spi_read_ctx;
+ struct usb_spi_transfer_ctx spi_write_ctx;
+ struct usb_spi_transfer_ctx spi_read_ctx;
};
/*
@@ -215,7 +489,7 @@ struct usb_spi_config {
INTERFACE, \
ENDPOINT, \
FLAGS) \
- static uint16_t CONCAT2(NAME, _buffer_)[USB_MAX_PACKET_SIZE / 2]; \
+ static uint16_t CONCAT2(NAME, _buffer_)[(USB_SPI_BUFFER_SIZE + 1) / 2];\
static usb_uint CONCAT2(NAME, _ep_rx_buffer_)[USB_MAX_PACKET_SIZE / 2] __usb_ram; \
static usb_uint CONCAT2(NAME, _ep_tx_buffer_)[USB_MAX_PACKET_SIZE / 2] __usb_ram; \
static void CONCAT2(NAME, _deferred_)(void); \
diff --git a/include/usb_descriptor.h b/include/usb_descriptor.h
index 4b7184a311..49114c38e0 100644
--- a/include/usb_descriptor.h
+++ b/include/usb_descriptor.h
@@ -191,7 +191,7 @@ struct usb_endpoint_descriptor {
#define USB_PROTOCOL_GOOGLE_SERIAL 0x01
#define USB_SUBCLASS_GOOGLE_SPI 0x51
-#define USB_PROTOCOL_GOOGLE_SPI 0x01
+#define USB_PROTOCOL_GOOGLE_SPI 0x02
#define USB_SUBCLASS_GOOGLE_I2C 0x52
#define USB_PROTOCOL_GOOGLE_I2C 0x01