blob: d7f4cbe003639ef13f1b3b7630465012314d22f6 [file] [log] [blame]
/*
* Copyright (c) 2023 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/mgmt/ec_host_cmd/backend.h>
#include <zephyr/mgmt/ec_host_cmd/ec_host_cmd.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/time_units.h>
#include <zephyr/sys/util.h>
#include <zephyr/types.h>
LOG_MODULE_REGISTER(host_cmd_uart, CONFIG_EC_HC_LOG_LEVEL);
/* TODO: Try to use circular mode once it is supported and compare timings */
enum uart_host_command_state {
/*
* UART host command handler not enabled.
*/
UART_HOST_CMD_STATE_DISABLED,
/*
* This state represents UART layer is initialized and ready to
* receive host request. Once the response is sent, the current state is
* reset to this state to accept next packet.
*/
UART_HOST_CMD_READY_TO_RX,
/*
* After first byte is received the 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.
*/
UART_HOST_CMD_RECEIVING,
/*
* Once the process_request starts processing the rx buffer,
* the current state is moved to processing state. Host should not send
* any bytes in this state as it would be considered contiguous
* request.
*/
UART_HOST_CMD_PROCESSING,
/*
* Once host task is ready with the response bytes, the current state is
* moved to sending state.
*/
UART_HOST_CMD_SENDING,
/*
* If bad packet header is received, the current state is moved to rx_bad
* state and after timeout all the bytes are dropped.
*/
UART_HOST_CMD_RX_BAD,
/*
* If extra bytes are received when the host command is being processed,
* host is sending extra bytes which indicates data overrun.
*/
UART_HOST_CMD_RX_OVERRUN,
};
struct ec_host_cmd_uart_ctx {
const struct device *uart_dev;
struct ec_host_cmd_rx_ctx *rx_ctx;
const size_t rx_buf_size;
struct ec_host_cmd_tx_buf *tx_buf;
struct k_work_delayable timeout_work;
enum uart_host_command_state state;
};
static int request_expected_size(const struct ec_host_cmd_request_header *r)
{
/* Check host request version */
if (r->prtcl_ver != 3) {
return 0;
}
/* Reserved byte should be 0 */
if (r->reserved) {
return 0;
}
return sizeof(*r) + r->data_len;
}
#define EC_HOST_CMD_UART_DEFINE(_name) \
static struct ec_host_cmd_uart_ctx _name##_hc_uart = { \
.rx_buf_size = CONFIG_EC_HOST_CMD_HANDLER_RX_BUFFER_SIZE, \
}; \
static struct ec_host_cmd_backend _name = { \
.api = &ec_host_cmd_api, \
.ctx = &_name##_hc_uart, \
}
/* Waiting time in microseconds to detect overrun */
#define UART_OVERRUN_TIMEOUT_US 300
/* Timeout after receiving first byte */
#define UART_REQ_RX_TIMEOUT K_MSEC(150)
static void rx_timeout(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct ec_host_cmd_uart_ctx *hc_uart =
CONTAINER_OF(dwork, struct ec_host_cmd_uart_ctx, timeout_work);
int res;
switch (hc_uart->state) {
case UART_HOST_CMD_RECEIVING:
/* If state is receiving then timeout was hit due to underrun */
LOG_ERR("Request underrun detected");
break;
case UART_HOST_CMD_RX_OVERRUN:
/* If state is rx_overrun then timeout was hit because
* process request was cancelled and extra rx bytes were
* dropped
*/
LOG_ERR("Request overrun detected");
break;
case UART_HOST_CMD_RX_BAD:
/* If state is rx_bad then packet header was bad and process
* request was cancelled to drop all incoming bytes.
*/
LOG_ERR("Bad packet header detected");
break;
default:
LOG_ERR("Request timeout mishandled, state: %d", hc_uart->state);
}
res = uart_rx_disable(hc_uart->uart_dev);
res = uart_rx_enable(hc_uart->uart_dev, hc_uart->rx_ctx->buf, hc_uart->rx_buf_size,
UART_OVERRUN_TIMEOUT_US);
hc_uart->state = UART_HOST_CMD_READY_TO_RX;
}
static void uart_callback(const struct device *dev, struct uart_event *evt, void *user_data)
{
struct ec_host_cmd_uart_ctx *hc_uart = user_data;
size_t new_len;
switch (evt->type) {
case UART_RX_RDY:
if (hc_uart->state == UART_HOST_CMD_READY_TO_RX) {
hc_uart->rx_ctx->len = 0;
hc_uart->state = UART_HOST_CMD_RECEIVING;
k_work_reschedule(&hc_uart->timeout_work, UART_REQ_RX_TIMEOUT);
} else if (hc_uart->state == UART_HOST_CMD_PROCESSING ||
hc_uart->state == UART_HOST_CMD_SENDING) {
LOG_ERR("UART HOST CMD ERROR: Received data while processing or sending");
return;
} else if (hc_uart->state == UART_HOST_CMD_RX_BAD ||
hc_uart->state == UART_HOST_CMD_RX_OVERRUN) {
/* Wait for timeout if an error has been detected */
return;
}
__ASSERT(hc_uart->state == UART_HOST_CMD_RECEIVING,
"UART Host Command state mishandled, state: %d", hc_uart->state);
new_len = hc_uart->rx_ctx->len + evt->data.rx.len;
if (new_len > hc_uart->rx_buf_size) {
/* Bad data error, set the state and wait for timeout */
hc_uart->state = UART_HOST_CMD_RX_BAD;
return;
}
hc_uart->rx_ctx->len = new_len;
if (hc_uart->rx_ctx->len >= sizeof(struct ec_host_cmd_request_header)) {
/* Buffer has request header. Check header and get data_len */
size_t expected_len = request_expected_size(
(struct ec_host_cmd_request_header *)hc_uart->rx_ctx->buf);
if (expected_len == 0 || expected_len > hc_uart->rx_buf_size) {
/* Invalid expected size, set the state and wait for timeout */
hc_uart->state = UART_HOST_CMD_RX_BAD;
} else if (hc_uart->rx_ctx->len == expected_len) {
/* Don't wait for overrun, because it is already done
* in a UART driver.
*/
(void)k_work_cancel_delayable(&hc_uart->timeout_work);
/* Disable receiving to prevent overwriting the rx buffer while
* processing. Enabling receiving to a temporary buffer to detect
* unexpected transfer while processing increases average handling
* time ~40% so don't do that.
*/
uart_rx_disable(hc_uart->uart_dev);
/* If no data more in request, packet is complete. Start processing
*/
hc_uart->state = UART_HOST_CMD_PROCESSING;
k_sem_give(&hc_uart->rx_ctx->handler_owns);
} else if (hc_uart->rx_ctx->len > expected_len) {
/* Overrun error, set the state and wait for timeout */
hc_uart->state = UART_HOST_CMD_RX_OVERRUN;
}
}
break;
case UART_RX_BUF_REQUEST:
/* Do not provide the second buffer, because we reload DMA after every packet. */
break;
case UART_TX_DONE:
if (hc_uart->state != UART_HOST_CMD_SENDING) {
LOG_ERR("UART HOST CMD ERROR: unexpected end of sending");
}
/* Receiving is already enabled in the send function. */
hc_uart->state = UART_HOST_CMD_READY_TO_RX;
break;
case UART_RX_STOPPED:
LOG_ERR("UART HOST CMD ERROR: Receiving data stopped");
break;
default:
break;
}
}
static int ec_host_cmd_uart_init(const struct ec_host_cmd_backend *backend,
struct ec_host_cmd_rx_ctx *rx_ctx, struct ec_host_cmd_tx_buf *tx)
{
int ret;
struct ec_host_cmd_uart_ctx *hc_uart = backend->ctx;
hc_uart->state = UART_HOST_CMD_STATE_DISABLED;
if (!device_is_ready(hc_uart->uart_dev)) {
return -ENODEV;
}
/* UART backend needs rx and tx buffers provided by the handler */
if (!rx_ctx->buf || !tx->buf) {
return -EIO;
}
hc_uart->rx_ctx = rx_ctx;
hc_uart->tx_buf = tx;
k_work_init_delayable(&hc_uart->timeout_work, rx_timeout);
uart_callback_set(hc_uart->uart_dev, uart_callback, hc_uart);
ret = uart_rx_enable(hc_uart->uart_dev, hc_uart->rx_ctx->buf, hc_uart->rx_buf_size,
UART_OVERRUN_TIMEOUT_US);
hc_uart->state = UART_HOST_CMD_READY_TO_RX;
return ret;
}
static int ec_host_cmd_uart_send(const struct ec_host_cmd_backend *backend)
{
struct ec_host_cmd_uart_ctx *hc_uart = backend->ctx;
int ret;
if (hc_uart->state != UART_HOST_CMD_PROCESSING) {
LOG_ERR("UART HOST CMD ERROR: unexpected state while sending");
}
/* The state is changed to UART_HOST_CMD_READY_TO_RX in the UART_TX_DONE event */
hc_uart->state = UART_HOST_CMD_SENDING;
/* The rx buffer is no longer in use by command handler.
* Enable receiving to be ready to get a new command right after sending the response.
*/
uart_rx_enable(hc_uart->uart_dev, hc_uart->rx_ctx->buf, hc_uart->rx_buf_size,
UART_OVERRUN_TIMEOUT_US);
/* uart_tx is non-blocking asynchronous function.
* The state is changed to UART_HOST_CMD_READY_TO_RX in the UART_TX_DONE event.
*/
ret = uart_tx(hc_uart->uart_dev, hc_uart->tx_buf->buf, hc_uart->tx_buf->len,
SYS_FOREVER_US);
/* If sending fails, reset the state */
if (ret) {
hc_uart->state = UART_HOST_CMD_READY_TO_RX;
LOG_ERR("UART HOST CMD ERROR: sending failed");
}
return ret;
}
static const struct ec_host_cmd_backend_api ec_host_cmd_api = {
.init = ec_host_cmd_uart_init,
.send = ec_host_cmd_uart_send,
};
EC_HOST_CMD_UART_DEFINE(ec_host_cmd_uart);
struct ec_host_cmd_backend *ec_host_cmd_backend_get_uart(const struct device *dev)
{
struct ec_host_cmd_uart_ctx *hc_uart = ec_host_cmd_uart.ctx;
hc_uart->uart_dev = dev;
return &ec_host_cmd_uart;
}
#if DT_NODE_EXISTS(DT_CHOSEN(zephyr_host_cmd_uart_backend)) && \
defined(CONFIG_EC_HOST_CMD_INITIALIZE_AT_BOOT)
static int host_cmd_init(void)
{
const struct device *const dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_host_cmd_uart_backend));
ec_host_cmd_init(ec_host_cmd_backend_get_uart(dev));
return 0;
}
SYS_INIT(host_cmd_init, POST_KERNEL, CONFIG_EC_HOST_CMD_INIT_PRIORITY);
#endif