/* 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; }