| /* |
| * Copyright (c) 2020 Google LLC. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/ec_host_cmd_periph.h> |
| #include <zephyr/mgmt/ec_host_cmd.h> |
| #include <zephyr/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 interface */ |
| 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; |
| |
| 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); |