ec_host_cmd: add ec host command handler framework
Add a generic host command handler framework that allows users to declare new host command handlers with the HOST_COMMAND_HANDLER macro at build time. The framework will handle incoming messages from the host command peripheral device and forwards the incoming data to the appropriate host command handler, which is looked up by id. The framework will also send the response from the handler back to the host command peripheral device. The device handles sending the data on the physical bus. This type of host command communication is typically done on an embedded controller for a notebook or computer. The host would be the main application processor (aka AP, CPU, SoC). Signed-off-by: Jett Rink <jettrink@google.com>
This commit is contained in:
parent
703fe86220
commit
1972f0b7f4
|
@ -491,6 +491,7 @@
|
|||
/subsys/fs/nvs/ @Laczen
|
||||
/subsys/logging/ @nordic-krch
|
||||
/subsys/logging/log_backend_net.c @nordic-krch @jukkar
|
||||
/subsys/mgmt/ec_host_cmd/ @jettr
|
||||
/subsys/mgmt/mcumgr/ @carlescufi @nvlsianpu
|
||||
/subsys/mgmt/hawkbit/ @Navin-Sankar
|
||||
/subsys/mgmt/mcumgr/smp_udp.c @aunsbjerg
|
||||
|
|
197
include/ec_host_cmd.h
Normal file
197
include/ec_host_cmd.h
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Google LLC
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_EC_HOST_CMD_H_
|
||||
#define ZEPHYR_INCLUDE_EC_HOST_CMD_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Arguments passed into every installed host command handler
|
||||
*/
|
||||
struct ec_host_cmd_handler_args {
|
||||
/** The incoming data that can be cast to the handlers request type. */
|
||||
const void *const input_buf;
|
||||
/** The number of valid bytes that can be read from @a input_buf. */
|
||||
const uint16_t input_buf_size;
|
||||
/** The data written to this buffer will be send to the host. */
|
||||
void *const output_buf;
|
||||
/** [in/out] Upon entry, this is the maximum number of bytes that can
|
||||
* be written to the @a output_buf. Upon exit, this should be
|
||||
* the number of bytes of @a output_buf to send to the host.
|
||||
*/
|
||||
uint16_t output_buf_size;
|
||||
/** The version of the host command that is being requested. This will
|
||||
* be a value that has been static registered as valid for the handler.
|
||||
*/
|
||||
const uint8_t version;
|
||||
};
|
||||
|
||||
typedef enum ec_host_cmd_status (*ec_host_cmd_handler_cb)(
|
||||
struct ec_host_cmd_handler_args *args);
|
||||
/**
|
||||
* @brief Structure use for statically registering host command handlers
|
||||
*/
|
||||
struct ec_host_cmd_handler {
|
||||
/** Callback routine to process commands that match @a id. */
|
||||
ec_host_cmd_handler_cb handler;
|
||||
/** The numberical command id used as the lookup for commands. */
|
||||
uint16_t id;
|
||||
/** The bitfield of all versions that the @a handler supports, where
|
||||
* each bit value represents that the @a handler supports that version.
|
||||
* E.g. BIT(0) corresponse to version 0.
|
||||
*/
|
||||
uint16_t version_mask;
|
||||
/** The minimum @a input_buf_size enforced by the framework before
|
||||
* passing to the handler.
|
||||
*/
|
||||
uint16_t min_rqt_size;
|
||||
/** The minimum @a output_buf_size enforced by the framework before
|
||||
* passing to the handler.
|
||||
*/
|
||||
uint16_t min_rsp_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* @def EC_HOST_CMD_HANDLER
|
||||
* @brief Statically define and register a host command handler.
|
||||
*
|
||||
* Helper macro to statically define and register a host command handler that
|
||||
* has a compile-time-fixed sizes for its both request and response structures.
|
||||
*
|
||||
* @param _function Name of handler function.
|
||||
* @param _id Id of host command to handle request for.
|
||||
* @param _version_mask The bitfield of all versions that the @a _function
|
||||
* supports. E.g. BIT(0) corresponse to version 0.
|
||||
* @param _request_type The datatype of the request parameters for @a _function.
|
||||
* @param _response_type The datatype of the response parameters for
|
||||
* @a _function.
|
||||
*/
|
||||
#define EC_HOST_CMD_HANDLER(_function, _id, _version_mask, _request_type, \
|
||||
_response_type) \
|
||||
const Z_STRUCT_SECTION_ITERABLE(ec_host_cmd_handler, __cmd##_id) = { \
|
||||
.id = _id, \
|
||||
.handler = _function, \
|
||||
.version_mask = _version_mask, \
|
||||
.min_rqt_size = sizeof(_request_type), \
|
||||
.min_rsp_size = sizeof(_response_type), \
|
||||
}
|
||||
|
||||
/**
|
||||
* @def EC_HOST_CMD_HANDLER_UNBOUND
|
||||
* @brief Statically define and register a host command handler without sizes.
|
||||
*
|
||||
* Helper macro to statically define and register a host command handler whose
|
||||
* request or response structure size is not known as compile time.
|
||||
*
|
||||
* @param _function Name of handler function.
|
||||
* @param _id Id of host command to handle request for.
|
||||
* @param _version_mask The bitfield of all versions that the @a _function
|
||||
* supports. E.g. BIT(0) corresponse to version 0.
|
||||
*/
|
||||
#define EC_HOST_CMD_HANDLER_UNBOUND(_function, _id, _version_mask) \
|
||||
const Z_STRUCT_SECTION_ITERABLE(ec_host_cmd_handler, __cmd##_id) = { \
|
||||
.id = _id, \
|
||||
.handler = _function, \
|
||||
.version_mask = _version_mask, \
|
||||
.min_rqt_size = 0, \
|
||||
.min_rsp_size = 0, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Header for requests from host to embedded controller
|
||||
*
|
||||
* Represent the over-the-wire header in LE format for host command requests.
|
||||
* This represent version 3 of the host command header. The requests are always
|
||||
* sent from host to embedded controller.
|
||||
*/
|
||||
struct ec_host_cmd_request_header {
|
||||
/** Should be 3. The EC will return EC_HOST_CMD_INVALID_HEADER if it
|
||||
* receives a header with a version it doesn't know how to parse.
|
||||
*/
|
||||
uint8_t prtcl_ver;
|
||||
/** Checksum of response and data; sum of all bytes including checksum.
|
||||
* Should total to 0.
|
||||
*/
|
||||
uint8_t checksum;
|
||||
/** Id of command that is being sent. */
|
||||
uint16_t cmd_id;
|
||||
/** Version of the specific @a cmd_id being requested. Valid
|
||||
* versions start at 0.
|
||||
*/
|
||||
uint8_t cmd_ver;
|
||||
/** Unused byte in current protocol version; set to 0. */
|
||||
uint8_t reserved;
|
||||
/** Length of data which follows this header. */
|
||||
uint16_t data_len;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* @brief Header for responses from embedded controller to host
|
||||
*
|
||||
* Represent the over-the-wire header in LE format for host command responses.
|
||||
* This represent version 3 of the host command header. Responses are always
|
||||
* sent from embedded controller to host.
|
||||
*/
|
||||
struct ec_host_cmd_response_header {
|
||||
/** Should be 3. */
|
||||
uint8_t prtcl_ver;
|
||||
/** Checksum of response and data; sum of all bytes including checksum.
|
||||
* Should total to 0.
|
||||
*/
|
||||
uint8_t checksum;
|
||||
/** A @a ec_host_cmd_status response code for specific command. */
|
||||
uint16_t result;
|
||||
/** Length of data which follows this header. */
|
||||
uint16_t data_len;
|
||||
/** Unused bytes in current protocol version; set to 0. */
|
||||
uint16_t reserved;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Host command response codes (16-bit).
|
||||
*/
|
||||
enum ec_host_cmd_status {
|
||||
/** Host command was successful. */
|
||||
EC_HOST_CMD_SUCCESS = 0,
|
||||
/** The specified command id is not recognized or supported. */
|
||||
EC_HOST_CMD_INVALID_COMMAND = 1,
|
||||
/** Generic Error. */
|
||||
EC_HOST_CMD_ERROR = 2,
|
||||
/** One of more of the input request parameters is invalid. */
|
||||
EC_HOST_CMD_INVALID_PARAM = 3,
|
||||
/** Host command is not permitted. */
|
||||
EC_HOST_CMD_ACCESS_DENIED = 4,
|
||||
/** Response was invalid (e.g. not version 3 of header). */
|
||||
EC_HOST_CMD_INVALID_RESPONSE = 5,
|
||||
/** Host command id version unsupported. */
|
||||
EC_HOST_CMD_INVALID_VERSION = 6,
|
||||
/** Checksum did not match */
|
||||
EC_HOST_CMD_INVALID_CHECKSUM = 7,
|
||||
/** A host command is currently being processed. */
|
||||
EC_HOST_CMD_IN_PROGRESS = 8,
|
||||
/** Requested information is currently unavailable. */
|
||||
EC_HOST_CMD_UNAVAILABLE = 9,
|
||||
/** Timeout during processing. */
|
||||
EC_HOST_CMD_TIMEOUT = 10,
|
||||
/** Data or table overflow. */
|
||||
EC_HOST_CMD_OVERFLOW = 11,
|
||||
/** Header is invalid or unsupported (e.g. not version 3 of header). */
|
||||
EC_HOST_CMD_INVALID_HEADER = 12,
|
||||
/** Did not receive all expected request data. */
|
||||
EC_HOST_CMD_REQUEST_TRUNCATED = 13,
|
||||
/** Response was too big to send within one response packet. */
|
||||
EC_HOST_CMD_RESPONSE_TOO_BIG = 14,
|
||||
/** Error on underlying communication bus. */
|
||||
EC_HOST_CMD_BUS_ERROR = 15,
|
||||
/** System busy. Should retry later. */
|
||||
EC_HOST_CMD_BUSY = 16,
|
||||
|
||||
EC_HOST_CMD_MAX = UINT16_MAX /* Force enum to be 16 bits */
|
||||
} __packed;
|
||||
BUILD_ASSERT(sizeof(enum ec_host_cmd_status) == sizeof(uint16_t));
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_EC_HOST_CMD_H_ */
|
|
@ -102,6 +102,10 @@
|
|||
|
||||
Z_ITERABLE_SECTION_ROM(bt_gatt_service_static, 4)
|
||||
|
||||
#if defined(CONFIG_EC_HOST_CMD)
|
||||
Z_ITERABLE_SECTION_ROM(ec_host_cmd_handler, 4)
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SETTINGS)
|
||||
Z_ITERABLE_SECTION_ROM(settings_handler_static, 4)
|
||||
#endif
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_EC_HOST_CMD ec_host_cmd)
|
||||
add_subdirectory_ifdef(CONFIG_MCUMGR mcumgr)
|
||||
add_subdirectory_ifdef(CONFIG_HAWKBIT hawkbit)
|
||||
add_subdirectory_ifdef(CONFIG_UPDATEHUB updatehub)
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
menu "Device Management"
|
||||
|
||||
source "subsys/mgmt/ec_host_cmd/Kconfig"
|
||||
|
||||
source "subsys/mgmt/mcumgr/Kconfig"
|
||||
|
||||
source "subsys/mgmt/hawkbit/Kconfig"
|
||||
|
|
4
subsys/mgmt/ec_host_cmd/CMakeLists.txt
Normal file
4
subsys/mgmt/ec_host_cmd/CMakeLists.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
zephyr_library_sources(ec_host_cmd_handler.c)
|
28
subsys/mgmt/ec_host_cmd/Kconfig
Normal file
28
subsys/mgmt/ec_host_cmd/Kconfig
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Host command handler functionality
|
||||
|
||||
# Copyright (c) 2020 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menu "Host command handler subsystem"
|
||||
|
||||
config EC_HOST_CMD
|
||||
bool "Support Embedded Controller host command handler subsystem"
|
||||
help
|
||||
Enable host command processing for embedded controllers on notebook
|
||||
computers. Enabling this option requires specifying a chosen
|
||||
zephyr,ec-host-interface device as the ec host command peripheral that
|
||||
receive incoming host command requests to process.
|
||||
|
||||
if EC_HOST_CMD
|
||||
|
||||
config EC_HOST_CMD_HANDLER_STACK_SIZE
|
||||
int "Stack size for the EC host command handler thread"
|
||||
default 512
|
||||
|
||||
config EC_HOST_CMD_HANDLER_TX_BUFFER
|
||||
int "Buffer size in bytes for TX buffer shared by all EC host commands"
|
||||
default 256
|
||||
|
||||
endif # EC_HOST_CMD
|
||||
|
||||
endmenu
|
201
subsys/mgmt/ec_host_cmd/ec_host_cmd_handler.c
Normal file
201
subsys/mgmt/ec_host_cmd/ec_host_cmd_handler.c
Normal file
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Google LLC.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <drivers/ec_host_cmd_periph.h>
|
||||
#include <ec_host_cmd.h>
|
||||
#include <devicetree.h>
|
||||
#include <string.h>
|
||||
|
||||
#if !DT_HAS_CHOSEN(zephyr_ec_host_interface)
|
||||
#error Must chose zephyr,ec-host-interface in device tree
|
||||
#endif
|
||||
|
||||
#define DT_HOST_CMD_DEV DT_CHOSEN(zephyr_ec_host_interface)
|
||||
|
||||
#define RX_HEADER_SIZE (sizeof(struct ec_host_cmd_request_header))
|
||||
#define TX_HEADER_SIZE (sizeof(struct ec_host_cmd_response_header))
|
||||
|
||||
/** Used by host command handlers for their response before going over wire */
|
||||
uint8_t tx_buffer[CONFIG_EC_HOST_CMD_HANDLER_TX_BUFFER];
|
||||
|
||||
static uint8_t cal_checksum(const uint8_t *const buffer, const uint16_t size)
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
checksum += buffer[i];
|
||||
}
|
||||
return (uint8_t)(-checksum);
|
||||
}
|
||||
|
||||
static void send_error_response(const struct device *const ec_host_cmd_dev,
|
||||
const enum ec_host_cmd_status error)
|
||||
{
|
||||
struct ec_host_cmd_response_header *const tx_header = (void *)tx_buffer;
|
||||
|
||||
tx_header->prtcl_ver = 3;
|
||||
tx_header->result = error;
|
||||
tx_header->data_len = 0;
|
||||
tx_header->reserved = 0;
|
||||
tx_header->checksum = 0;
|
||||
tx_header->checksum = cal_checksum(tx_buffer, TX_HEADER_SIZE);
|
||||
|
||||
const struct ec_host_cmd_periph_tx_buf tx = {
|
||||
.buf = tx_buffer,
|
||||
.len = TX_HEADER_SIZE,
|
||||
};
|
||||
ec_host_cmd_periph_send(ec_host_cmd_dev, &tx);
|
||||
}
|
||||
|
||||
static void handle_host_cmds_entry(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
ARG_UNUSED(arg1);
|
||||
ARG_UNUSED(arg2);
|
||||
ARG_UNUSED(arg3);
|
||||
const struct device *ec_host_cmd_dev;
|
||||
struct ec_host_cmd_periph_rx_ctx rx;
|
||||
|
||||
ec_host_cmd_dev = device_get_binding(DT_LABEL(DT_HOST_CMD_DEV));
|
||||
|
||||
ec_host_cmd_periph_init(ec_host_cmd_dev, &rx);
|
||||
|
||||
while (1) {
|
||||
/* We have finished reading from RX buffer, so allow another
|
||||
* incoming msg.
|
||||
*/
|
||||
k_sem_give(rx.dev_owns);
|
||||
|
||||
/* Wait until and RX messages is received on host interace */
|
||||
k_sem_take(rx.handler_owns, K_FOREVER);
|
||||
/* rx buf and len now have valid incoming data */
|
||||
|
||||
if (*rx.len < RX_HEADER_SIZE) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_REQUEST_TRUNCATED);
|
||||
continue;
|
||||
}
|
||||
|
||||
const struct ec_host_cmd_request_header *const rx_header =
|
||||
(void *)rx.buf;
|
||||
|
||||
/* Only support version 3 */
|
||||
if (rx_header->prtcl_ver != 3) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_INVALID_HEADER);
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint16_t rx_valid_data_size =
|
||||
rx_header->data_len + RX_HEADER_SIZE;
|
||||
/*
|
||||
* Ensure we received at least as much data as is expected.
|
||||
* It is okay to receive more since some hardware interfaces
|
||||
* add on extra padding bytes at the end.
|
||||
*/
|
||||
if (*rx.len < rx_valid_data_size) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_REQUEST_TRUNCATED);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Validate checksum */
|
||||
if (cal_checksum(rx.buf, rx_valid_data_size) != 0) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_INVALID_CHECKSUM);
|
||||
continue;
|
||||
}
|
||||
|
||||
const struct ec_host_cmd_handler *found_handler = NULL;
|
||||
|
||||
Z_STRUCT_SECTION_FOREACH(ec_host_cmd_handler, handler)
|
||||
{
|
||||
if (handler->id == rx_header->cmd_id) {
|
||||
found_handler = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* No handler in this image for requested command */
|
||||
if (found_handler == NULL) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_INVALID_COMMAND);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure that RX/TX buffers are cleared between each host
|
||||
* command to ensure subsequent host command handlers cannot
|
||||
* read data from previous host command runs.
|
||||
*/
|
||||
memset(&rx.buf[rx_valid_data_size], 0,
|
||||
*rx.len - rx_valid_data_size);
|
||||
memset(tx_buffer, 0, sizeof(tx_buffer));
|
||||
|
||||
struct ec_host_cmd_handler_args args = {
|
||||
.input_buf = rx.buf + RX_HEADER_SIZE,
|
||||
.input_buf_size = rx_header->data_len,
|
||||
.output_buf = tx_buffer + TX_HEADER_SIZE,
|
||||
.output_buf_size = sizeof(tx_buffer) - TX_HEADER_SIZE,
|
||||
.version = rx_header->cmd_ver,
|
||||
};
|
||||
|
||||
if (found_handler->min_rqt_size > args.input_buf_size) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_REQUEST_TRUNCATED);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found_handler->min_rsp_size > args.output_buf_size) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_INVALID_RESPONSE);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args.version > sizeof(found_handler->version_mask) ||
|
||||
!(found_handler->version_mask & BIT(args.version))) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_INVALID_VERSION);
|
||||
continue;
|
||||
}
|
||||
|
||||
const enum ec_host_cmd_status handler_rv =
|
||||
found_handler->handler(&args);
|
||||
|
||||
if (handler_rv != EC_HOST_CMD_SUCCESS) {
|
||||
send_error_response(ec_host_cmd_dev, handler_rv);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct ec_host_cmd_response_header *const tx_header =
|
||||
(void *)tx_buffer;
|
||||
|
||||
tx_header->prtcl_ver = 3;
|
||||
tx_header->result = EC_HOST_CMD_SUCCESS;
|
||||
tx_header->data_len = args.output_buf_size;
|
||||
|
||||
const uint16_t tx_valid_data_size =
|
||||
tx_header->data_len + TX_HEADER_SIZE;
|
||||
if (tx_valid_data_size > sizeof(tx_buffer)) {
|
||||
send_error_response(ec_host_cmd_dev,
|
||||
EC_HOST_CMD_INVALID_RESPONSE);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Calculate checksum */
|
||||
tx_header->checksum =
|
||||
cal_checksum(tx_buffer, tx_valid_data_size);
|
||||
|
||||
const struct ec_host_cmd_periph_tx_buf tx = {
|
||||
.buf = tx_buffer,
|
||||
.len = tx_valid_data_size,
|
||||
};
|
||||
ec_host_cmd_periph_send(ec_host_cmd_dev, &tx);
|
||||
}
|
||||
}
|
||||
|
||||
K_THREAD_DEFINE(ec_host_cmd_handler_tid, CONFIG_EC_HOST_CMD_HANDLER_STACK_SIZE,
|
||||
handle_host_cmds_entry, NULL, NULL, NULL,
|
||||
K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0);
|
8
tests/subsys/mgmt/ec_host_cmd/CMakeLists.txt
Normal file
8
tests/subsys/mgmt/ec_host_cmd/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.13.1)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(integration)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
4
tests/subsys/mgmt/ec_host_cmd/prj.conf
Normal file
4
tests/subsys/mgmt/ec_host_cmd/prj.conf
Normal file
|
@ -0,0 +1,4 @@
|
|||
CONFIG_EC_HOST_CMD=y
|
||||
CONFIG_EC_HOST_CMD_PERIPH=y
|
||||
CONFIG_EC_HOST_CMD_SIMULATOR=y
|
||||
CONFIG_ZTEST=y
|
526
tests/subsys/mgmt/ec_host_cmd/src/main.c
Normal file
526
tests/subsys/mgmt/ec_host_cmd/src/main.c
Normal file
|
@ -0,0 +1,526 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <drivers/ec_host_cmd_periph/ec_host_cmd_simulator.h>
|
||||
#include <ec_host_cmd.h>
|
||||
#include <ztest.h>
|
||||
|
||||
/* Variables used to record what is "sent" to host for verification. */
|
||||
K_SEM_DEFINE(send_called, 0, 1);
|
||||
struct ec_host_cmd_periph_tx_buf sent;
|
||||
|
||||
static int host_send(const struct device *const dev,
|
||||
const struct ec_host_cmd_periph_tx_buf *const buf)
|
||||
{
|
||||
sent.len = buf->len;
|
||||
sent.buf = buf->buf;
|
||||
k_sem_give(&send_called);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct ec_params_add - Parameters to the add command.
|
||||
* @in_data: Pass anything here.
|
||||
*/
|
||||
struct ec_params_add {
|
||||
uint32_t in_data;
|
||||
} __packed;
|
||||
|
||||
struct ec_params_unbounded {
|
||||
uint32_t bytes_to_write;
|
||||
} __packed;
|
||||
|
||||
/**
|
||||
* struct ec_response_add - Response to the add command.
|
||||
* @out_data: Output will be in_data + 0x01020304.
|
||||
*/
|
||||
struct ec_response_add {
|
||||
uint32_t out_data;
|
||||
} __packed;
|
||||
|
||||
struct ec_response_too_big {
|
||||
uint8_t out_data[512];
|
||||
} __packed;
|
||||
|
||||
/* Buffer used to simulate incoming data from host to EC. */
|
||||
static uint8_t host_to_dut_buffer[256];
|
||||
struct rx_structure {
|
||||
struct ec_host_cmd_request_header header;
|
||||
union {
|
||||
struct ec_params_add add;
|
||||
struct ec_params_unbounded unbounded;
|
||||
uint8_t raw[0];
|
||||
};
|
||||
} __packed * const host_to_dut = (void *)&host_to_dut_buffer;
|
||||
|
||||
/* Buffer used to verify expected outgoing data from EC to host. */
|
||||
static uint8_t expected_dut_to_host_buffer[256];
|
||||
struct tx_structure {
|
||||
struct ec_host_cmd_response_header header;
|
||||
union {
|
||||
struct ec_response_add add;
|
||||
struct ec_response_too_big too_big;
|
||||
uint8_t raw[0];
|
||||
};
|
||||
} __packed * const expected_dut_to_host = (void *)&expected_dut_to_host_buffer;
|
||||
|
||||
static void update_host_to_dut_checksum(void)
|
||||
{
|
||||
host_to_dut->header.checksum = 0;
|
||||
|
||||
uint8_t checksum = 0;
|
||||
|
||||
for (size_t i = 0;
|
||||
i < sizeof(host_to_dut->header) + host_to_dut->header.data_len;
|
||||
++i) {
|
||||
checksum += host_to_dut_buffer[i];
|
||||
}
|
||||
host_to_dut->header.checksum = (uint8_t)(-checksum);
|
||||
}
|
||||
|
||||
static void update_dut_to_host_checksum(void)
|
||||
{
|
||||
const uint16_t buf_size = sizeof(expected_dut_to_host->header) +
|
||||
expected_dut_to_host->header.data_len;
|
||||
|
||||
expected_dut_to_host->header.checksum = 0;
|
||||
|
||||
uint8_t checksum = 0;
|
||||
|
||||
for (size_t i = 0; i < buf_size; ++i) {
|
||||
checksum += expected_dut_to_host_buffer[i];
|
||||
}
|
||||
|
||||
expected_dut_to_host->header.checksum = (uint8_t)(-checksum);
|
||||
}
|
||||
|
||||
static void simulate_rx_data(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
update_host_to_dut_checksum();
|
||||
/*
|
||||
* Always send entire buffer and let host command framework read what it
|
||||
* needs.
|
||||
*/
|
||||
rv = ec_host_cmd_periph_sim_data_received(host_to_dut_buffer,
|
||||
sizeof(host_to_dut_buffer));
|
||||
zassert_equal(rv, 0, "Could not send data %d", rv);
|
||||
|
||||
/* Ensure send was called so we can verify outputs */
|
||||
rv = k_sem_take(&send_called, K_SECONDS(1));
|
||||
zassert_equal(rv, 0, "Send was not called");
|
||||
}
|
||||
|
||||
static uint16_t expected_tx_size(void)
|
||||
{
|
||||
return sizeof(expected_dut_to_host->header) +
|
||||
expected_dut_to_host->header.data_len;
|
||||
}
|
||||
|
||||
static void verify_tx_data(void)
|
||||
{
|
||||
update_dut_to_host_checksum();
|
||||
|
||||
zassert_equal(sent.len, expected_tx_size(), "Sent bytes did not match");
|
||||
zassert_mem_equal(sent.buf, expected_dut_to_host, expected_tx_size(),
|
||||
"Sent buffer did not match");
|
||||
}
|
||||
|
||||
static void verify_tx_error(enum ec_host_cmd_status error)
|
||||
{
|
||||
expected_dut_to_host->header.prtcl_ver = 3;
|
||||
expected_dut_to_host->header.result = error;
|
||||
expected_dut_to_host->header.data_len = 0;
|
||||
expected_dut_to_host->header.reserved = 0;
|
||||
update_dut_to_host_checksum();
|
||||
|
||||
zassert_equal(sent.len, expected_tx_size(), "Sent bytes did not match");
|
||||
zassert_mem_equal(sent.buf, expected_dut_to_host, expected_tx_size(),
|
||||
"Sent buffer did not match");
|
||||
}
|
||||
|
||||
#define EC_CMD_HELLO 0x0001
|
||||
static enum ec_host_cmd_status
|
||||
ec_host_cmd_add(struct ec_host_cmd_handler_args *args)
|
||||
{
|
||||
const struct ec_params_add *const request = args->input_buf;
|
||||
struct ec_response_add *const response = args->output_buf;
|
||||
|
||||
if (args->version == 0) {
|
||||
response->out_data = request->in_data + 0x01020304;
|
||||
} else if (args->version == 1) {
|
||||
response->out_data = request->in_data + 0x02040608;
|
||||
} else if (args->version == 2) {
|
||||
return EC_HOST_CMD_OVERFLOW;
|
||||
} else {
|
||||
zassert_unreachable("Should not get version %d", args->version);
|
||||
}
|
||||
|
||||
args->output_buf_size = sizeof(*response);
|
||||
return EC_HOST_CMD_SUCCESS;
|
||||
}
|
||||
EC_HOST_CMD_HANDLER(ec_host_cmd_add, EC_CMD_HELLO, BIT(0) | BIT(1) | BIT(2),
|
||||
struct ec_params_add, struct ec_response_add);
|
||||
|
||||
static void test_add(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 0;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
expected_dut_to_host->header.prtcl_ver = 3;
|
||||
expected_dut_to_host->header.result = 0;
|
||||
expected_dut_to_host->header.reserved = 0;
|
||||
expected_dut_to_host->header.data_len =
|
||||
sizeof(expected_dut_to_host->add);
|
||||
expected_dut_to_host->add.out_data = 0x11223344;
|
||||
|
||||
verify_tx_data();
|
||||
}
|
||||
|
||||
static void test_add_version_2(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 1;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
expected_dut_to_host->header.prtcl_ver = 3;
|
||||
expected_dut_to_host->header.result = 0;
|
||||
expected_dut_to_host->header.reserved = 0;
|
||||
expected_dut_to_host->header.data_len =
|
||||
sizeof(expected_dut_to_host->add);
|
||||
expected_dut_to_host->add.out_data = 0x12243648;
|
||||
|
||||
verify_tx_data();
|
||||
}
|
||||
|
||||
static void test_add_invalid_version(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 3;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_VERSION);
|
||||
}
|
||||
|
||||
static void test_add_invalid_version_big(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 128;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_VERSION);
|
||||
}
|
||||
|
||||
static void test_add_invalid_prtcl_ver_2(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 2;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_HEADER);
|
||||
}
|
||||
|
||||
static void test_add_invalid_prtcl_ver_4(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 4;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_HEADER);
|
||||
}
|
||||
|
||||
static void test_add_invalid_rx_checksum(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
/* Set an invalid checksum */
|
||||
host_to_dut->header.checksum = 42;
|
||||
|
||||
/* Always send entire buffer and let host command framework read what it
|
||||
* needs.
|
||||
*/
|
||||
rv = ec_host_cmd_periph_sim_data_received(host_to_dut_buffer,
|
||||
sizeof(host_to_dut_buffer));
|
||||
zassert_equal(rv, 0, "Could not send data %d", rv);
|
||||
|
||||
/* Ensure Send was called so we can verify outputs */
|
||||
rv = k_sem_take(&send_called, K_SECONDS(1));
|
||||
zassert_equal(rv, 0, "Send was not called");
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_CHECKSUM);
|
||||
}
|
||||
|
||||
static void test_add_rx_size_too_small_for_header(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
rv = ec_host_cmd_periph_sim_data_received(host_to_dut_buffer, 4);
|
||||
zassert_equal(rv, 0, "Could not send data %d", rv);
|
||||
|
||||
/* Ensure Send was called so we can verify outputs */
|
||||
rv = k_sem_take(&send_called, K_SECONDS(1));
|
||||
zassert_equal(rv, 0, "Send was not called");
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_REQUEST_TRUNCATED);
|
||||
}
|
||||
|
||||
static void test_add_rx_size_too_small(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_HELLO;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->add);
|
||||
host_to_dut->add.in_data = 0x10203040;
|
||||
|
||||
rv = ec_host_cmd_periph_sim_data_received(
|
||||
host_to_dut_buffer,
|
||||
sizeof(host_to_dut->header) + host_to_dut->header.data_len - 1);
|
||||
zassert_equal(rv, 0, "Could not send data %d", rv);
|
||||
|
||||
/* Ensure Send was called so we can verify outputs */
|
||||
rv = k_sem_take(&send_called, K_SECONDS(1));
|
||||
zassert_equal(rv, 0, "Send was not called");
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_REQUEST_TRUNCATED);
|
||||
}
|
||||
|
||||
static void test_unknown_command(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = 1234;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = 0;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_COMMAND);
|
||||
}
|
||||
|
||||
#define EC_CMD_UNBOUNDED 0x0002
|
||||
static enum ec_host_cmd_status
|
||||
ec_host_cmd_unbounded(struct ec_host_cmd_handler_args *args)
|
||||
{
|
||||
const struct ec_params_unbounded *const request = args->input_buf;
|
||||
const uint8_t *in_buffer = args->input_buf;
|
||||
uint8_t *out_buffer = args->output_buf;
|
||||
|
||||
/* Version 1 just says we used the space without writing */
|
||||
if (args->version == 1) {
|
||||
args->output_buf_size = request->bytes_to_write;
|
||||
return EC_HOST_CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/* Version 2 adds extra asserts */
|
||||
if (args->version == 2) {
|
||||
zassert_equal(in_buffer[4], 0, "Ensure input data is clear");
|
||||
zassert_equal(out_buffer[0], 0, "Ensure output is clear");
|
||||
zassert_equal(out_buffer[1], 0, "Ensure output is clear");
|
||||
zassert_equal(out_buffer[2], 0, "Ensure output is clear");
|
||||
zassert_equal(out_buffer[3], 0, "Ensure output is clear");
|
||||
}
|
||||
|
||||
/* Version 0 (and 2) write request bytes if it can fit */
|
||||
if (request->bytes_to_write > args->output_buf_size) {
|
||||
return EC_HOST_CMD_OVERFLOW;
|
||||
}
|
||||
|
||||
for (int i = 0; i < request->bytes_to_write; ++i) {
|
||||
zassert_equal(out_buffer[i], 0, "Ensure every TX byte is 0");
|
||||
out_buffer[i] = i;
|
||||
}
|
||||
|
||||
args->output_buf_size = request->bytes_to_write;
|
||||
return EC_HOST_CMD_SUCCESS;
|
||||
}
|
||||
EC_HOST_CMD_HANDLER_UNBOUND(ec_host_cmd_unbounded, EC_CMD_UNBOUNDED,
|
||||
BIT(0) | BIT(1) | BIT(2));
|
||||
|
||||
static void test_unbounded_handler_error_return(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_UNBOUNDED;
|
||||
host_to_dut->header.cmd_ver = 0;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->unbounded);
|
||||
host_to_dut->unbounded.bytes_to_write = INT16_MAX;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_OVERFLOW);
|
||||
}
|
||||
|
||||
static void test_unbounded_handler_response_too_big(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_UNBOUNDED;
|
||||
host_to_dut->header.cmd_ver = 1;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->unbounded);
|
||||
host_to_dut->unbounded.bytes_to_write = INT16_MAX;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_RESPONSE);
|
||||
}
|
||||
|
||||
static void test_rx_buffer_cleared_foreach_hostcommand(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_UNBOUNDED;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->unbounded);
|
||||
host_to_dut->unbounded.bytes_to_write = 5;
|
||||
|
||||
/* Write data after the entire request message. The host command handler
|
||||
* always assert that this data is cleared upon receipt.
|
||||
*/
|
||||
host_to_dut->raw[4] = 42;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
expected_dut_to_host->header.prtcl_ver = 3;
|
||||
expected_dut_to_host->header.result = 0;
|
||||
expected_dut_to_host->header.reserved = 0;
|
||||
expected_dut_to_host->header.data_len = 5;
|
||||
expected_dut_to_host->raw[0] = 0;
|
||||
expected_dut_to_host->raw[1] = 1;
|
||||
expected_dut_to_host->raw[2] = 2;
|
||||
expected_dut_to_host->raw[3] = 3;
|
||||
expected_dut_to_host->raw[4] = 4;
|
||||
|
||||
verify_tx_data();
|
||||
}
|
||||
|
||||
static void test_tx_buffer_cleared_foreach_hostcommand(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_UNBOUNDED;
|
||||
host_to_dut->header.cmd_ver = 2;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(host_to_dut->unbounded);
|
||||
host_to_dut->unbounded.bytes_to_write = 5;
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
expected_dut_to_host->header.prtcl_ver = 3;
|
||||
expected_dut_to_host->header.result = 0;
|
||||
expected_dut_to_host->header.reserved = 0;
|
||||
expected_dut_to_host->header.data_len = 5;
|
||||
expected_dut_to_host->raw[0] = 0;
|
||||
expected_dut_to_host->raw[1] = 1;
|
||||
expected_dut_to_host->raw[2] = 2;
|
||||
expected_dut_to_host->raw[3] = 3;
|
||||
expected_dut_to_host->raw[4] = 4;
|
||||
|
||||
verify_tx_data();
|
||||
|
||||
/* Send second command with less bytes to write. Host command handler
|
||||
* asserts that the previous output data is zero.
|
||||
*/
|
||||
|
||||
host_to_dut->unbounded.bytes_to_write = 2;
|
||||
simulate_rx_data();
|
||||
|
||||
expected_dut_to_host->header.data_len = 2;
|
||||
verify_tx_data();
|
||||
}
|
||||
|
||||
#define EC_CMD_TOO_BIG 0x0003
|
||||
static enum ec_host_cmd_status
|
||||
ec_host_cmd_too_big(struct ec_host_cmd_handler_args *args)
|
||||
{
|
||||
return EC_HOST_CMD_SUCCESS;
|
||||
}
|
||||
EC_HOST_CMD_HANDLER(ec_host_cmd_too_big, EC_CMD_TOO_BIG, BIT(0), uint32_t,
|
||||
struct ec_response_too_big);
|
||||
|
||||
static void test_response_always_too_big(void)
|
||||
{
|
||||
host_to_dut->header.prtcl_ver = 3;
|
||||
host_to_dut->header.cmd_id = EC_CMD_TOO_BIG;
|
||||
host_to_dut->header.cmd_ver = 0;
|
||||
host_to_dut->header.reserved = 0;
|
||||
host_to_dut->header.data_len = sizeof(uint32_t);
|
||||
|
||||
simulate_rx_data();
|
||||
|
||||
verify_tx_error(EC_HOST_CMD_INVALID_RESPONSE);
|
||||
}
|
||||
|
||||
void test_main(void)
|
||||
{
|
||||
ec_host_cmd_periph_sim_install_send_cb(host_send);
|
||||
|
||||
ztest_test_suite(
|
||||
ec_host_cmd_tests, ztest_unit_test(test_add),
|
||||
ztest_unit_test(test_add_version_2),
|
||||
ztest_unit_test(test_add_invalid_prtcl_ver_2),
|
||||
ztest_unit_test(test_add_invalid_prtcl_ver_4),
|
||||
ztest_unit_test(test_add_invalid_version),
|
||||
ztest_unit_test(test_add_invalid_version_big),
|
||||
ztest_unit_test(test_add_invalid_rx_checksum),
|
||||
ztest_unit_test(test_add_rx_size_too_small_for_header),
|
||||
ztest_unit_test(test_add_rx_size_too_small),
|
||||
ztest_unit_test(test_unknown_command),
|
||||
ztest_unit_test(test_unbounded_handler_error_return),
|
||||
ztest_unit_test(test_unbounded_handler_response_too_big),
|
||||
ztest_unit_test(test_rx_buffer_cleared_foreach_hostcommand),
|
||||
ztest_unit_test(test_tx_buffer_cleared_foreach_hostcommand),
|
||||
ztest_unit_test(test_response_always_too_big));
|
||||
|
||||
ztest_run_test_suite(ec_host_cmd_tests);
|
||||
}
|
4
tests/subsys/mgmt/ec_host_cmd/testcase.yaml
Normal file
4
tests/subsys/mgmt/ec_host_cmd/testcase.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
tests:
|
||||
ec_host_cmd.core:
|
||||
platform_allow: native_posix
|
||||
tags: ec_host_cmd
|
Loading…
Reference in a new issue