Skip to content

Commit

Permalink
ec_host_cmd: add UART backend
Browse files Browse the repository at this point in the history
Add a new backend for Host Commands that uses UART. The backend bases
asynchronous UART API.

The UART backend is mainly used by FPMCU.

Signed-off-by: Dawid Niedzwiecki <[email protected]>
  • Loading branch information
niedzwiecki-dawid authored and carlescufi committed Apr 7, 2023
1 parent 73480ed commit 4ef4788
Show file tree
Hide file tree
Showing 13 changed files with 783 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/services/device_mgmt/ec_host_cmd.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ The supported backend and peripheral drivers:
* SHI - ITE and NPCX
* eSPI - any eSPI slave driver that support :kconfig:option:`CONFIG_ESPI_PERIPHERAL_EC_HOST_CMD` and
:kconfig:option:`CONFIG_ESPI_PERIPHERAL_CUSTOM_OPCODE`
* UART - any UART driver that supports the asynchronous API

Initialization
**************
Expand Down
12 changes: 12 additions & 0 deletions include/zephyr/mgmt/ec_host_cmd/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ struct ec_host_cmd_backend *ec_host_cmd_backend_get_shi_npcx(void);
*/
struct ec_host_cmd_backend *ec_host_cmd_backend_get_shi_ite(void);

/**
* @brief Get the UART Host Command backend pointer
*
* Get the UART pointer backend and pass a pointer to UART device instance that will be used for
* the Host Command communication.
*
* @param dev Pointer to UART device instance.
*
* @retval The UART backend pointer.
*/
struct ec_host_cmd_backend *ec_host_cmd_backend_get_uart(const struct device *dev);

/**
* @}
*/
Expand Down
2 changes: 2 additions & 0 deletions subsys/mgmt/ec_host_cmd/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ config EC_HOST_CMD_HANDLER_TX_BUFFER
int "Buffer size in bytes for TX buffer shared by all EC host commands"
default 0 if EC_HOST_CMD_BACKEND_ESPI
default 0 if EC_HOST_CMD_BACKEND_SHI
default 256 if EC_HOST_CMD_BACKEND_UART
default 256
help
Buffer size in bytes for TX buffer defined by the host command handler.
Expand All @@ -36,6 +37,7 @@ config EC_HOST_CMD_HANDLER_RX_BUFFER
int "Buffer size in bytes for RX buffer shared by all EC host commands"
default 256 if EC_HOST_CMD_BACKEND_ESPI
default 0 if EC_HOST_CMD_BACKEND_SHI
default 544 if EC_HOST_CMD_BACKEND_UART
default 256
help
Buffer size in bytes for TX buffer defined by the host command handler.
Expand Down
4 changes: 4 additions & 0 deletions subsys/mgmt/ec_host_cmd/backends/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ zephyr_library_sources_ifdef(
zephyr_library_sources_ifdef(
CONFIG_EC_HOST_CMD_BACKEND_SHI_ITE
ec_host_cmd_backend_shi_ite.c)

zephyr_library_sources_ifdef(
CONFIG_EC_HOST_CMD_BACKEND_UART
ec_host_cmd_backend_uart.c)
7 changes: 7 additions & 0 deletions subsys/mgmt/ec_host_cmd/backends/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ config EC_HOST_CMD_BACKEND_SHI
Enable support for Embedded Controller host commands using
the Serial Host Interface.

config EC_HOST_CMD_BACKEND_UART
bool "Host commands support using UART"
depends on UART_ASYNC_API
help
Enable support for Embedded Controller host commands using
the UART.

if EC_HOST_CMD_BACKEND_SHI

choice EC_HOST_CMD_BACKEND_SHI_DRIVER
Expand Down
315 changes: 315 additions & 0 deletions subsys/mgmt/ec_host_cmd/backends/ec_host_cmd_backend_uart.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
/*
* 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, \
}; \
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_backend))
static int host_cmd_init(const struct device *arg)
{
ARG_UNUSED(arg);
const struct device *const dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_host_cmd_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
8 changes: 8 additions & 0 deletions tests/subsys/mgmt/ec_host_cmd/uart/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(integration)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
Loading

0 comments on commit 4ef4788

Please sign in to comment.