208 lines
5.5 KiB
C
208 lines
5.5 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 */
|
|
if (k_sem_take(rx.handler_owns, K_FOREVER) < 0) {
|
|
/* This code path should never occur due to the nature of
|
|
* k_sem_take with K_FOREVER
|
|
*/
|
|
send_error_response(ec_host_cmd_dev,
|
|
EC_HOST_CMD_ERROR);
|
|
}
|
|
/* 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);
|