| /* |
| * Copyright (c) 2022 Google LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <string.h> |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/espi.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/mgmt/ec_host_cmd/backend.h> |
| #include <zephyr/mgmt/ec_host_cmd/ec_host_cmd.h> |
| |
| LOG_MODULE_REGISTER(host_cmd_espi, CONFIG_EC_HC_LOG_LEVEL); |
| |
| #define RX_HEADER_SIZE (sizeof(struct ec_host_cmd_request_header)) |
| |
| /* eSPI Host Command state */ |
| enum ec_host_cmd_espi_state { |
| /* Interface is disabled */ |
| ESPI_STATE_DISABLED, |
| /* Ready to receive next request */ |
| ESPI_STATE_READY_TO_RECV, |
| /* Processing request */ |
| ESPI_STATE_PROCESSING, |
| /* Processing request */ |
| ESPI_STATE_SENDING, |
| ESPI_STATE_COUNT, |
| }; |
| |
| struct ec_host_cmd_espi_ctx { |
| /* eSPI device instance */ |
| const struct device *espi_dev; |
| /* Context for read operation */ |
| struct ec_host_cmd_rx_ctx *rx_ctx; |
| /* Transmit buffer */ |
| struct ec_host_cmd_tx_buf *tx; |
| /* eSPI callback */ |
| struct espi_callback espi_cb; |
| /* eSPI Host Command state */ |
| enum ec_host_cmd_espi_state state; |
| }; |
| |
| #define EC_HOST_CMD_ESPI_DEFINE(_name) \ |
| static struct ec_host_cmd_espi_ctx _name##_hc_espi; \ |
| struct ec_host_cmd_backend _name = { \ |
| .api = &ec_host_cmd_api, \ |
| .ctx = (struct ec_host_cmd_espi_ctx *)&_name##_hc_espi, \ |
| } |
| |
| static void espi_handler(const struct device *dev, struct espi_callback *cb, |
| struct espi_event espi_evt) |
| { |
| struct ec_host_cmd_espi_ctx *hc_espi = |
| CONTAINER_OF(cb, struct ec_host_cmd_espi_ctx, espi_cb); |
| uint16_t event_type = (uint16_t)espi_evt.evt_details; |
| /* tx stores the shared memory buf pointer and size, so use it */ |
| const struct ec_host_cmd_request_header *rx_header = hc_espi->tx->buf; |
| const size_t shared_size = hc_espi->tx->len_max; |
| const uint16_t rx_valid_data_size = rx_header->data_len + RX_HEADER_SIZE; |
| |
| if (event_type != ESPI_PERIPHERAL_EC_HOST_CMD) { |
| return; |
| } |
| |
| /* Make sure we've received a Host Command in a good state not to override buffers for |
| * a Host Command that is currently being processed. There is a moment between sending |
| * a response and setting state to ESPI_STATE_READY_TO_RECV when we can receive a new |
| * host command, so accept the sending state as well. |
| */ |
| if (hc_espi->state != ESPI_STATE_READY_TO_RECV && hc_espi->state != ESPI_STATE_SENDING) { |
| LOG_ERR("Received HC in bad state"); |
| return; |
| } |
| |
| /* Only support version 3 and make sure the number of bytes to copy is not |
| * bigger than rx buf size or the shared memory size |
| */ |
| if (rx_header->prtcl_ver != 3 || |
| rx_valid_data_size > CONFIG_EC_HOST_CMD_HANDLER_RX_BUFFER_SIZE || |
| rx_valid_data_size > shared_size) { |
| memcpy(hc_espi->rx_ctx->buf, (void *)rx_header, RX_HEADER_SIZE); |
| hc_espi->rx_ctx->len = RX_HEADER_SIZE; |
| } else { |
| memcpy(hc_espi->rx_ctx->buf, (void *)rx_header, rx_valid_data_size); |
| hc_espi->rx_ctx->len = rx_valid_data_size; |
| } |
| |
| /* Even in case of errors, let the general handler send response */ |
| hc_espi->state = ESPI_STATE_PROCESSING; |
| ec_host_cmd_rx_notify(); |
| } |
| |
| static int ec_host_cmd_espi_init(const struct ec_host_cmd_backend *backend, |
| struct ec_host_cmd_rx_ctx *rx_ctx, struct ec_host_cmd_tx_buf *tx) |
| { |
| struct ec_host_cmd_espi_ctx *hc_espi = (struct ec_host_cmd_espi_ctx *)backend->ctx; |
| |
| hc_espi->state = ESPI_STATE_DISABLED; |
| |
| if (!device_is_ready(hc_espi->espi_dev)) { |
| return -ENODEV; |
| } |
| |
| hc_espi->rx_ctx = rx_ctx; |
| hc_espi->tx = tx; |
| |
| espi_init_callback(&hc_espi->espi_cb, espi_handler, ESPI_BUS_PERIPHERAL_NOTIFICATION); |
| espi_add_callback(hc_espi->espi_dev, &hc_espi->espi_cb); |
| /* Use shared memory as the tx buffer */ |
| espi_read_lpc_request(hc_espi->espi_dev, ECUSTOM_HOST_CMD_GET_PARAM_MEMORY, |
| (uint32_t *)&tx->buf); |
| espi_read_lpc_request(hc_espi->espi_dev, ECUSTOM_HOST_CMD_GET_PARAM_MEMORY_SIZE, |
| &tx->len_max); |
| |
| hc_espi->state = ESPI_STATE_READY_TO_RECV; |
| |
| return 0; |
| } |
| |
| static int ec_host_cmd_espi_send(const struct ec_host_cmd_backend *backend) |
| { |
| struct ec_host_cmd_espi_ctx *hc_espi = (struct ec_host_cmd_espi_ctx *)backend->ctx; |
| struct ec_host_cmd_response_header *resp_hdr = hc_espi->tx->buf; |
| uint32_t result = resp_hdr->result; |
| int ret; |
| |
| /* Ignore in-progress on eSPI since interface is synchronous anyway */ |
| if (result == EC_HOST_CMD_IN_PROGRESS) |
| return 0; |
| |
| hc_espi->state = ESPI_STATE_SENDING; |
| |
| /* Data to transfer are already in the tx buffer (shared memory) */ |
| ret = espi_write_lpc_request(hc_espi->espi_dev, ECUSTOM_HOST_CMD_SEND_RESULT, &result); |
| hc_espi->state = ESPI_STATE_READY_TO_RECV; |
| |
| return ret; |
| } |
| |
| static const struct ec_host_cmd_backend_api ec_host_cmd_api = { |
| .init = &ec_host_cmd_espi_init, |
| .send = &ec_host_cmd_espi_send, |
| }; |
| |
| EC_HOST_CMD_ESPI_DEFINE(ec_host_cmd_espi); |
| struct ec_host_cmd_backend *ec_host_cmd_backend_get_espi(const struct device *dev) |
| { |
| ((struct ec_host_cmd_espi_ctx *)(ec_host_cmd_espi.ctx))->espi_dev = dev; |
| return &ec_host_cmd_espi; |
| } |
| |
| #if DT_NODE_EXISTS(DT_CHOSEN(zephyr_host_cmd_espi_backend)) && \ |
| defined(CONFIG_EC_HOST_CMD_INITIALIZE_AT_BOOT) |
| static int host_cmd_init(void) |
| { |
| const struct device *const dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_host_cmd_espi_backend)); |
| |
| ec_host_cmd_init(ec_host_cmd_backend_get_espi(dev)); |
| return 0; |
| } |
| SYS_INIT(host_cmd_init, POST_KERNEL, CONFIG_EC_HOST_CMD_INIT_PRIORITY); |
| #endif |