zephyr/subsys/mgmt/ec_host_cmd/ec_host_cmd_handler.c
Jett Rink 1972f0b7f4 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>
2020-09-04 14:50:45 -04:00

202 lines
5.4 KiB
C

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