blob: f4d6a65fc4b6d2cdaf631262a6529d9332728a98 [file] [log] [blame]
/* Copyright 2020 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 "clock.h"
#include "dma.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "queue_policies.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "usart_rx_dma.h"
#include "usart_host_command.h"
#include "usart-stm32f4.h"
#include "util.h"
/* Console output macros */
#define CPRINTS(format, args...) cprints(CC_HOSTCMD, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_HOSTCMD, format, ## args)
/*
* Timeout to wait for complete request packet
*
* This value determines how long we should wait for entire packet to arrive.
* USART host command handler should wait for at least 75% of
* EC_MSG_DEADLINE_MS, before declaring timeout and dropping the packet.
*
* This timeout should be less than host's driver timeout to make sure that
* last packet can be successfully discarded before AP attempts to resend
* request. AP driver waits for EC_MSG_DEADLINE_MS = 200 before attempting a
* retry.
*/
#define USART_REQ_RX_TIMEOUT (150 * MSEC)
/*
* Timeout to wait for overrun bytes on USART
*
* This values determines how long call to process_request should be deferred
* in case host is sending extra bytes. This value is based on DMA buffer size.
*
* There is no guarantee that AP will send continuous bytes on usart. Wait
* for USART_DEFERRED_PROCESS_REQ_TIMEOUT_US to check if host is sending
* extra bytes.
* Note: This value affects the response latency.
*/
#define USART_DEFERRED_PROCESS_REQ_TIMEOUT 300
/*
* Max data size for a version 3 request/response packet. This is big enough
* to handle a request/response header, flash write offset/size and 512 bytes
* of request payload or 224 bytes of response payload.
*/
#define USART_MAX_REQUEST_SIZE 0x220
#define USART_MAX_RESPONSE_SIZE 0x100
/*
* FIFO size for USART DMA. Should be big enough to handle worst case
* data processing
*/
#define USART_DMA_FIFO_SIZE 0x110
/* Local definitions */
/*
* Raw USART RX/TX byte buffers.
*/
static uint8_t usart_in_buffer[USART_MAX_REQUEST_SIZE] __aligned(4);
static uint8_t usart_out_buffer[USART_MAX_RESPONSE_SIZE] __aligned(4);
/*
* Maintain head position of in buffer
* Head always starts with zero and goes up to max bytes.
* Once the buffer contents are read, it should go back to zero.
*/
static uint16_t usart_in_head;
/*
* Maintain head position of out buffer
* Head always starts from zero and goes up to max bytes.
* Head is moved by tx interrupt handler to response size sent by host command
* task. Once all the bytes are sent (head == tail) both should go back to 0.
*/
static uint16_t usart_out_head;
/*
* Once the response is ready, get the datalen
*/
static uint16_t usart_out_datalen;
/*
* Enumeration to maintain different states of incoming request from
* host
*/
static enum uart_host_command_state {
/*
* USART host command handler not enabled.
*/
USART_HOST_CMD_STATE_DISABLED,
/*
* Ready to receive next request
* This state represents USART layer is initialized and ready to
* receive host request. Once the response is sent, current_state is
* reset to this state to accept next packet.
*/
USART_HOST_CMD_READY_TO_RX,
/*
* Receiving request
* After first byte is received current_state is moved to receiving
* state until all the header bytes + datalen bytes are received.
* If host_request_timeout was called in this state, it would be
* because of an underrun situation.
*/
USART_HOST_CMD_RECEIVING,
/*
* Receiving complete
* Once all the header bytes + datalen bytes are received, current_state
* is moved to complete. Ideally, host should wait for response or retry
* timeout before sending anymore bytes, otherwise current_state will
* be moved to overrun to represent extra bytes sent by host.
*/
USART_HOST_CMD_COMPLETE,
/*
* Processing request
* Once the process_request starts processing usart_in_buffer,
* current_state is moved to processing state. Host should not send
* any bytes in this state as it would be considered contiguous
* request.
*/
USART_HOST_CMD_PROCESSING,
/*
* Sending response
* Once host task is ready with the response bytes, current_state is
* moved to sending state.
*/
USART_HOST_CMD_SENDING,
/*
* Received bad data
* If bad packet header is received, current_state is moved to rx_bad
* state and after rx_timeout all the bytes are dropped.
*/
USART_HOST_CMD_RX_BAD,
/*
* Receiving data overrun bytes
* If extra bytes are received after current_state is in complete,
* host is sending extra bytes which indicates data overrun.
*/
USART_HOST_CMD_RX_OVERRUN,
} current_state __aligned(4);
/*
* This diagram is the state machine representation of USART host
* command layer.
*
* This layer is responsible for checking packet integrity of incoming bytes
* on usart transceiver. It will only process packet header to check version,
* data_len. This layer will not process payload bytes.
*
* STATE = USART_HOST_CMD_STATE_DISABLED
*
* Initialize USART and local variables
*
* STATE = USART_HOST_CMD_READY_TO_RX
*
* |<---------- HOST RETRY TIMEOUT = 200 ms ---------->|
* |
* |--------------USART_REQ_RX_TIMEOUT------>|
* | Underrun if request not complete -->|
* | |<-- USART ready to rx
* |____REQUEST____ ____REQUEST____
* | | | | | |
* | HDR | DATA | | HDR | DATA |
* |_____|_________| |_____|_________|
* |
* |<-- Request packet start
* |
* STATE = USART_HOST_CMD_RECEIVING
* |
* |<-- HDR received, now we will wait for data len bytes
* |
* If bad packet is received, move state to rx_bad
* STATE = USART_HOST_CMD_RX_BAD
* Ignore data processing, print status on console and reset layer -----------
* | |
* |<-- Request packet end (data rx complete) |
* | |
* If request_timeout is called, it represents packet underrun |
* Ignore data processing, print status on console and reset layer -----------
* | |
* STATE = USART_HOST_CMD_COMPLETE |
* | |
* |<-- Deferred call to process request |
* | |
* If extra byte is received, move state to overrun |
* STATE = USART_HOST_CMD_RX_OVERRUN |
* Ignore data processing, print status on console and reset layer -----------
* | |
* -->| |<-- USART_DEFERRED_PROCESS_REQ_TIMEOUT |
* | Start process request |
* | |
* STATE = USART_HOST_CMD_PROCESSING |
* | |
* Send ec_host_request to host command task |
* |<-- Packet sent to host command task |
* >| |<-- host command task process time |
* |<-- host command task ready for response |
* | |
* STATE = USART_HOST_CMD_SENDING |
* | |
* |____RESPONSE____ |
* | | | |
* | HDR | DATA | |
* |_____|__________| |
* | |
* |<-- Response send complete |
* |
* STATE = USART_HOST_CMD_READY_TO_RX <------------------------------
*/
/*
* Local function definition
*/
static void usart_host_command_reset(void);
static void usart_host_command_request_timeout(void);
static void usart_host_command_process_request(void);
static void usart_host_command_process_response(struct host_packet *pkt);
/*
* Local variable declaration
*/
/*
* Configure dma instance for rx
*
* STM32_DMAS_USART1_RX is the DMA channel to be used for reception. This DMA
* channel is for the USART peripheral.
*
* A unnamed, valid, empty usart_rx_dma_state structure is required to manage
* DMA based transmission.
*
* USART_DMA_FIFO_SIZE is size of the valid, unnamed DMA circular buffer.
* This buffer is large enough to process worst case interrupt latency this
* layer can encounter.
*/
static struct usart_rx_dma const usart_host_command_rx_dma = {
.usart_rx = {
.producer_ops = {
.read = NULL,
},
.init = usart_rx_dma_init,
.interrupt = usart_host_command_rx_dma_interrupt,
.info = USART_RX_DMA_INFO,
},
.state = &((struct usart_rx_dma_state) {}),
.fifo_buffer = ((uint8_t[USART_DMA_FIFO_SIZE]) {}),
.fifo_size = USART_DMA_FIFO_SIZE,
.channel = STM32_DMAS_USART1_RX,
};
/*
* Configure USART structure with hardware, interrupt handlers, baudrate.
*/
static struct usart_config const tl_usart = {
.hw = &CONFIG_UART_HOST_COMMAND_HW,
.rx = &usart_host_command_rx_dma.usart_rx,
.tx = &usart_host_command_tx_interrupt,
.state = &((struct usart_state){}),
.baud = CONFIG_UART_HOST_COMMAND_BAUD_RATE,
.flags = 0,
};
/*
* Local function declaration
*/
/*
* This function will be called only if request rx timed out.
* Drop the packet and put tl state into RX_READY
*/
static void usart_host_command_request_timeout(void)
{
switch (current_state) {
case USART_HOST_CMD_RECEIVING:
/* If state is receiving then timeout was hit due to underrun */
CPRINTS("USART HOST CMD ERROR: Request underrun detected.");
break;
case USART_HOST_CMD_RX_OVERRUN:
/* If state is rx_overrun then timeout was hit because
* process request was cancelled and extra rx bytes were
* dropped
*/
CPRINTS("USART HOST CMD ERROR: Request overrun detected.");
break;
case USART_HOST_CMD_RX_BAD:
/* If state is rx_bad then packet header was bad and process
* request was cancelled to drop all incoming bytes.
*/
CPRINTS("USART HOST CMD ERROR: Bad packet header detected.");
break;
default:
CPRINTS("USART HOST CMD ERROR: Request timeout mishandled");
}
/* Reset host command layer to accept new request */
usart_host_command_reset();
}
DECLARE_DEFERRED(usart_host_command_request_timeout);
/*
* This function is called from interrupt handler after entire packet is
* received.
*/
static void usart_host_command_process_request(void)
{
/* Handle usart_in_buffer as ec_host_request */
struct ec_host_request *ec_request =
(struct ec_host_request *)usart_in_buffer;
/* Prepare host_packet for host command task */
static struct host_packet uart_packet;
/*
* Disable interrupts before processing request to be sent
* to host command task.
*/
interrupt_disable();
/*
* In case rx interrupt handler was called in this function's prologue,
* host was trying to send extra byte(s) exactly when
* USART_DEFERRED_PROCESS_REQ_TIMEOUT expired. If state is
* not USART_HOST_CMD_COMPLETE, overrun condition is already
* handled.
*/
if (current_state != USART_HOST_CMD_COMPLETE) {
/* Enable interrupts before exiting this function. */
interrupt_enable();
return;
}
/* Move current_state to USART_HOST_CMD_PROCESSING */
current_state = USART_HOST_CMD_PROCESSING;
/* Enable interrupts as current_state is safely handled. */
interrupt_enable();
/*
* Cancel deferred call to timeout handler as request
* received was good.
*/
hook_call_deferred(
&usart_host_command_request_timeout_data,
-1);
uart_packet.send_response = usart_host_command_process_response;
uart_packet.request = usart_in_buffer;
uart_packet.request_temp = NULL;
uart_packet.request_max = sizeof(usart_in_buffer);
uart_packet.request_size =
host_request_expected_size(ec_request);
uart_packet.response = usart_out_buffer;
uart_packet.response_max = sizeof(usart_out_buffer);
uart_packet.response_size = 0;
uart_packet.driver_result = EC_RES_SUCCESS;
/* Process usart_packet */
host_packet_receive(&uart_packet);
}
DECLARE_DEFERRED(usart_host_command_process_request);
/*
* This function is called from host command task after it is ready with a
* response.
*/
static void usart_host_command_process_response(struct host_packet *pkt)
{
/* Disable interrupts before entering critical section. */
interrupt_disable();
/*
* Send host command response in usart_out_buffer via
* tx_interrupt_handler.
*
* Send response if current state is USART_HOST_CMD_PROCESSING
* state. If this layer is in any other state drop response and
* let request timeout handler handle state transitions.
*/
if (current_state != USART_HOST_CMD_PROCESSING) {
/* Enable interrupts before exiting critical section. */
interrupt_enable();
return;
}
/* Move to sending state. */
current_state = USART_HOST_CMD_SENDING;
/* Enable interrupts before exiting critical section. */
interrupt_enable();
usart_out_datalen = pkt->response_size;
usart_out_head = 0;
/* Start sending response to host via usart tx by
* triggering tx interrupt.
*/
usart_tx_start(&tl_usart);
}
/*
* This function will drop current request, clear buffers.
*/
static void usart_host_command_reset(void)
{
/* Cancel deferred call to process_request. */
hook_call_deferred(
&usart_host_command_process_request_data,
-1);
/* Cancel deferred call to timeout handler. */
hook_call_deferred(
&usart_host_command_request_timeout_data,
-1);
/*
* Disable interrupts before entering critical region
* Operations in this section should be minimum to avoid
* harming the real-time characteristics of the runtime.
*/
interrupt_disable();
/* Clear in buffer, head and datalen */
usart_in_head = 0;
/* Clear out buffer, head and datalen */
usart_out_datalen = 0;
usart_out_head = 0;
/* Move to ready state*/
current_state = USART_HOST_CMD_READY_TO_RX;
/* Enable interrupts before exiting critical region
*/
interrupt_enable();
}
/*
* Exported functions
*/
/*
* Initialize USART host command layer.
*/
void usart_host_command_init(void)
{
/* USART host command layer starts in DISABLED state */
current_state = USART_HOST_CMD_STATE_DISABLED;
/* Initialize transport uart */
usart_init(&tl_usart);
/* Initialize local variables */
usart_in_head = 0;
usart_out_head = 0;
usart_out_datalen = 0;
/* Move to ready state */
current_state = USART_HOST_CMD_READY_TO_RX;
}
/*
* Function to handle incoming bytes from DMA interrupt handler
*
*/
size_t usart_host_command_rx_append_data(struct usart_config const *config,
const uint8_t *src, size_t count)
{
/* Define ec_host_request pointer to process in bytes later*/
struct ec_host_request *ec_request =
(struct ec_host_request *) usart_in_buffer;
/* Once the header is received, store the datalen */
static int usart_in_datalen;
/*
* Host can send extra bytes than in header data_len
* Only copy valid bytes in buffer
*/
if (current_state == USART_HOST_CMD_READY_TO_RX ||
current_state == USART_HOST_CMD_RECEIVING ||
(usart_in_head + count) < USART_MAX_REQUEST_SIZE) {
/* Copy all the bytes from DMA FIFO */
memcpy(usart_in_buffer + usart_in_head,
src, count);
}
/*
* Add incoming byte count to usart_in_head.
* Even if overflow bytes are not copied in buffer, maintain
* the overflow count so that packet can be dropped later in this
* function.
*/
usart_in_head += count;
if (current_state == USART_HOST_CMD_READY_TO_RX) {
/* Kick deferred call to request timeout handler */
hook_call_deferred(&usart_host_command_request_timeout_data,
USART_REQ_RX_TIMEOUT);
/* Move current state to receiving */
current_state = USART_HOST_CMD_RECEIVING;
}
if (usart_in_head >= sizeof(struct ec_host_request)) {
/* Buffer has request header. Check header and get data_len */
usart_in_datalen = host_request_expected_size(ec_request);
if (usart_in_datalen == 0 ||
usart_in_datalen > USART_MAX_REQUEST_SIZE) {
/* EC host request version not compatible or
* reserved byte is not zero.
*/
current_state = USART_HOST_CMD_RX_BAD;
} else if (usart_in_head == usart_in_datalen) {
/*
* Once all the datalen bytes are received, wait for
* USART_DEFERRED_PROCESS_REQ_TIMEOUT to call
* process_request function. This is to catch overrun
* bytes before processing the packet.
*/
hook_call_deferred(
&usart_host_command_process_request_data,
USART_DEFERRED_PROCESS_REQ_TIMEOUT);
/* If no data in request, packet is complete */
current_state = USART_HOST_CMD_COMPLETE;
} else if (usart_in_head > usart_in_datalen) {
/* Cancel deferred call to process_request */
hook_call_deferred(
&usart_host_command_process_request_data,
-1);
/* Move state to overrun*/
current_state = USART_HOST_CMD_RX_OVERRUN;
}
}
if (current_state == USART_HOST_CMD_PROCESSING)
/* Host should not send data before receiving a response.
* Since the request was already sent to host command task,
* just notify console about this. After response is sent
* dma will be cleared to handle next packet
*/
CPRINTS("USART HOST CMD ERROR: Contiguous packets detected.");
/* Return count to show all incoming bytes were processed */
return count;
}
/*
* This function processes the outgoing bytes from tl usart.
*/
size_t usart_host_command_tx_remove_data(struct usart_config const *config,
uint8_t *dest)
{
size_t bytes_remaining = 0;
if (current_state == USART_HOST_CMD_SENDING &&
usart_out_datalen != 0) {
/* Calculate byte_remaining in out_buffer */
bytes_remaining = usart_out_datalen - usart_out_head;
/* Get char on the head */
*((uint8_t *) dest) = usart_out_buffer[usart_out_head++];
/* If no bytes remaining, reset layer to accept next
* request.
*/
if (bytes_remaining == 0)
usart_host_command_reset();
}
/* Return count of bytes remaining in out buffer */
return bytes_remaining;
}
/*
* Get protocol information
*/
enum ec_status usart_get_protocol_info(struct host_cmd_handler_args *args)
{
struct ec_response_get_protocol_info *r = args->response;
memset(r, 0, sizeof(*r));
r->protocol_versions |= BIT(3);
r->max_request_packet_size = USART_MAX_REQUEST_SIZE;
r->max_response_packet_size = USART_MAX_RESPONSE_SIZE;
r->flags = EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}