summaryrefslogtreecommitdiff
path: root/chip/ish/heci.c
diff options
context:
space:
mode:
authorHyungwoo Yang <hyungwoo.yang@intel.com>2018-10-12 19:28:25 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-12-28 16:14:00 -0800
commita2c87e75cc5b42b900832f0e80244c077b497f2c (patch)
tree4c75487b398691bdc1264a78aad2b48a98aa88fb /chip/ish/heci.c
parent8123080f146d712ca0942873584c8a0f1f489237 (diff)
downloadchrome-ec-a2c87e75cc5b42b900832f0e80244c077b497f2c.tar.gz
ISH: HECI: implement HECI layer
Introduce Host Embedeed Controller Interface for ISH. HECI is bi-directional fully asynchronous communication interface between host and ISH. It enables a host software to communicate with a ISH software(a HECI client). BUG=b:79676054 BRANCH=none TEST=Tested on Atlas board. CQ-DEPEND=CL:1279363 Change-Id: I5fdc3018e9575c5fd0c804a883293f6c9f8aa2e7 Reviewed-on: https://chromium-review.googlesource.com/1279432 Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com> Tested-by: Hyungwoo Yang <hyungwoo.yang@intel.com> Reviewed-by: Hyungwoo Yang <hyungwoo.yang@intel.com> Reviewed-by: Jett Rink <jettrink@chromium.org>
Diffstat (limited to 'chip/ish/heci.c')
-rw-r--r--chip/ish/heci.c940
1 files changed, 940 insertions, 0 deletions
diff --git a/chip/ish/heci.c b/chip/ish/heci.c
new file mode 100644
index 0000000000..512a9393a4
--- /dev/null
+++ b/chip/ish/heci.c
@@ -0,0 +1,940 @@
+/* Copyright 2018 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "atomic.h"
+#include "compile_time_macros.h"
+#include "console.h"
+#include "hbm.h"
+#include "heci_client.h"
+#include "ipc_heci.h"
+#include "system_state.h"
+#include "task.h"
+#include "util.h"
+
+#ifdef DEBUG_IPC_HECI
+#define CPUTS(outstr) cputs(CC_LPC, outstr)
+#define CPRINTS(format, args...) cprints(CC_LPC, format, ## args)
+#define CPRINTF(format, args...) cprintf(CC_LPC, format, ## args)
+#else
+#define CPUTS(outstr)
+#define CPRINTS(format, args...)
+#define CPRINTF(format, args...)
+#endif
+
+struct heci_header {
+ uint8_t fw_addr;
+ uint8_t host_addr;
+ uint16_t length; /* [8:0] length, [14:9] reserved, [15] msg_complete */
+} __packed;
+#define HECI_MSG_CMPL_SHIFT 15
+#define HECI_MSG_LENGTH_MASK 0x01FF
+#define HECI_MSG_LENGTH(length) ((length) & HECI_MSG_LENGTH_MASK)
+#define HECI_MSG_IS_COMPLETED(length) \
+ (!!((length) & (0x01 << HECI_MSG_CMPL_SHIFT)))
+
+#define HECI_IPC_PAYLOAD_SIZE \
+ (IPC_MAX_PAYLOAD_SIZE - sizeof(struct heci_header))
+
+struct heci_msg {
+ struct heci_header hdr;
+ uint8_t payload[HECI_IPC_PAYLOAD_SIZE];
+} __packed;
+
+/* address for Host Bus */
+#define HECI_HBM_ADDRESS 0
+
+/* should be less than HECI_INVALID_HANDLE - 1 */
+BUILD_ASSERT(HECI_MAX_NUM_OF_CLIENTS < 0x0FE);
+
+struct heci_client_connect {
+ uint8_t is_connected; /* client is connected to host */
+ uint8_t host_addr; /* connected host address */
+
+ /* receiving message */
+ uint8_t ignore_rx_msg;
+ uint8_t rx_msg[HECI_MAX_MSG_SIZE];
+ size_t rx_msg_length;
+
+ uint32_t flow_ctrl_creds; /* flow control */
+ struct mutex lock; /* mutext for operation related with connection */
+};
+
+struct heci_client_context {
+ const struct heci_client *client;
+ void *data; /* client specific data */
+
+ struct heci_client_connect connect; /* connection context */
+ struct ss_subsys_device ss_device; /* system state receiver device */
+};
+
+struct heci_bus_context {
+ ipc_handle_t ipc_handle; /* ipc handle for heci protocol */
+
+ int num_of_clients;
+ struct heci_client_context client_ctxs[HECI_MAX_NUM_OF_CLIENTS];
+};
+
+/* declare heci bus */
+struct heci_bus_context heci_bus_ctx = {
+ .ipc_handle = IPC_INVALID_HANDLE,
+};
+
+/* host generates client fw address starting from 1 */
+static inline struct heci_client_context *
+heci_get_client_context(const uint8_t fw_addr)
+{
+ return &heci_bus_ctx.client_ctxs[fw_addr - 1];
+}
+
+static inline struct heci_client_connect *
+heci_get_client_connect(const uint8_t fw_addr)
+{
+ struct heci_client_context *cli_ctx = heci_get_client_context(fw_addr);
+ return &cli_ctx->connect;
+}
+
+static inline int heci_is_client_connected(const uint8_t fw_addr)
+{
+ struct heci_client_context *cli_ctx = heci_get_client_context(fw_addr);
+ return cli_ctx->connect.is_connected;
+}
+
+static inline int heci_is_valid_client_addr(const uint8_t fw_addr)
+{
+ return fw_addr > 0 && fw_addr <= heci_bus_ctx.num_of_clients;
+}
+
+static inline int heci_is_valid_handle(const heci_handle_t handle)
+{
+ return heci_is_valid_client_addr((uintptr_t)(handle));
+}
+
+#define TO_FW_ADDR(handle) ((uintptr_t)(handle))
+#define TO_HECI_HANDLE(fw_addr) ((heci_handle_t)(uintptr_t)(fw_addr))
+
+/* find heci device that contains this system state device in it */
+#define ss_device_to_heci_client_context(ss_dev) \
+ ((struct heci_client_context *)((void *)(ss_dev) - \
+ (void *)(&(((struct heci_client_context *)0)->ss_device))))
+#define client_context_to_handle(cli_ctx) \
+ ((heci_handle_t)((uint32_t)((cli_ctx) - &heci_bus_ctx.client_ctxs[0]) \
+ / sizeof(heci_bus_ctx.client_ctxs[0]) + 1))
+
+/*
+ * each heci device registered as system state device which gets
+ * system state(e.g. suspend/resume, portrait/landscape) events
+ * through system state subsystem from host
+ */
+static int heci_client_suspend(struct ss_subsys_device *ss_device)
+{
+ struct heci_client_context *cli_ctx =
+ ss_device_to_heci_client_context(ss_device);
+ heci_handle_t handle = client_context_to_handle(cli_ctx);
+
+ if (cli_ctx->client->cbs->suspend)
+ cli_ctx->client->cbs->suspend(handle);
+
+ return EC_SUCCESS;
+}
+
+static int heci_client_resume(struct ss_subsys_device *ss_device)
+{
+ struct heci_client_context *cli_ctx =
+ ss_device_to_heci_client_context(ss_device);
+ heci_handle_t handle = client_context_to_handle(cli_ctx);
+
+ if (cli_ctx->client->cbs->resume)
+ cli_ctx->client->cbs->resume(handle);
+
+ return EC_SUCCESS;
+}
+
+struct system_state_callbacks heci_ss_cbs = {
+ .suspend = heci_client_suspend,
+ .resume = heci_client_resume,
+};
+
+/*
+ * This function should be called only by HECI_CLIENT_ENTRY()
+ */
+heci_handle_t heci_register_client(const struct heci_client *client)
+{
+ int ret;
+ struct heci_client_context *cli_ctx;
+
+ if (client == NULL || client->cbs == NULL)
+ return HECI_INVALID_HANDLE;
+
+ /*
+ * we don't need mutex here since this function is called by
+ * entry function which is serialized among heci clients.
+ */
+ if (heci_bus_ctx.num_of_clients >= HECI_MAX_NUM_OF_CLIENTS)
+ return HECI_INVALID_HANDLE;
+
+ /* we only support 1 connection */
+ if (client->max_n_of_connections > 1)
+ return HECI_INVALID_HANDLE;
+
+ if (client->max_msg_size > HECI_MAX_MSG_SIZE)
+ return HECI_INVALID_HANDLE;
+
+ cli_ctx = &heci_bus_ctx.client_ctxs[heci_bus_ctx.num_of_clients++];
+
+ cli_ctx->client = client;
+
+ if (client->cbs->initialize) {
+ ret = client->cbs->initialize(
+ (heci_handle_t)heci_bus_ctx.num_of_clients);
+
+ if (ret) {
+ heci_bus_ctx.num_of_clients--;
+ return HECI_INVALID_HANDLE;
+ }
+ }
+
+ if (client->cbs->suspend || client->cbs->resume) {
+ cli_ctx->ss_device.cbs = &heci_ss_cbs;
+ ss_subsys_register_client(&cli_ctx->ss_device);
+ }
+
+ return (heci_handle_t)heci_bus_ctx.num_of_clients;
+}
+
+static void heci_build_hbm_header(struct heci_header *hdr, uint32_t length)
+{
+ hdr->fw_addr = HECI_HBM_ADDRESS;
+ hdr->host_addr = HECI_HBM_ADDRESS;
+ hdr->length = length;
+ /* payload of hbm is less than IPC payload */
+ hdr->length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
+}
+
+static void heci_build_fixed_client_header(struct heci_header *hdr,
+ const uint8_t fw_addr,
+ const uint32_t length)
+{
+ hdr->fw_addr = fw_addr;
+ hdr->host_addr = 0;
+ hdr->length = length;
+ /* Fixed client payload < IPC payload */
+ hdr->length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
+}
+
+static int heci_send_heci_msg(struct heci_msg *msg)
+{
+ int length, written;
+
+ if (heci_bus_ctx.ipc_handle == IPC_INVALID_HANDLE)
+ return -1;
+
+ length = sizeof(msg->hdr) + HECI_MSG_LENGTH(msg->hdr.length);
+ written = ipc_write(heci_bus_ctx.ipc_handle, msg, length);
+
+ if (written != length) {
+ CPRINTF("%s error : len = %d err = %d\n", __func__,
+ (int)length, written);
+ return -EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
+}
+
+int heci_set_client_data(const heci_handle_t handle, void *data)
+{
+ struct heci_client_context *cli_ctx;
+ const uint8_t fw_addr = TO_FW_ADDR(handle);
+
+ if (!heci_is_valid_handle(handle))
+ return -EC_ERROR_INVAL;
+
+ cli_ctx = heci_get_client_context(fw_addr);
+ cli_ctx->data = data;
+
+ return EC_SUCCESS;
+}
+
+void *heci_get_client_data(const heci_handle_t handle)
+{
+ struct heci_client_context *cli_ctx;
+ const uint8_t fw_addr = TO_FW_ADDR(handle);
+
+ if (!heci_is_valid_handle(handle))
+ return NULL;
+
+ cli_ctx = heci_get_client_context(fw_addr);
+ return cli_ctx->data;
+}
+
+int heci_send_msg(const heci_handle_t handle, uint8_t *buf,
+ const size_t buf_size)
+{
+ int buf_offset = 0, ret = 0, remain, payload_size;
+ struct heci_client_connect *connect;
+ struct heci_msg msg;
+ const uint8_t fw_addr = TO_FW_ADDR(handle);
+
+ if (!heci_is_valid_handle(handle))
+ return -EC_ERROR_INVAL;
+
+ if (buf_size > HECI_MAX_MSG_SIZE)
+ return -EC_ERROR_OVERFLOW;
+
+ connect = heci_get_client_connect(fw_addr);
+ mutex_lock(&connect->lock);
+
+ if (!heci_is_client_connected(fw_addr)) {
+ ret = -HECI_ERR_CLIENT_IS_NOT_CONNECTED;
+ goto err_locked;
+ }
+
+ if (!connect->flow_ctrl_creds) {
+ CPRINTF("no cred\n");
+ ret = -HECI_ERR_NO_CRED_FROM_CLIENT_IN_HOST;
+ goto err_locked;
+ }
+
+ msg.hdr.fw_addr = fw_addr;
+ msg.hdr.host_addr = connect->host_addr;
+
+ remain = buf_size;
+ while (remain) {
+ if (remain > HECI_IPC_PAYLOAD_SIZE) {
+ msg.hdr.length = HECI_IPC_PAYLOAD_SIZE;
+ payload_size = HECI_IPC_PAYLOAD_SIZE;
+ } else {
+ msg.hdr.length = remain;
+ /* set as last heci msg */
+ msg.hdr.length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
+ payload_size = remain;
+ }
+
+ memcpy(msg.payload, buf + buf_offset, payload_size);
+
+ heci_send_heci_msg(&msg);
+
+ remain -= payload_size;
+ buf_offset += payload_size;
+ }
+
+ atomic_sub(&connect->flow_ctrl_creds, 1);
+ mutex_unlock(&connect->lock);
+
+ return buf_size;
+
+err_locked:
+ mutex_unlock(&connect->lock);
+
+ return ret;
+}
+
+int heci_send_msgs(const heci_handle_t handle,
+ const struct heci_msg_list *msg_list)
+{
+ struct heci_msg_item *cur_item;
+ int total_size = 0;
+ int i, msg_cur_pos, buf_size, copy_size, msg_sent;
+ struct heci_client_connect *connect;
+ struct heci_msg msg;
+ const uint8_t fw_addr = TO_FW_ADDR(handle);
+
+ if (!heci_is_valid_handle(handle))
+ return -EC_ERROR_INVAL;
+
+ for (i = 0; i < msg_list->num_of_items; i++) {
+ if (!msg_list->items[i]->size || !msg_list->items[i]->buf)
+ return -EC_ERROR_INVAL;
+
+ total_size += msg_list->items[i]->size;
+ }
+
+ if (total_size > HECI_MAX_MSG_SIZE)
+ return -EC_ERROR_OVERFLOW;
+
+ if (msg_list->num_of_items > HECI_MAX_MSGS)
+ return -HECI_ERR_TOO_MANY_MSG_ITEMS;
+
+ connect = heci_get_client_connect(fw_addr);
+ mutex_lock(&connect->lock);
+
+ if (!heci_is_client_connected(fw_addr)) {
+ total_size = -HECI_ERR_CLIENT_IS_NOT_CONNECTED;
+ goto err_locked;
+ }
+
+ if (!connect->flow_ctrl_creds) {
+ CPRINTF("no cred\n");
+ total_size = -HECI_ERR_NO_CRED_FROM_CLIENT_IN_HOST;
+ goto err_locked;
+ }
+
+ msg.hdr.fw_addr = fw_addr;
+ msg.hdr.host_addr = connect->host_addr;
+
+ i = 1;
+ msg_cur_pos = 0;
+ buf_size = 0;
+ cur_item = msg_list->items[0];
+ msg_sent = 0;
+ while (1) {
+ /* get next item if current item is consumed */
+ if (msg_cur_pos == cur_item->size) {
+ /*
+ * break if no more item.
+ * if "msg" contains data to be sent
+ * it will be sent after break.
+ */
+ if (i == msg_list->num_of_items)
+ break;
+
+ /* get next item and reset msg_cur_pos */
+ cur_item = msg_list->items[i++];
+ msg_cur_pos = 0;
+ }
+
+ /* send data in ipc buf if it's completely filled */
+ if (buf_size == HECI_IPC_PAYLOAD_SIZE) {
+ msg.hdr.length = buf_size;
+ msg_sent += buf_size;
+
+ /* no leftovers, send the last msg here */
+ if (msg_sent == total_size) {
+ msg.hdr.length |=
+ (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
+ }
+
+ heci_send_heci_msg(&msg);
+ buf_size = 0;
+ }
+
+ /* fill ipc msg buffer */
+ if (cur_item->size - msg_cur_pos >
+ HECI_IPC_PAYLOAD_SIZE - buf_size) {
+ copy_size = HECI_IPC_PAYLOAD_SIZE - buf_size;
+ } else {
+ copy_size = cur_item->size - msg_cur_pos;
+ }
+
+ memcpy(msg.payload + buf_size, cur_item->buf + msg_cur_pos,
+ copy_size);
+
+ msg_cur_pos += copy_size;
+ buf_size += copy_size;
+ }
+
+ /* leftovers ? send last msg */
+ if (buf_size != 0) {
+ msg.hdr.length = buf_size;
+ msg.hdr.length |= (uint16_t)1 << HECI_MSG_CMPL_SHIFT;
+
+ heci_send_heci_msg(&msg);
+ }
+
+ atomic_sub(&connect->flow_ctrl_creds, 1);
+
+err_locked:
+ mutex_unlock(&connect->lock);
+
+ return total_size;
+
+}
+
+/* For now, we only support fixed client payload size < IPC payload size */
+int heci_send_fixed_client_msg(const uint8_t fw_addr, uint8_t *buf,
+ const size_t buf_size)
+{
+ struct heci_msg msg;
+
+ heci_build_fixed_client_header(&msg.hdr, fw_addr, buf_size);
+
+ memcpy(msg.payload, buf, buf_size);
+
+ heci_send_heci_msg(&msg);
+
+ return EC_SUCCESS;
+}
+
+static int handle_version_req(struct hbm_version_req *ver_req)
+{
+ struct hbm_version_res *ver_res;
+ struct heci_msg heci_msg;
+ struct hbm_i2h *i2h;
+
+ heci_build_hbm_header(&heci_msg.hdr,
+ sizeof(i2h->cmd) + sizeof(*ver_res));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_VERSION_RESP;
+ ver_res = (struct hbm_version_res *)&i2h->data;
+
+ memset(ver_res, 0, sizeof(*ver_res));
+
+ ver_res->version.major = HBM_MAJOR_VERSION;
+ ver_res->version.minor = HBM_MINOR_VERSION;
+ if (ver_req->version.major == HBM_MAJOR_VERSION &&
+ ver_req->version.minor == HBM_MINOR_VERSION) {
+ ver_res->supported = 1;
+ } else {
+ ver_res->supported = 0;
+ }
+
+ heci_send_heci_msg(&heci_msg);
+
+ return EC_SUCCESS;
+}
+
+#define BITS_PER_BYTE 8
+/* get number of bits for one element of "valid_addresses" array */
+#define BITS_PER_ELEMENT \
+ (sizeof(((struct hbm_enum_res *)0)->valid_addresses[0]) * BITS_PER_BYTE)
+
+static int handle_enum_req(struct hbm_enum_req *enum_req)
+{
+ struct hbm_enum_res *enum_res;
+ struct heci_msg heci_msg;
+ struct hbm_i2h *i2h;
+ int i;
+
+ heci_build_hbm_header(&heci_msg.hdr,
+ sizeof(i2h->cmd) + sizeof(*enum_res));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_HOST_ENUM_RESP;
+ enum_res = (struct hbm_enum_res *)&i2h->data;
+
+ memset(enum_res, 0, sizeof(*enum_res));
+
+ /*
+ * current host driver implementation doesn't allow to use bit0
+ * due to its fw address generation scheme.
+ * bit-0 set -> fw address "0", bit-1 set -> fw address "1"
+ * but fw address "0" is used for HECI bus management only.
+ * so the bitmap should not use bit-0
+ */
+ for (i = 0; i < heci_bus_ctx.num_of_clients; i++) {
+ enum_res->valid_addresses[(i + 1) / BITS_PER_ELEMENT] |=
+ 1 << ((i + 1) & (BITS_PER_ELEMENT - 1));
+ }
+
+ heci_send_heci_msg(&heci_msg);
+
+ return EC_SUCCESS;
+}
+
+static int handle_client_prop_req(struct hbm_client_prop_req *client_prop_req)
+{
+ struct hbm_client_prop_res *client_prop_res;
+ struct heci_msg heci_msg;
+ struct hbm_i2h *i2h;
+ struct heci_client_context *client_ctx;
+ const struct heci_client *client;
+
+ heci_build_hbm_header(&heci_msg.hdr,
+ sizeof(i2h->cmd) + sizeof(*client_prop_res));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_HOST_CLIENT_PROP_RESP;
+ client_prop_res = (struct hbm_client_prop_res *)&i2h->data;
+
+ memset(client_prop_res, 0, sizeof(*client_prop_res));
+
+ client_prop_res->address = client_prop_req->address;
+ if (!heci_is_valid_client_addr(client_prop_req->address)) {
+ client_prop_res->status = HECI_CONNECT_STATUS_CLIENT_NOT_FOUND;
+ } else {
+ struct hbm_client_properties *client_prop;
+
+ client_ctx = heci_get_client_context(client_prop_req->address);
+ client = client_ctx->client;
+ client_prop = &client_prop_res->client_prop;
+
+ client_prop->protocol_name = client->protocol_id;
+ client_prop->protocol_version = client->protocol_ver;
+ client_prop->max_number_of_connections =
+ client->max_n_of_connections;
+ client_prop->max_msg_length = client->max_msg_size;
+ client_prop->dma_hdr_len = client->dma_header_length;
+ client_prop->dma_hdr_len |= client->dma_enabled ?
+ CLIENT_DMA_ENABLE : 0;
+ }
+
+ heci_send_heci_msg(&heci_msg);
+
+ return EC_SUCCESS;
+}
+
+static int heci_send_flow_control(uint8_t fw_addr)
+{
+ struct heci_client_connect *connect;
+ struct hbm_i2h *i2h;
+ struct hbm_flow_control *flow_ctrl;
+ struct heci_msg heci_msg;
+
+ connect = heci_get_client_connect(fw_addr);
+
+ heci_build_hbm_header(&heci_msg.hdr,
+ sizeof(i2h->cmd) + sizeof(*flow_ctrl));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_FLOW_CONTROL;
+ flow_ctrl = (struct hbm_flow_control *)&i2h->data;
+
+ memset(flow_ctrl, 0, sizeof(*flow_ctrl));
+
+ flow_ctrl->fw_addr = fw_addr;
+ flow_ctrl->host_addr = connect->host_addr;
+
+ heci_send_heci_msg(&heci_msg);
+
+ return EC_SUCCESS;
+}
+
+static int handle_client_connect_req(
+ struct hbm_client_connect_req *client_connect_req)
+{
+ struct hbm_client_connect_res *client_connect_res;
+ struct heci_msg heci_msg;
+ struct hbm_i2h *i2h;
+ struct heci_client_connect *connect;
+
+ heci_build_hbm_header(&heci_msg.hdr,
+ sizeof(i2h->cmd) + sizeof(*client_connect_res));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_CLIENT_CONNECT_RESP;
+ client_connect_res = (struct hbm_client_connect_res *)&i2h->data;
+
+ memset(client_connect_res, 0, sizeof(*client_connect_res));
+
+ client_connect_res->fw_addr = client_connect_req->fw_addr;
+ client_connect_res->host_addr = client_connect_req->host_addr;
+ if (!heci_is_valid_client_addr(client_connect_req->fw_addr)) {
+ client_connect_res->status =
+ HECI_CONNECT_STATUS_CLIENT_NOT_FOUND;
+ } else {
+ connect = heci_get_client_connect(client_connect_req->fw_addr);
+ if (connect->is_connected) {
+ client_connect_res->status =
+ HECI_CONNECT_STATUS_ALREADY_EXISTS;
+ } else {
+ connect->is_connected = 1;
+ connect->host_addr = client_connect_req->host_addr;
+ }
+ }
+
+ heci_send_heci_msg(&heci_msg);
+ heci_send_flow_control(client_connect_req->fw_addr);
+
+ return EC_SUCCESS;
+}
+
+static int handle_flow_control_cmd(struct hbm_flow_control *flow_ctrl)
+{
+ struct heci_client_connect *connect;
+
+ if (!heci_is_valid_client_addr(flow_ctrl->fw_addr))
+ return -1;
+
+ if (!heci_is_client_connected(flow_ctrl->fw_addr))
+ return -1;
+
+ connect = heci_get_client_connect(flow_ctrl->fw_addr);
+ atomic_add(&connect->flow_ctrl_creds, 1);
+
+ return EC_SUCCESS;
+}
+
+static void heci_handle_client_msg(struct heci_msg *msg, size_t length)
+{
+ struct heci_client_context *cli_ctx;
+ struct heci_client_connect *connect;
+ const struct heci_client_callbacks *cbs;
+ int payload_size;
+
+ if (!heci_is_valid_client_addr(msg->hdr.fw_addr))
+ return;
+
+ if (!heci_is_client_connected(msg->hdr.fw_addr))
+ return;
+
+ cli_ctx = heci_get_client_context(msg->hdr.fw_addr);
+ cbs = cli_ctx->client->cbs;
+ connect = &cli_ctx->connect;
+
+ payload_size = HECI_MSG_LENGTH(msg->hdr.length);
+ if (connect->is_connected &&
+ msg->hdr.host_addr == connect->host_addr) {
+ if (!connect->ignore_rx_msg &&
+ connect->rx_msg_length + payload_size > HECI_MAX_MSG_SIZE) {
+ connect->ignore_rx_msg = 1; /* too big. discard */
+ }
+
+ if (!connect->ignore_rx_msg) {
+ memcpy(connect->rx_msg + connect->rx_msg_length,
+ msg->payload, payload_size);
+
+ connect->rx_msg_length += payload_size;
+ }
+
+ if (HECI_MSG_IS_COMPLETED(msg->hdr.length)) {
+ if (!connect->ignore_rx_msg) {
+ cbs->new_msg_received(
+ TO_HECI_HANDLE(msg->hdr.fw_addr),
+ connect->rx_msg,
+ connect->rx_msg_length);
+ }
+
+ connect->rx_msg_length = 0;
+ connect->ignore_rx_msg = 0;
+
+ heci_send_flow_control(msg->hdr.fw_addr);
+ }
+ }
+}
+
+static int handle_client_disconnect_req(
+ struct hbm_client_disconnect_req *client_disconnect_req)
+{
+ struct hbm_client_disconnect_res *client_disconnect_res;
+ struct heci_msg heci_msg;
+ struct hbm_i2h *i2h;
+ struct heci_client_context *cli_ctx;
+ struct heci_client_connect *connect;
+ const struct heci_client_callbacks *cbs;
+ uint8_t fw_addr;
+
+ heci_build_hbm_header(&heci_msg.hdr, sizeof(i2h->cmd) +
+ sizeof(*client_disconnect_res));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_CLIENT_DISCONNECT_RESP;
+ client_disconnect_res = (struct hbm_client_disconnect_res *)&i2h->data;
+
+ memset(client_disconnect_res, 0, sizeof(*client_disconnect_res));
+
+ fw_addr = client_disconnect_req->fw_addr;
+
+ client_disconnect_res->fw_addr = fw_addr;
+ client_disconnect_res->host_addr = client_disconnect_req->host_addr;
+ if (!heci_is_valid_client_addr(fw_addr) ||
+ !heci_is_client_connected(fw_addr)) {
+ client_disconnect_res->status =
+ HECI_CONNECT_STATUS_CLIENT_NOT_FOUND;
+ } else {
+ connect = heci_get_client_connect(fw_addr);
+ cli_ctx = heci_get_client_context(fw_addr);
+ cbs = cli_ctx->client->cbs;
+ mutex_lock(&connect->lock);
+ if (connect->is_connected) {
+ cbs->disconnected(TO_HECI_HANDLE(fw_addr));
+ connect->is_connected = 0;
+ }
+ mutex_unlock(&connect->lock);
+ }
+
+ heci_send_heci_msg(&heci_msg);
+
+ return EC_SUCCESS;
+}
+
+/* host stops due to version mismatch */
+static int handle_host_stop_req(struct hbm_host_stop_req *host_stop_req)
+{
+ struct hbm_host_stop_res *host_stop_res;
+ struct heci_msg heci_msg;
+ struct hbm_i2h *i2h;
+
+ heci_build_hbm_header(&heci_msg.hdr,
+ sizeof(i2h->cmd) + sizeof(*host_stop_res));
+
+ i2h = (struct hbm_i2h *)heci_msg.payload;
+ i2h->cmd = HECI_BUS_MSG_HOST_STOP_RESP;
+ host_stop_res = (struct hbm_host_stop_res *)&i2h->data;
+
+ memset(host_stop_res, 0, sizeof(*host_stop_res));
+
+ heci_send_heci_msg(&heci_msg);
+
+ return EC_SUCCESS;
+}
+
+static int is_hbm_validity(struct hbm_h2i *h2i, size_t length)
+{
+ int valid_msg_len;
+
+ valid_msg_len = sizeof(h2i->cmd);
+
+ switch (h2i->cmd) {
+ case HECI_BUS_MSG_VERSION_REQ:
+ valid_msg_len += sizeof(struct hbm_version_req);
+ break;
+
+ case HECI_BUS_MSG_HOST_ENUM_REQ:
+ valid_msg_len += sizeof(struct hbm_enum_req);
+ break;
+
+ case HECI_BUS_MSG_HOST_CLIENT_PROP_REQ:
+ valid_msg_len += sizeof(struct hbm_client_prop_req);
+ break;
+
+ case HECI_BUS_MSG_CLIENT_CONNECT_REQ:
+ valid_msg_len += sizeof(struct hbm_client_connect_req);
+ break;
+
+ case HECI_BUS_MSG_FLOW_CONTROL:
+ valid_msg_len += sizeof(struct hbm_flow_control);
+ break;
+
+ case HECI_BUS_MSG_CLIENT_DISCONNECT_REQ:
+ valid_msg_len += sizeof(struct hbm_client_disconnect_req);
+ break;
+
+ case HECI_BUS_MSG_HOST_STOP_REQ:
+ valid_msg_len += sizeof(struct hbm_host_stop_req);
+ break;
+
+/* TODO: DMA support for large data */
+#if 0
+ case HECI_BUS_MSG_DMA_REQ:
+ valid_msg_len += sizeof(struct hbm_dma_req);
+ break;
+
+ case HECI_BUS_MSG_DMA_ALLOC_NOTIFY:
+ valid_msg_len += sizeof(struct hbm_dma_alloc_notify);
+ break;
+
+ case HECI_BUS_MSG_DMA_XFER_REQ: /* DMA transfer to FW */
+ valid_msg_len += sizeof(struct hbm_dma_xfer_req);
+ break;
+
+ case HECI_BUS_MSG_DMA_XFER_RESP: /* Ack for DMA transfer from FW */
+ valid_msg_len += sizeof(struct hbm_dma_xfer_resp);
+ break;
+#endif
+ default:
+ break;
+ }
+
+ if (valid_msg_len != length) {
+ CPRINTF("invalid cmd(%d) valid : %d, cur : %d\n",
+ valid_msg_len, length);
+ /* TODO: invalid cmd. not sure to reply with error ? */
+ return 0;
+ }
+
+ return 1;
+}
+
+static void heci_handle_hbm(struct hbm_h2i *h2i, size_t length)
+{
+ void *data = (void *)&h2i->data;
+
+ if (!is_hbm_validity(h2i, length))
+ return;
+
+ switch (h2i->cmd) {
+ case HECI_BUS_MSG_VERSION_REQ:
+ handle_version_req((struct hbm_version_req *)data);
+ break;
+
+ case HECI_BUS_MSG_HOST_ENUM_REQ:
+ handle_enum_req((struct hbm_enum_req *)data);
+ break;
+
+ case HECI_BUS_MSG_HOST_CLIENT_PROP_REQ:
+ handle_client_prop_req((struct hbm_client_prop_req *)data);
+ break;
+
+ case HECI_BUS_MSG_CLIENT_CONNECT_REQ:
+ handle_client_connect_req(
+ (struct hbm_client_connect_req *)data);
+ break;
+
+ case HECI_BUS_MSG_FLOW_CONTROL:
+ handle_flow_control_cmd((struct hbm_flow_control *)data);
+ break;
+
+ case HECI_BUS_MSG_CLIENT_DISCONNECT_REQ:
+ handle_client_disconnect_req(
+ (struct hbm_client_disconnect_req *)data);
+ break;
+
+ case HECI_BUS_MSG_HOST_STOP_REQ:
+ handle_host_stop_req((struct hbm_host_stop_req *)data);
+ break;
+
+/* TODO: DMA transfer if data is too big >= ? KB */
+#if 0
+ case HECI_BUS_MSG_DMA_REQ:
+ handle_dma_req((struct hbm_dma_req *)data);
+ break;
+
+ case HECI_BUS_MSG_DMA_ALLOC_NOTIFY:
+ handle_dma_alloc_notify((struct hbm_dma_alloc_notify *));
+ break;
+
+ case HECI_BUS_MSG_DMA_XFER_REQ: /* DMA transfer to FW */
+ handle_dma_xfer_req((struct hbm_dma_xfer_req *)data);
+ break;
+
+ case HECI_BUS_MSG_DMA_XFER_RESP: /* Ack for DMA transfer from FW */
+ handle_dma_xfer_resp((struct hbm_dma_xfer_resp *)data);
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+static void heci_handle_heci_msg(struct heci_msg *heci_msg, size_t msg_length)
+{
+ if (heci_msg->hdr.fw_addr) {
+ if (heci_msg->hdr.fw_addr == HECI_FIXED_SYSTEM_STATE_ADDR)
+ heci_handle_system_state_msg(
+ heci_msg->payload,
+ HECI_MSG_LENGTH(heci_msg->hdr.length)
+ );
+ else
+ heci_handle_client_msg(heci_msg, msg_length);
+ } else {
+ heci_handle_hbm((struct hbm_h2i *)heci_msg->payload,
+ HECI_MSG_LENGTH(heci_msg->hdr.length));
+ }
+}
+
+/* event flag for HECI msg */
+#define EVENT_FLAG_BIT_HECI_MSG TASK_EVENT_CUSTOM(1)
+
+void heci_rx_task(void)
+{
+ int msg_len;
+ struct heci_msg heci_msg;
+ ipc_handle_t ipc_handle;
+
+ /* open IPC for HECI protocol */
+ heci_bus_ctx.ipc_handle = ipc_open(IPC_PEER_ID_HOST, IPC_PROTOCOL_HECI,
+ EVENT_FLAG_BIT_HECI_MSG);
+
+ ASSERT(heci_bus_ctx.ipc_handle != IPC_INVALID_HANDLE);
+
+ /* get ipc handle */
+ ipc_handle = heci_bus_ctx.ipc_handle;
+
+ while (1) {
+ /* task will be blocked here, waiting for event */
+ msg_len = ipc_read(ipc_handle, &heci_msg, sizeof(heci_msg), -1);
+
+ if (msg_len <= 0) {
+ CPRINTS("discard heci packet");
+ continue;
+ }
+
+ if (HECI_MSG_LENGTH(heci_msg.hdr.length) + sizeof(heci_msg.hdr)
+ == msg_len)
+ heci_handle_heci_msg(&heci_msg, msg_len);
+ else
+ CPRINTS("msg len mismatch.. discard..");
+ }
+}