forked from zephyrproject-rtos/zephyr
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
73480ed
commit 4ef4788
Showing
13 changed files
with
783 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
315 changes: 315 additions & 0 deletions
315
subsys/mgmt/ec_host_cmd/backends/ec_host_cmd_backend_uart.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}) |
Oops, something went wrong.