/* * Copyright (c) 2020 Google LLC. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #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);