summaryrefslogtreecommitdiff
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
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>
-rw-r--r--chip/ish/build.mk1
-rw-r--r--chip/ish/hbm.h195
-rw-r--r--chip/ish/heci.c940
-rw-r--r--chip/ish/heci_client.h102
-rw-r--r--chip/ish/system_state.h31
-rw-r--r--chip/ish/system_state_subsys.c149
6 files changed, 1418 insertions, 0 deletions
diff --git a/chip/ish/build.mk b/chip/ish/build.mk
index c8461b3638..13b6ae2474 100644
--- a/chip/ish/build.mk
+++ b/chip/ish/build.mk
@@ -21,6 +21,7 @@ chip-y+=clock.o gpio.o system.o hwtimer.o uart.o flash.o
chip-$(CONFIG_I2C)+=i2c.o
chip-$(CONFIG_HOSTCMD_LPC)+=ipc.o
chip-$(CONFIG_ISH_IPC)+=ipc_heci.o
+chip-$(CONFIG_HECI)+=heci.o system_state_subsys.o
chip-$(CONFIG_WATCHDOG)+=watchdog.o
# location of the scripts and keys used to pack the SPI flash image
diff --git a/chip/ish/hbm.h b/chip/ish/hbm.h
new file mode 100644
index 0000000000..edfb587d21
--- /dev/null
+++ b/chip/ish/hbm.h
@@ -0,0 +1,195 @@
+/* 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.
+ */
+
+#ifndef __HBM_H
+#define __HBM_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include "heci_client.h"
+
+#define HBM_MAJOR_VERSION 1
+#ifdef HECI_ENABLE_DMA
+#define HBM_MINOR_VERSION 2
+#else
+#define HBM_MINOR_VERSION 0
+#endif
+
+#define __packed __attribute__((packed))
+
+#define HECI_MSG_REPONSE_FLAG 0x80
+
+enum HECI_BUS_MSG {
+ /* requests */
+ HECI_BUS_MSG_VERSION_REQ = 1,
+ HECI_BUS_MSG_HOST_STOP_REQ = 2,
+ HECI_BUS_MSG_ME_STOP_REQ = 3,
+ HECI_BUS_MSG_HOST_ENUM_REQ = 4,
+ HECI_BUS_MSG_HOST_CLIENT_PROP_REQ = 5,
+ HECI_BUS_MSG_CLIENT_CONNECT_REQ = 6,
+ HECI_BUS_MSG_CLIENT_DISCONNECT_REQ = 7,
+ HECI_BUS_MSG_FLOW_CONTROL = 8,
+ HECI_BUS_MSG_RESET_REQ = 9,
+ HECI_BUS_MSG_ADD_CLIENT_REQ = 0x0A,
+ HECI_BUS_MSG_DMA_REQ = 0x10,
+ HECI_BUS_MSG_DMA_ALLOC_NOTIFY = 0x11,
+ HECI_BUS_MSG_DMA_XFER_REQ = 0x12,
+
+ /* responses */
+ HECI_BUS_MSG_VERSION_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_VERSION_REQ),
+ HECI_BUS_MSG_HOST_STOP_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_HOST_STOP_REQ),
+ HECI_BUS_MSG_HOST_ENUM_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_HOST_ENUM_REQ),
+ HECI_BUS_MSG_HOST_CLIENT_PROP_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_HOST_CLIENT_PROP_REQ),
+ HECI_BUS_MSG_CLIENT_CONNECT_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_CLIENT_CONNECT_REQ),
+ HECI_BUS_MSG_CLIENT_DISCONNECT_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_CLIENT_DISCONNECT_REQ),
+ HECI_BUS_MSG_RESET_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_RESET_REQ),
+ HECI_BUS_MSG_ADD_CLIENT_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_ADD_CLIENT_REQ),
+ HECI_BUS_MSG_DMA_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_DMA_REQ),
+ HECI_BUS_MSG_DMA_ALLOC_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_DMA_ALLOC_NOTIFY),
+ HECI_BUS_MSG_DMA_XFER_RESP =
+ (HECI_MSG_REPONSE_FLAG | HECI_BUS_MSG_DMA_XFER_REQ)
+};
+
+enum {
+ HECI_CONNECT_STATUS_SUCCESS = 0,
+ HECI_CONNECT_STATUS_CLIENT_NOT_FOUND = 1,
+ HECI_CONNECT_STATUS_ALREADY_EXISTS = 2,
+ HECI_CONNECT_STATUS_REJECTED = 3,
+ HECI_CONNECT_STATUS_INVALID_PARAMETER = 4,
+ HECI_CONNECT_STATUS_INACTIVE_CLIENT = 5,
+};
+
+struct hbm_version {
+ uint8_t minor;
+ uint8_t major;
+} __packed;
+
+struct hbm_version_req {
+ uint8_t reserved;
+ struct hbm_version version;
+} __packed;
+
+struct hbm_version_res {
+ uint8_t supported;
+ struct hbm_version version;
+} __packed;
+
+struct hbm_enum_req {
+ uint8_t reserved[3];
+} __packed;
+
+struct hbm_enum_res {
+ uint8_t reserved[3];
+ uint8_t valid_addresses[32];
+} __packed;
+
+struct hbm_client_prop_req {
+ uint8_t address;
+ uint8_t reserved[2];
+} __packed;
+
+#define CLIENT_DMA_ENABLE 0x80
+
+struct hbm_client_properties {
+ struct heci_guid protocol_name; /* heci client protocol ID */
+ uint8_t protocol_version; /* protocol version */
+ /* max connection from host to client. currently only 1 is allowed */
+ uint8_t max_number_of_connections;
+ uint8_t fixed_address; /* not yet supported */
+ uint8_t single_recv_buf; /* not yet supported */
+ uint32_t max_msg_length; /* max payload size */
+ /* not yet supported. [7] enable/disable, [6:0] dma length */
+ uint8_t dma_hdr_len;
+ uint8_t reserved4;
+ uint8_t reserved5;
+ uint8_t reserved6;
+} __packed;
+
+struct hbm_client_prop_res {
+ uint8_t address;
+ uint8_t status;
+ uint8_t reserved[1];
+ struct hbm_client_properties client_prop;
+} __packed;
+
+struct hbm_client_connect_req {
+ uint8_t fw_addr;
+ uint8_t host_addr;
+ uint8_t reserved;
+} __packed;
+
+struct hbm_client_connect_res {
+ uint8_t fw_addr;
+ uint8_t host_addr;
+ uint8_t status;
+} __packed;
+
+struct hbm_flow_control {
+ uint8_t fw_addr;
+ uint8_t host_addr;
+ uint8_t reserved[5];
+} __packed;
+
+struct hbm_client_disconnect_req {
+ uint8_t fw_addr;
+ uint8_t host_addr;
+ uint8_t reserved;
+} __packed;
+
+struct hbm_client_disconnect_res {
+ uint8_t fw_addr;
+ uint8_t host_addr;
+ uint8_t status;
+} __packed;
+
+struct hbm_host_stop_req {
+ uint8_t reason;
+ uint8_t reserved[2];
+};
+
+struct hbm_host_stop_res {
+ uint8_t reserved[3];
+};
+
+/* host bus message : host -> ish */
+struct hbm_h2i {
+ uint8_t cmd;
+ union {
+ struct hbm_version_req ver_req;
+ struct hbm_enum_req enum_req;
+ struct hbm_client_prop_req client_prop_req;
+ struct hbm_client_connect_req client_connect_req;
+ struct hbm_flow_control flow_ctrl;
+ struct hbm_client_disconnect_req client_disconnect_req;
+ struct hbm_host_stop_req host_stop_req;
+ } data;
+} __packed;
+
+/* host bus message : i2h -> host */
+struct hbm_i2h {
+ uint8_t cmd;
+ union {
+ struct hbm_version_res ver_res;
+ struct hbm_enum_res enum_res;
+ struct hbm_client_prop_res client_prop_res;
+ struct hbm_client_connect_res client_connect_res;
+ struct hbm_flow_control flow_ctrl;
+ struct hbm_client_disconnect_res client_disconnect_res;
+ struct hbm_host_stop_res host_stop_res;
+ } data;
+} __packed;
+
+#endif /* __HBM_H */
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..");
+ }
+}
diff --git a/chip/ish/heci_client.h b/chip/ish/heci_client.h
new file mode 100644
index 0000000000..00f4753951
--- /dev/null
+++ b/chip/ish/heci_client.h
@@ -0,0 +1,102 @@
+/* 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.
+ */
+
+#ifndef __HECI_CLIENT_H
+#define __HECI_CLIENT_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include "hooks.h"
+
+#define HECI_MAX_NUM_OF_CLIENTS 2
+
+#define HECI_MAX_MSG_SIZE 4960
+#define HECI_MAX_MSGS 3
+
+enum HECI_ERR {
+ HECI_ERR_TOO_MANY_MSG_ITEMS = EC_ERROR_INTERNAL_FIRST + 0,
+ HECI_ERR_NO_CRED_FROM_CLIENT_IN_HOST = EC_ERROR_INTERNAL_FIRST + 1,
+ HECI_ERR_CLIENT_IS_NOT_CONNECTED = EC_ERROR_INTERNAL_FIRST + 2,
+};
+
+typedef void * heci_handle_t;
+
+#define HECI_INVALID_HANDLE NULL
+
+struct heci_guid {
+ uint32_t data1;
+ uint16_t data2;
+ uint16_t data3;
+ uint8_t data4[8];
+};
+
+struct heci_client_callbacks {
+ /*
+ * called while registering heci client.
+ * if returns non-zero, the registration will fail.
+ */
+ int (*initialize)(const heci_handle_t handle);
+ /* called when new heci msg for the client is arrived */
+ void (*new_msg_received)(const heci_handle_t handle, uint8_t *msg,
+ const size_t msg_size);
+ /* called when the heci client is disconnected */
+ void (*disconnected)(const heci_handle_t handle);
+
+ /* called when ISH goes to suspend */
+ int (*suspend)(const heci_handle_t);
+ /* called when ISH resumes */
+ int (*resume)(const heci_handle_t);
+};
+
+struct heci_client {
+ struct heci_guid protocol_id;
+ uint32_t max_msg_size;
+ uint8_t protocol_ver;
+ uint8_t max_n_of_connections;
+ uint8_t dma_header_length :7;
+ uint8_t dma_enabled :1;
+
+ const struct heci_client_callbacks *cbs;
+};
+
+struct heci_msg_item {
+ size_t size;
+ uint8_t *buf;
+};
+
+struct heci_msg_list {
+ int num_of_items;
+ struct heci_msg_item *items[HECI_MAX_MSGS];
+};
+
+/*
+ * Do not call this function directly.
+ * The function should be called only by HECI_CLIENT_ENTRY()
+ */
+heci_handle_t heci_register_client(const struct heci_client *client);
+int heci_set_client_data(const heci_handle_t handle, void *data);
+void *heci_get_client_data(const heci_handle_t handle);
+/* send client msg */
+int heci_send_msg(const heci_handle_t handle, uint8_t *buf,
+ const size_t buf_size);
+/*
+ * send client msgs(using list of buffer&size).
+ * heci_msg_item with size == 0 is not acceptable.
+ */
+int heci_send_msgs(const heci_handle_t handle,
+ const struct heci_msg_list *msg_list);
+/* send msg to fixed client(system level client) */
+int heci_send_fixed_client_msg(const uint8_t fw_addr, uint8_t *buf,
+ const size_t buf_size);
+
+#define HECI_CLIENT_ENTRY(heci_client) \
+ void _heci_entry_##heci_client(void) \
+ { \
+ heci_register_client(&(heci_client)); \
+ } \
+ DECLARE_HOOK(HOOK_INIT, _heci_entry_##heci_client, HOOK_PRIO_LAST - 1)
+
+#endif /* __HECI_CLIENT_H */
diff --git a/chip/ish/system_state.h b/chip/ish/system_state.h
new file mode 100644
index 0000000000..20de1aaf4b
--- /dev/null
+++ b/chip/ish/system_state.h
@@ -0,0 +1,31 @@
+/* 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.
+ */
+
+#ifndef __SYSTEM_STATE_H
+#define __SYSTEM_STATE_H
+
+#define HECI_FIXED_SYSTEM_STATE_ADDR 13
+
+struct ss_subsys_device;
+
+struct system_state_callbacks {
+ int (*resume)(struct ss_subsys_device *ss_device);
+ int (*suspend)(struct ss_subsys_device *ss_device);
+};
+
+struct ss_subsys_device {
+ struct system_state_callbacks *cbs;
+};
+
+/* register system state client */
+int ss_subsys_register_client(struct ss_subsys_device *ss_device);
+
+/*
+ * this function is called by HECI layer when there's a message for
+ * system state subsystem
+ */
+void heci_handle_system_state_msg(uint8_t *msg, const size_t length);
+
+#endif /* __SYSTEM_STATE_H */
diff --git a/chip/ish/system_state_subsys.c b/chip/ish/system_state_subsys.c
new file mode 100644
index 0000000000..bb5b62dce3
--- /dev/null
+++ b/chip/ish/system_state_subsys.c
@@ -0,0 +1,149 @@
+/* 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 "heci_client.h"
+#include "system_state.h"
+#include "console.h"
+
+#ifdef SS_SUBSYSTEM_DEBUG
+#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
+
+
+/* the following "define"s and structures are from host driver
+ * and they are slightly modified for look&feel purpose.
+ */
+#define SYSTEM_STATE_SUBSCRIBE 0x1
+#define SYSTEM_STATE_STATUS 0x2
+#define SYSTEM_STATE_QUERY_SUBSCRIBERS 0x3
+#define SYSTEM_STATE_STATE_CHANGE_REQ 0x4
+
+#define SUSPEND_STATE_BIT (1<<1) /* suspend/resume */
+
+struct ss_header {
+ uint32_t cmd;
+ uint32_t cmd_status;
+} __packed;
+
+struct ss_query_subscribers {
+ struct ss_header hdr;
+} __packed;
+
+struct ss_subscribe {
+ struct ss_header hdr;
+ uint32_t states;
+} __packed;
+
+struct ss_status {
+ struct ss_header hdr;
+ uint32_t supported_states;
+ uint32_t states_status;
+} __packed;
+
+/* change request from device but host doesn't support it */
+struct ss_state_change_req {
+ struct ss_header hdr;
+ uint32_t requested_states;
+ uint32_t states_status;
+} __packed;
+
+/*
+ * TODO: For now, every HECI client with valid .suspend or .resume callback is
+ * automatically registered as client of system state subsystem.
+ * so MAX_SS_CLIENTS should be HECI_MAX_NUM_OF_CLIENTS.
+ * if an object wants to get system state event then it can embeds
+ * "struct ss_subsys_device" in it and calls ss_subsys_register_client() like
+ * HECI client.
+ */
+#define MAX_SS_CLIENTS HECI_MAX_NUM_OF_CLIENTS
+
+struct ss_subsystem_context {
+ uint32_t registered_state;
+
+ int num_of_ss_client;
+ struct ss_subsys_device *clients[MAX_SS_CLIENTS];
+};
+
+static struct ss_subsystem_context ss_subsys_ctx;
+
+int ss_subsys_register_client(struct ss_subsys_device *ss_device)
+{
+ int handle;
+
+ if (ss_subsys_ctx.num_of_ss_client == MAX_SS_CLIENTS)
+ return -1;
+
+ if (ss_device->cbs->resume || ss_device->cbs->suspend) {
+ handle = ss_subsys_ctx.num_of_ss_client++;
+ ss_subsys_ctx.registered_state |= SUSPEND_STATE_BIT;
+ ss_subsys_ctx.clients[handle] = ss_device;
+ } else {
+ return -1;
+ }
+
+ return handle;
+}
+
+static int ss_subsys_suspend(void)
+{
+ int i;
+
+ for (i = ss_subsys_ctx.num_of_ss_client - 1; i >= 0; i--) {
+ if (ss_subsys_ctx.clients[i]->cbs->suspend)
+ ss_subsys_ctx.clients[i]->cbs->suspend(
+ ss_subsys_ctx.clients[i]);
+ }
+
+ return EC_SUCCESS;
+}
+
+static int ss_subsys_resume(void)
+{
+ int i;
+
+ for (i = 0; i < ss_subsys_ctx.num_of_ss_client; i++) {
+ if (ss_subsys_ctx.clients[i]->cbs->resume)
+ ss_subsys_ctx.clients[i]->cbs->resume(
+ ss_subsys_ctx.clients[i]);
+ }
+
+ return EC_SUCCESS;
+}
+
+void heci_handle_system_state_msg(uint8_t *msg, const size_t length)
+{
+ struct ss_header *hdr = (struct ss_header *)msg;
+ struct ss_subscribe subscribe;
+ struct ss_status *status;
+
+ switch (hdr->cmd) {
+ case SYSTEM_STATE_QUERY_SUBSCRIBERS:
+ subscribe.hdr.cmd = SYSTEM_STATE_SUBSCRIBE;
+ subscribe.hdr.cmd_status = 0;
+ subscribe.states = ss_subsys_ctx.registered_state;
+
+ heci_send_fixed_client_msg(HECI_FIXED_SYSTEM_STATE_ADDR,
+ (uint8_t *)&subscribe,
+ sizeof(subscribe));
+
+ break;
+ case SYSTEM_STATE_STATUS:
+ status = (struct ss_status *)msg;
+ if (status->supported_states & SUSPEND_STATE_BIT) {
+ if (status->states_status & SUSPEND_STATE_BIT)
+ ss_subsys_suspend();
+ else
+ ss_subsys_resume();
+ }
+
+ break;
+ }
+}