/* Copyright 2014 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 "common.h" #include "link_defs.h" #include "registers.h" #include "spi.h" #include "usb_descriptor.h" #include "usb_hw.h" #include "usb_spi.h" #include "util.h" /* 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 *packet); static void usb_spi_write_packet(struct usb_spi_config const *config, struct usb_spi_packet_ctx *packet); /* * Map EC error codes to USB_SPI error codes. * * @param error EC error code * * @returns USB SPI error code based on the mapping. */ 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); } } /* * Read data into the receive buffer. * * @param dst Destination receive context we are writing data to. * @param src Source packet context we are reading data from. * * @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 *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; const uint8_t *packet_buffer = src->bytes + src->header_size; if (bytes_in_buffer > max_read_length) { /* * An error occurred, we should not receive more data than * the buffer can support. */ return USB_SPI_RX_DATA_OVERFLOW; } memcpy(dst->buffer + dst->transfer_index, packet_buffer, bytes_in_buffer); dst->transfer_index += bytes_in_buffer; return USB_SPI_SUCCESS; } /* * Fill the USB packet with data from the transmit buffer. * * @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 *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; uint8_t *packet_buffer = dst->bytes + dst->header_size; if (transfer_size > max_buffer_size) transfer_size = max_buffer_size; memcpy(packet_buffer, src->buffer + src->transfer_index, transfer_size); dst->packet_size = dst->header_size + transfer_size; src->transfer_index += transfer_size; } /* * Setup the USB SPI state to start a new SPI transfer. * * @param config USB SPI config * @param write_count Number of bytes to write in the SPI transfer * @param read_count Number of bytes to read in the SPI transfer */ static void usb_spi_setup_transfer(struct usb_spi_config const *config, size_t write_count, size_t read_count) { /* Reset any status code. */ config->state->status_code = USB_SPI_SUCCESS; /* Reset the write and read counts. */ config->state->spi_write_ctx.transfer_size = write_count; config->state->spi_write_ctx.transfer_index = 0; config->state->spi_read_ctx.transfer_size = read_count; config->state->spi_read_ctx.transfer_index = 0; } /* * Handle USB events that will reset the USB SPI state. * * @param config USB SPI config */ static void usb_spi_reset_interface(struct usb_spi_config const *config) { /* Setup a 0 byte transfer to clear the contexts. */ usb_spi_setup_transfer(config, 0, 0); } /* * 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 */ void usb_spi_deferred(struct usb_spi_config const *config) { int enabled; struct usb_spi_packet_ctx *receive_packet = &config->state->receive_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; else enabled = config->state->enabled_device && config->state->enabled_host; /* * If our overall enabled state has changed we call the board specific * enable or disable routines and save our new state. */ if (enabled != config->state->enabled) { if (enabled) usb_spi_board_enable(config); else usb_spi_board_disable(config); config->state->enabled = enabled; } /* Read any packets from the endpoint. */ usb_spi_read_packet(config, receive_packet); if (receive_packet->packet_size) { usb_spi_process_rx_packet(config, 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; } /* 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); } 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); } } /* * Sets which SPI modes will be enabled * * @param config USB SPI config * @param enabled usb_spi_request indicating which SPI mode is enabled. */ void usb_spi_enable(struct usb_spi_config const *config, int enabled) { config->state->enabled_device = 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. * * @param config USB SPI config * @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 *packet) { size_t packet_size; if (!usb_spi_received_packet(config)) { /* No data is present on the endpoint. */ packet->packet_size = 0; return; } /* Copy bytes from endpoint memory. */ packet_size = btable_ep[config->endpoint].rx_count & RX_COUNT_MASK; memcpy_from_usbram(packet->bytes, (void *)usb_sram_addr(config->ep_rx_ram), packet_size); packet->packet_size = packet_size; /* Set endpoint as valid for accepting new packet. */ STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_VALID, 0); } /* * STM32 Platform: Transmit data from the packet to the endpoint buffer. * If a packet is written, the endpoint will be marked valid for transmitting. * * @param config USB SPI 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 *packet) { if (packet->packet_size == 0) return; /* Copy bytes to endpoint memory. */ 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; /* 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); } /* * STM32 Platform: Returns the RX endpoint status * * @param config USB SPI config * * @returns Returns true when the RX endpoint has a packet. */ 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: 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) { /* * 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); } /* * STM32 Platform: Handle interrupt for USB data transmitted. * * @param config USB SPI 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); } /* * STM32 Platform: Handle interrupt for USB events * * @param config USB SPI config * @param evt USB event */ void usb_spi_event(struct usb_spi_config const *config, enum usb_ep_event evt) { int endpoint; if (evt != USB_EVENT_RESET) return; endpoint = config->endpoint; usb_spi_reset_interface(config); btable_ep[endpoint].tx_addr = usb_sram_addr(config->ep_tx_ram); btable_ep[endpoint].tx_count = 0; btable_ep[endpoint].rx_addr = usb_sram_addr(config->ep_rx_ram); btable_ep[endpoint].rx_count = 0x8000 | ((USB_MAX_PACKET_SIZE / 32 - 1) << 10); STM32_USB_EP(endpoint) = ((endpoint << 0) | /* Endpoint Addr*/ (2 << 4) | /* TX NAK */ (0 << 9) | /* Bulk EP */ (3 << 12)); /* RX Valid */ } /* * STM32 Platform: Handle control transfers. * * @param config USB SPI config * @param rx_buf Contains setup packet * @param tx_buf unused */ int usb_spi_interface(struct usb_spi_config const *config, usb_uint *rx_buf, usb_uint *tx_buf) { struct usb_setup_packet setup; usb_read_setup_packet(rx_buf, &setup); if (setup.bmRequestType != (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE)) return 1; if (setup.wValue != 0 || setup.wIndex != config->interface || setup.wLength != 0) return 1; switch (setup.bRequest) { case USB_SPI_REQ_ENABLE: config->state->enabled_host = 1; break; case USB_SPI_REQ_DISABLE: config->state->enabled_host = 0; break; default: return 1; } /* * Our state has changed, call the deferred function to handle the * state change. */ if (!(config->flags & USB_SPI_CONFIG_FLAGS_IGNORE_HOST_SIDE_ENABLE)) hook_call_deferred(config->deferred, 0); usb_spi_reset_interface(config); btable_ep[0].tx_count = 0; STM32_TOGGLE_EP(0, EP_TX_RX_MASK, EP_TX_RX_VALID, EP_STATUS_OUT); return 0; }