summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHyungwoo Yang <hyungwoo.yang@intel.com>2018-10-12 17:17:21 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-12-27 13:26:58 -0800
commit737317a19e54bb4dfa4d646b11354b4a9d275791 (patch)
treee6c2f3900527c61c51b7702485b70e0136307cde
parentdb9a02ec4110769b7300f45ade770205e70934de (diff)
downloadchrome-ec-737317a19e54bb4dfa4d646b11354b4a9d275791.tar.gz
ISH: IPC: implement generic IPC layer
Introduce new IPC API supporting MNG and HECI protocols. Currently it supports communication with host(x64) BUG=b:79676054 BRANCH=none TEST=Tested on Atlas board. Change-Id: Iea6d1f96c89228b425861d045618d58f9d146f08 Reviewed-on: https://chromium-review.googlesource.com/1279363 Commit-Ready: Hyungwoo Yang <hyungwoo.yang@intel.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/ipc_heci.c729
-rw-r--r--chip/ish/ipc_heci.h83
-rw-r--r--chip/ish/ish_fwst.h189
-rw-r--r--include/config.h7
5 files changed, 1009 insertions, 0 deletions
diff --git a/chip/ish/build.mk b/chip/ish/build.mk
index 467cbb2f67..c8461b3638 100644
--- a/chip/ish/build.mk
+++ b/chip/ish/build.mk
@@ -20,6 +20,7 @@ endif
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_WATCHDOG)+=watchdog.o
# location of the scripts and keys used to pack the SPI flash image
diff --git a/chip/ish/ipc_heci.c b/chip/ish/ipc_heci.c
new file mode 100644
index 0000000000..33e33f1717
--- /dev/null
+++ b/chip/ish/ipc_heci.c
@@ -0,0 +1,729 @@
+/* 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.
+ */
+
+/* IPC module for ISH */
+
+/**
+ * IPC - Inter Processor Communication
+ * -----------------------------------
+ *
+ * IPC is a bi-directional doorbell based message passing interface sans
+ * session and transport layers, between hardware blocks. ISH uses IPC to
+ * communicate with the Host, PMC (Power Management Controller), CSME
+ * (Converged Security and Manageability Engine), Audio, Graphics and ISP.
+ *
+ * Both the initiator and target ends each have a 32-bit doorbell register and
+ * 128-byte message regions. In addition, the following register pairs help in
+ * synchronizing IPC.
+ *
+ * - Peripheral Interrupt Status Register (PISR)
+ * - Peripheral Interrupt Mask Register (PIMR)
+ * - Doorbell Clear Status Register (DB CSR)
+ */
+
+#include "registers.h"
+#include "console.h"
+#include "task.h"
+#include "util.h"
+#include "ipc_heci.h"
+#include "ish_fwst.h"
+#include "queue.h"
+#include "hooks.h"
+
+#ifdef IPC_HECI_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
+
+/*
+ * comminucation protocol is defined in Linux Documentation
+ * <kernel_root>/Documentation/hid/intel-ish-hid.txt
+ */
+
+/* MNG commands */
+/* The ipc_mng_task manages IPC link. It should be the highest priority */
+#define MNG_RX_CMPL_ENABLE 0
+#define MNG_RX_CMPL_DISABLE 1
+#define MNG_RX_CMPL_INDICATION 2
+#define MNG_RESET_NOTIFY 3
+#define MNG_RESET_NOTIFY_ACK 4
+#define MNG_SYNC_FW_CLOCK 5
+#define MNG_ILLEGAL_CMD 0xFF
+
+/* Peripheral Interrupt Satus Register */
+#define IPC_PISR_HOST2ISH_BIT (1<<0)
+#define IPC_PISR_PMC2ISH_BIT (1<<1)
+#define IPC_PISR_CSME2ISH_BIT (1<<2)
+
+/* Peripheral Interrupt Mask Register */
+#define IPC_PIMR_HOST2ISH_BIT (1<<0)
+#define IPC_PIMR_PMC2ISH_BIT (1<<1)
+#define IPC_PIMR_CSME2ISH_BIT (1<<2)
+
+#define IPC_PIMR_ISH2HOST_CLR_BIT (1<<11)
+#define IPC_PIMR_ISH2PMC_CLR_BIT (1<<12)
+#define IPC_PIMR_ISH2CSME_CLR_BIT (1<<13)
+
+/* Peripheral Interrupt DB(DoorBell) Clear Status Register */
+#define IPC_DB_CLR_STS_ISH2HOST_BIT (1<<0)
+#define IPC_DB_CLR_STS_ISH2ISP_BIT (1<<2)
+#define IPC_DB_CLR_STS_ISH2AUDIO_BIT (1<<3)
+#define IPC_DB_CLR_STS_ISH2PMC_BIT (1<<8)
+#define IPC_DB_CLR_STS_ISH2CSME_BIT (1<<16)
+
+/* Doorbell */
+#define IPC_DB_MSG_LENGTH_FIELD 0x3FF
+#define IPC_DB_MSG_LENGTH_SHIFT 0
+#define IPC_DB_MSG_LENGTH_MASK \
+ (IPC_DB_MSG_LENGTH_FIELD << IPC_DB_MSG_LENGTH_SHIFT)
+
+#define IPC_DB_PROTOCOL_FIELD 0x0F
+#define IPC_DB_PROTOCOL_SHIFT 10
+#define IPC_DB_PROTOCOL_MASK (IPC_DB_PROTOCOL_FIELD << IPC_DB_PROTOCOL_SHIFT)
+
+#define IPC_DB_CMD_FIELD 0x0F
+#define IPC_DB_CMD_SHIFT 16
+#define IPC_DB_CMD_MASK (IPC_DB_CMD_FIELD << IPC_DB_CMD_SHIFT)
+
+#define IPC_DB_BUSY_SHIFT 31
+#define IPC_DB_BUSY_MASK (1 << IPC_DB_BUSY_SHIFT)
+
+#define IPC_DB_MSG_LENGTH(drbl) \
+ (((drbl) & IPC_DB_MSG_LENGTH_MASK) >> IPC_DB_MSG_LENGTH_SHIFT)
+#define IPC_DB_PROTOCOL(drbl) \
+ (((drbl) & IPC_DB_PROTOCOL_MASK) >> IPC_DB_PROTOCOL_SHIFT)
+#define IPC_DB_CMD(drbl) \
+ (((drbl) & IPC_DB_CMD_MASK) >> IPC_DB_CMD_SHIFT)
+#define IPC_DB_BUSY(drbl) (!!((drbl) & IPC_DB_BUSY_MASK))
+
+#define IPC_BUILD_DB(length, proto, cmd, busy) \
+ (((busy) << IPC_DB_BUSY_SHIFT) | ((cmd) << IPC_DB_CMD_SHIFT) | \
+ ((proto) << IPC_DB_PROTOCOL_SHIFT) | \
+ ((length) << IPC_DB_MSG_LENGTH_SHIFT))
+
+#define IPC_BUILD_MNG_DB(cmd, length) \
+ IPC_BUILD_DB(length, IPC_PROTOCOL_MNG, cmd, 1)
+
+#define IPC_BUILD_HECI_DB(length) \
+ IPC_BUILD_DB(length, IPC_PROTOCOL_HECI, 0, 1)
+
+#define IPC_MSG_MAX_SIZE 0x80
+#define IPC_HOST_MSG_QUEUE_SIZE 8
+#define IPC_PMC_MSG_QUEUE_SIZE 2
+
+#define IPC_HANDLE_PEER_ID_SHIFT 4
+#define IPC_HANDLE_PROTOCOL_SHIFT 0
+#define IPC_HANDLE_PROTOCOL_MASK 0x0F
+#define IPC_BUILD_HANDLE(peer_id, protocol) \
+ ((ipc_handle_t)(((peer_id) << IPC_HANDLE_PEER_ID_SHIFT) | (protocol)))
+#define IPC_BUILD_MNG_HANDLE(peer_id) \
+ IPC_BUILD_HANDLE((peer_id), IPC_PROTOCOL_MNG)
+#define IPC_BUILD_HOST_MNG_HANDLE() IPC_BUILD_MNG_HANDLE(IPC_PEER_ID_HOST)
+#define IPC_HANDLE_PEER_ID(handle) \
+ ((uint32_t)(handle) >> IPC_HANDLE_PEER_ID_SHIFT)
+#define IPC_HANDLE_PROTOCOL(handle) \
+ ((uint32_t)(handle) & IPC_HANDLE_PROTOCOL_MASK)
+#define IPC_IS_VALID_HANDLE(handle) \
+ (IPC_HANDLE_PEER_ID(handle) < IPC_PEERS_COUNT && \
+ IPC_HANDLE_PROTOCOL(handle) < IPC_PROTOCOL_COUNT)
+
+struct ipc_msg {
+ uint32_t drbl;
+ uint8_t payload[IPC_MSG_MAX_SIZE];
+} __packed;
+
+struct ipc_rst_payload {
+ uint16_t reset_id;
+ uint16_t reserved;
+};
+
+struct ipc_oob_msg {
+ uint32_t address;
+ uint32_t length;
+};
+
+struct ipc_msg_event {
+ task_id_t task_id;
+ uint32_t event;
+ uint8_t enabled;
+};
+
+/*
+ * IPC interface context
+ * This is per-IPC context.
+ */
+struct ipc_if_ctx {
+ uint32_t in_msg_reg;
+ uint32_t out_msg_reg;
+ uint32_t in_drbl_reg;
+ uint32_t out_drbl_reg;
+ uint32_t clr_busy_bit;
+ uint32_t pimr_2ish_bit;
+ uint32_t pimr_2host_clearing_bit;
+ uint8_t irq_in;
+ uint8_t irq_clr;
+ uint16_t reset_id;
+ struct ipc_msg_event msg_events[IPC_PROTOCOL_COUNT];
+ struct mutex lock;
+ struct mutex write_lock;
+
+ struct queue tx_queue;
+ uint8_t is_tx_ipc_busy;
+ uint8_t initialized;
+};
+
+/* list of peer contexts */
+static struct ipc_if_ctx ipc_peer_ctxs[IPC_PEERS_COUNT] = {
+ [IPC_PEER_ID_HOST] = {
+ .in_msg_reg = IPC_HOST2ISH_MSG_REGS,
+ .out_msg_reg = IPC_ISH2HOST_MSG_REGS,
+ .in_drbl_reg = IPC_HOST2ISH_DOORBELL,
+ .out_drbl_reg = IPC_ISH2HOST_DOORBELL,
+ .clr_busy_bit = IPC_DB_CLR_STS_ISH2HOST_BIT,
+ .pimr_2ish_bit = IPC_PIMR_HOST2ISH_BIT,
+ .pimr_2host_clearing_bit = IPC_PIMR_ISH2HOST_CLR_BIT,
+ .irq_in = ISH_IPC_HOST2ISH_IRQ,
+ .irq_clr = ISH_IPC_ISH2HOST_CLR_IRQ,
+ .tx_queue = QUEUE_NULL(IPC_HOST_MSG_QUEUE_SIZE, struct ipc_msg),
+ },
+ /* Other peers (PMC, CSME, etc) to be added when required */
+};
+
+static inline struct ipc_if_ctx *ipc_get_if_ctx(const uint32_t peer_id)
+{
+ return &ipc_peer_ctxs[peer_id];
+}
+
+static inline struct ipc_if_ctx *ipc_handle_to_if_ctx(const ipc_handle_t handle)
+{
+ return ipc_get_if_ctx(IPC_HANDLE_PEER_ID(handle));
+}
+
+static inline void ipc_enable_pimr_db_interrupt(const struct ipc_if_ctx *ctx)
+{
+ REG32(IPC_PIMR) |= ctx->pimr_2ish_bit;
+}
+
+static inline void ipc_disable_pimr_db_interrupt(const struct ipc_if_ctx *ctx)
+{
+ REG32(IPC_PIMR) &= ~ctx->pimr_2ish_bit;
+}
+
+static inline void ipc_enable_pimr_clearing_interrupt(
+ const struct ipc_if_ctx *ctx)
+{
+ REG32(IPC_PIMR) |= ctx->pimr_2host_clearing_bit;
+}
+
+static inline void ipc_disable_pimr_clearing_interrupt(
+ const struct ipc_if_ctx *ctx)
+{
+ REG32(IPC_PIMR) &= ~ctx->pimr_2host_clearing_bit;
+}
+
+static void write_payload_and_ring_drbl(const struct ipc_if_ctx *ctx,
+ uint32_t drbl,
+ const uint8_t *payload,
+ size_t payload_size)
+{
+ uint32_t msg_idx = 0;
+
+ /* write in 32-bits unit */
+ while (payload_size >= sizeof(uint32_t)) {
+ REG32(ctx->out_msg_reg + msg_idx) =
+ *(uint32_t *)(payload + msg_idx);
+ msg_idx += sizeof(uint32_t);
+ payload_size -= sizeof(uint32_t);
+ }
+
+ /* write leftovers in 8-bits unit */
+ while (payload_size) {
+ REG8(ctx->out_msg_reg + msg_idx) =
+ *(uint8_t *)(payload + msg_idx);
+ msg_idx++;
+ payload_size--;
+ }
+
+ REG32(ctx->out_drbl_reg) = drbl;
+}
+
+
+static int ipc_write_raw(struct ipc_if_ctx *ctx, uint32_t drbl,
+ const uint8_t *payload, size_t payload_size)
+{
+ struct queue *q = &ctx->tx_queue;
+ struct ipc_msg *msg;
+ size_t tail, space;
+ int res = 0;
+
+ mutex_lock(&ctx->write_lock);
+
+ ipc_disable_pimr_clearing_interrupt(ctx);
+ if (ctx->is_tx_ipc_busy) {
+ space = queue_space(q);
+ if (space) {
+ tail = q->state->tail & (q->buffer_units - 1);
+ msg = (struct ipc_msg *)q->buffer + tail;
+ msg->drbl = drbl;
+ memcpy(msg->payload, payload, payload_size);
+ queue_advance_tail(q, 1);
+ } else {
+ CPRINTS("tx queue is full\n");
+ res = -IPC_ERR_TX_QUEUE_FULL;
+ }
+
+ ipc_enable_pimr_clearing_interrupt(ctx);
+ goto write_unlock;
+ }
+ ipc_enable_pimr_clearing_interrupt(ctx);
+
+ ctx->is_tx_ipc_busy = 1;
+ write_payload_and_ring_drbl(ctx, drbl, payload, payload_size);
+
+write_unlock:
+ mutex_unlock(&ctx->write_lock);
+ return res;
+}
+
+static int ipc_send_reset_notify(const ipc_handle_t handle)
+{
+ struct ipc_rst_payload *ipc_rst;
+ struct ipc_if_ctx *ctx;
+ struct ipc_msg msg;
+
+ ctx = ipc_handle_to_if_ctx(handle);
+ ctx->reset_id = (uint16_t)ish_fwst_get_reset_id();
+ ipc_rst = (struct ipc_rst_payload *)msg.payload;
+ ipc_rst->reset_id = ctx->reset_id;
+
+ msg.drbl = IPC_BUILD_MNG_DB(MNG_RESET_NOTIFY, sizeof(*ipc_rst));
+ ipc_write_raw(ctx, msg.drbl, msg.payload, IPC_DB_MSG_LENGTH(msg.drbl));
+
+ return 0;
+}
+
+static int ipc_send_cmpl_indication(struct ipc_if_ctx *ctx)
+{
+ struct ipc_msg msg;
+
+ msg.drbl = IPC_BUILD_MNG_DB(MNG_RX_CMPL_INDICATION, 0);
+ ipc_write_raw(ctx, msg.drbl, msg.payload, IPC_DB_MSG_LENGTH(msg.drbl));
+
+ return 0;
+}
+
+static int ipc_get_protocol_data(const struct ipc_if_ctx *ctx,
+ const uint32_t protocol,
+ uint8_t *buf, const size_t buf_size)
+{
+ int len = 0, payload_size;
+ uint8_t *src = NULL, *dest = NULL;
+ struct ipc_msg *msg;
+ uint32_t drbl_val;
+
+ drbl_val = REG32(ctx->in_drbl_reg);
+ payload_size = IPC_DB_MSG_LENGTH(drbl_val);
+
+ if (payload_size > IPC_MAX_PAYLOAD_SIZE) {
+ CPRINTS("invalid msg : payload is too big\n");
+ return -IPC_ERR_INVALID_MSG;
+ }
+
+ switch (protocol) {
+ case IPC_PROTOCOL_HECI:
+ /* copy only payload which is a heci packet */
+ len = payload_size;
+ break;
+ case IPC_PROTOCOL_MNG:
+ /* copy including doorbell which forms a ipc packet */
+ len = payload_size + sizeof(drbl_val);
+ break;
+ default:
+ CPRINTS("protocol %d not supported yet\n", protocol);
+ break;
+ }
+
+ if (len > buf_size) {
+ CPRINTS("buffer is smaller than payload\n");
+ return -IPC_ERR_TOO_SMALL_BUFFER;
+ }
+
+ CPRINTF("ipc p=%d, db=0x%0x, payload_size=%d\n", protocol, drbl_val,
+ IPC_DB_MSG_LENGTH(drbl_val));
+
+ switch (protocol) {
+ case IPC_PROTOCOL_HECI:
+ src = (uint8_t *)ctx->in_msg_reg;
+ dest = buf;
+ break;
+ case IPC_PROTOCOL_MNG:
+ src = (uint8_t *)ctx->in_msg_reg;
+
+ msg = (struct ipc_msg *)buf;
+ msg->drbl = drbl_val;
+ dest = msg->payload;
+ break;
+ default :
+ break;
+ }
+
+ memcpy(dest, src, payload_size);
+
+ return len;
+}
+
+static void set_pimr_and_send_rx_complete(struct ipc_if_ctx *ctx)
+{
+ ipc_enable_pimr_db_interrupt(ctx);
+ ipc_send_cmpl_indication(ctx);
+}
+
+static void handle_msg_recv_interrupt(const uint32_t peer_id)
+{
+ struct ipc_if_ctx *ctx;
+ uint32_t drbl_val, payload_size, protocol, invalid_msg = 0;
+
+ ctx = ipc_get_if_ctx(peer_id);
+ ipc_disable_pimr_db_interrupt(ctx);
+
+ drbl_val = REG32(ctx->in_drbl_reg);
+ protocol = IPC_DB_PROTOCOL(drbl_val);
+ payload_size = IPC_DB_MSG_LENGTH(drbl_val);
+
+ if (payload_size > IPC_MSG_MAX_SIZE)
+ invalid_msg = 1;
+
+ if (!ctx->msg_events[protocol].enabled)
+ invalid_msg = 2;
+
+ if (!invalid_msg) {
+ /* send event to task */
+ task_set_event(ctx->msg_events[protocol].task_id,
+ ctx->msg_events[protocol].event, 0);
+ } else {
+ CPRINTS("discard msg : %d\n", invalid_msg);
+
+ REG32(ctx->in_drbl_reg) = 0;
+ set_pimr_and_send_rx_complete(ctx);
+ }
+}
+
+static void handle_busy_clear_interrupt(const uint32_t peer_id)
+{
+ struct ipc_if_ctx *ctx;
+ struct ipc_msg *msg;
+ struct queue *q;
+ size_t head;
+
+ ctx = ipc_get_if_ctx(peer_id);
+ /*
+ * No need to use sync mechanism here since the accesing the queue
+ * happens only when either this IRQ is disabled or
+ * in ISR context(here) of this IRQ.
+ */
+ if (!queue_is_empty(&ctx->tx_queue)) {
+ q = &ctx->tx_queue;
+ head = q->state->head & (q->buffer_units - 1);
+ msg = (struct ipc_msg *)(q->buffer + head * q->unit_bytes);
+ write_payload_and_ring_drbl(ctx, msg->drbl, msg->payload,
+ IPC_DB_MSG_LENGTH(msg->drbl));
+ queue_advance_head(q, 1);
+ } else {
+ ctx->is_tx_ipc_busy = 0;
+ }
+
+ REG32(IPC_BUSY_CLEAR) = ctx->clr_busy_bit;
+}
+
+/**
+ * IPC interrupts are received by the FW when a) Host SW rings doorbell and
+ * b) when Host SW clears doorbell busy bit [31].
+ *
+ * Doorbell Register (DB) bits
+ * ----+-------+--------+-----------+--------+------------+--------------------
+ * 31 | 30 29 | 28-20 |19 18 17 16| 15 14 | 13 12 11 10| 9 8 7 6 5 4 3 2 1 0
+ * ----+-------+--------+-----------+--------+------------+--------------------
+ * Busy|Options|Reserved| Command |Reserved| Protocol | Message Length
+ * ----+-------+--------+-----------+--------+------------+--------------------
+ *
+ * ISH Peripheral Interrupt Status Register:
+ * Bit 0 - If set, indicates interrupt was caused by setting Host2ISH DB
+ *
+ * ISH Peripheral Interrupt Mask Register
+ * Bit 0 - If set, mask interrupt caused by Host2ISH DB
+ *
+ * ISH Peripheral DB Clear Status Register
+ * Bit 0 - If set, indicates interrupt was caused by clearing Host2ISH DB
+ */
+static void ipc_host2ish_isr(void)
+{
+ uint32_t pisr = REG32(IPC_PISR);
+ uint32_t pimr = REG32(IPC_PIMR);
+
+ if ((pisr & IPC_PISR_HOST2ISH_BIT) && (pimr & IPC_PIMR_HOST2ISH_BIT))
+ handle_msg_recv_interrupt(IPC_PEER_ID_HOST);
+}
+DECLARE_IRQ(ISH_IPC_HOST2ISH_IRQ, ipc_host2ish_isr);
+
+static void ipc_host2ish_busy_clear_isr(void)
+{
+ uint32_t busy_clear = REG32(IPC_BUSY_CLEAR);
+ uint32_t pimr = REG32(IPC_PIMR);
+
+ if ((busy_clear & IPC_DB_CLR_STS_ISH2HOST_BIT) &&
+ (pimr & IPC_PIMR_ISH2HOST_CLR_BIT))
+ handle_busy_clear_interrupt(IPC_PEER_ID_HOST);
+}
+DECLARE_IRQ(ISH_IPC_ISH2HOST_CLR_IRQ, ipc_host2ish_busy_clear_isr);
+
+int ipc_write(const ipc_handle_t handle, const void *buf, const size_t buf_size)
+{
+ int ret;
+ struct ipc_if_ctx *ctx;
+ uint32_t drbl = 0;
+ const uint8_t *payload = NULL;
+ int payload_size;
+ uint32_t protocol;
+
+ if (!IPC_IS_VALID_HANDLE(handle))
+ return -EC_ERROR_INVAL;
+
+ protocol = IPC_HANDLE_PROTOCOL(handle);
+ ctx = ipc_handle_to_if_ctx(handle);
+
+ if (ctx->initialized == 0) {
+ CPRINTS("open_ipc() for the peer is never called\n");
+ return -EC_ERROR_INVAL;
+ }
+
+ if (!ctx->msg_events[protocol].enabled) {
+ CPRINTS("call open_ipc() for the protocol first\n");
+ return -EC_ERROR_INVAL;
+ }
+
+ switch (protocol) {
+ case IPC_PROTOCOL_BOOT:
+ break;
+ case IPC_PROTOCOL_HECI:
+ drbl = IPC_BUILD_HECI_DB(buf_size);
+ payload = buf;
+ break;
+ case IPC_PROTOCOL_MCTP:
+ break;
+ case IPC_PROTOCOL_MNG:
+ drbl = ((struct ipc_msg *)buf)->drbl;
+ payload = ((struct ipc_msg *)buf)->payload;
+ break;
+ case IPC_PROTOCOL_ECP:
+ /* TODO : EC protocol */
+ break;
+ }
+
+ payload_size = IPC_DB_MSG_LENGTH(drbl);
+ if (payload_size > IPC_MSG_MAX_SIZE) {
+ /* too much input */
+ return -EC_ERROR_OVERFLOW;
+ }
+
+ ret = ipc_write_raw(ctx, drbl, payload, payload_size);
+ if (ret)
+ return ret;
+
+ return buf_size;
+}
+
+ipc_handle_t ipc_open(const enum ipc_peer_id peer_id,
+ const enum ipc_protocol protocol,
+ const uint32_t event)
+{
+ struct ipc_if_ctx *ctx;
+
+ if (protocol >= IPC_PROTOCOL_COUNT ||
+ peer_id >= IPC_PEERS_COUNT)
+ return IPC_INVALID_HANDLE;
+
+ ctx = ipc_get_if_ctx(peer_id);
+ mutex_lock(&ctx->lock);
+ if (ctx->msg_events[protocol].enabled) {
+ mutex_unlock(&ctx->lock);
+ return IPC_INVALID_HANDLE;
+ }
+
+ ctx->msg_events[protocol].task_id = task_get_current();
+ ctx->msg_events[protocol].enabled = 1;
+ ctx->msg_events[protocol].event = event;
+
+ /* For HECI protocol, set HECI UP status when IPC link is ready */
+ if (peer_id == IPC_PEER_ID_HOST &&
+ protocol == IPC_PROTOCOL_HECI && ish_fwst_is_ilup_set())
+ ish_fwst_set_hup();
+
+ if (ctx->initialized == 0) {
+ task_enable_irq(ctx->irq_in);
+ task_enable_irq(ctx->irq_clr);
+
+ ipc_enable_pimr_db_interrupt(ctx);
+ ipc_enable_pimr_clearing_interrupt(ctx);
+
+ ctx->initialized = 1;
+ }
+ mutex_unlock(&ctx->lock);
+
+ return IPC_BUILD_HANDLE(peer_id, protocol);
+}
+
+static void handle_mng_commands(const ipc_handle_t handle,
+ const struct ipc_msg *msg)
+{
+ struct ipc_rst_payload *ipc_rst;
+ struct ipc_if_ctx *ctx;
+ uint32_t peer_id = IPC_HANDLE_PEER_ID(handle);
+
+ ctx = ipc_handle_to_if_ctx(handle);
+
+ switch (IPC_DB_CMD(msg->drbl)) {
+ case MNG_RX_CMPL_ENABLE:
+ case MNG_RX_CMPL_DISABLE:
+ case MNG_RX_CMPL_INDICATION:
+ case MNG_RESET_NOTIFY:
+ CPRINTS("msg not handled %d\n", IPC_DB_CMD(msg->drbl));
+ break;
+ case MNG_RESET_NOTIFY_ACK:
+ ipc_rst = (struct ipc_rst_payload *)msg->payload;
+ if (peer_id == IPC_PEER_ID_HOST &&
+ ipc_rst->reset_id == ctx->reset_id) {
+ ish_fwst_set_ilup();
+ if (ctx->msg_events[IPC_PROTOCOL_HECI].enabled)
+ ish_fwst_set_hup();
+ }
+
+ break;
+ case MNG_SYNC_FW_CLOCK:
+ /* TODO: if there's data to host requires timestamp
+ * we need to implement this
+ */
+ CPRINTS("sync fw clock");
+ break;
+ }
+}
+
+static int do_ipc_read(struct ipc_if_ctx *ctx, const uint32_t protocol,
+ uint8_t *buf, const size_t buf_size)
+{
+ int len;
+
+ len = ipc_get_protocol_data(ctx, protocol, buf, buf_size);
+
+ REG32(ctx->in_drbl_reg) = 0;
+ set_pimr_and_send_rx_complete(ctx);
+
+ return len;
+}
+
+static int ipc_check_read_validity(const struct ipc_if_ctx *ctx,
+ const uint32_t protocol)
+{
+ if (ctx->initialized == 0)
+ return -EC_ERROR_INVAL;
+
+ if (!ctx->msg_events[protocol].enabled)
+ return -EC_ERROR_INVAL;
+
+ /* ipc_read() should be called by the same task called ipc_open() */
+ if (ctx->msg_events[protocol].task_id != task_get_current())
+ return -IPC_ERR_INVALID_TASK;
+
+ return 0;
+}
+
+/*
+ * ipc_read should be called by the same task context which called ipc_open()
+ */
+int ipc_read(const ipc_handle_t handle, void *buf, const size_t buf_size,
+ int timeout_us)
+{
+ struct ipc_if_ctx *ctx;
+ uint32_t events, protocol, drbl_protocol, drbl_val;
+ int ret;
+
+ if (!IPC_IS_VALID_HANDLE(handle))
+ return -EC_ERROR_INVAL;
+
+ protocol = IPC_HANDLE_PROTOCOL(handle);
+ ctx = ipc_handle_to_if_ctx(handle);
+
+ ret = ipc_check_read_validity(ctx, protocol);
+ if (ret)
+ return ret;
+
+ if (timeout_us) {
+ events = task_wait_event_mask(ctx->msg_events[protocol].event,
+ timeout_us);
+
+ if (events & TASK_EVENT_TIMER)
+ return -EC_ERROR_TIMEOUT;
+
+ if (!(events & ctx->msg_events[protocol].event))
+ return -EC_ERROR_UNKNOWN;
+ } else {
+ /* check if msg for the protocol is available */
+ drbl_val = REG32(ctx->in_drbl_reg);
+ drbl_protocol = IPC_DB_PROTOCOL(drbl_val);
+ if (!(protocol == drbl_protocol) || !IPC_DB_BUSY(drbl_val))
+ return -IPC_ERR_MSG_NOT_AVAILABLE;
+ }
+
+ return do_ipc_read(ctx, protocol, buf, buf_size);
+}
+
+/* event flag for MNG msg */
+#define EVENT_FLAG_BIT_MNG_MSG TASK_EVENT_CUSTOM(1)
+
+/*
+ * This task handles MNG messages
+ */
+void ipc_mng_task(void)
+{
+ int payload_size;
+ struct ipc_msg msg;
+ ipc_handle_t handle;
+
+ handle = ipc_open(IPC_PEER_ID_HOST, IPC_PROTOCOL_MNG,
+ EVENT_FLAG_BIT_MNG_MSG);
+
+ ASSERT(handle != IPC_INVALID_HANDLE);
+
+ ipc_send_reset_notify(handle);
+
+ while (1) {
+ payload_size = ipc_read(handle, &msg, sizeof(msg), -1);
+
+ /* allow doorbell with any payload */
+ if (payload_size < 0) {
+ CPRINTS("ipc_read error. discard msg\n");
+ continue; /* TODO: retry several and exit */
+ }
+
+ /* handle MNG commands */
+ handle_mng_commands(handle, &msg);
+ }
+}
+
+void ipc_init(void)
+{
+ int i;
+ struct ipc_if_ctx *ctx;
+
+ for (i = 0; i < IPC_PEERS_COUNT; i++) {
+ ctx = ipc_get_if_ctx(i);
+ queue_init(&ctx->tx_queue);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, ipc_init, HOOK_PRIO_DEFAULT);
diff --git a/chip/ish/ipc_heci.h b/chip/ish/ipc_heci.h
new file mode 100644
index 0000000000..e7238df83d
--- /dev/null
+++ b/chip/ish/ipc_heci.h
@@ -0,0 +1,83 @@
+/* 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.
+ */
+
+/* IPC module for ISH */
+#ifndef __IPC_HECI_H
+#define __IPC_HECI_H
+
+enum IPC_ERR {
+ IPC_ERR_IPC_IS_NOT_READY = EC_ERROR_INTERNAL_FIRST + 0,
+ IPC_ERR_TOO_SMALL_BUFFER = EC_ERROR_INTERNAL_FIRST + 1,
+ IPC_ERR_TX_QUEUE_FULL = EC_ERROR_INTERNAL_FIRST + 2,
+ IPC_ERR_INVALID_TASK = EC_ERROR_INTERNAL_FIRST + 3,
+ IPC_ERR_MSG_NOT_AVAILABLE = EC_ERROR_INTERNAL_FIRST + 4,
+ IPC_ERR_INVALID_MSG = EC_ERROR_INTERNAL_FIRST + 5,
+};
+
+enum ipc_peer_id {
+ IPC_PEER_ID_HOST = 0, /* x64 host */
+#if 0 /* other peers are not implemented yet */
+ IPC_PEER_ID_PMC = 1, /* Power Management Controller */
+ IPC_PEER_ID_CSME = 2, /* Converged Security Management Engine */
+ IPC_PEER_ID_CAVS = 3, /* Audio, Voice, and Speech engine */
+ IPC_PEER_ID_ISP = 4, /* Image Signal Processor */
+#endif
+ IPC_PEERS_COUNT,
+};
+/*
+ * Currently ipc handle encoding only allows maximum 16 peers which is
+ * enough for ISH3, ISH4, and ISH5. They have 5 peers.
+ */
+BUILD_ASSERT(IPC_PEERS_COUNT <= 0x0F);
+
+enum ipc_protocol {
+ IPC_PROTOCOL_BOOT = 0, /* Not supported */
+ IPC_PROTOCOL_HECI, /* Host Embedded Controller Interface */
+ IPC_PROTOCOL_MCTP, /* not supported */
+ IPC_PROTOCOL_MNG, /* Management protocol */
+ IPC_PROTOCOL_ECP, /* EC Protocol. not supported */
+ IPC_PROTOCOL_COUNT
+};
+/*
+ * IPC handle enconding only supports 16 protocols which is the
+ * maximum protocols supported by IPC doorbell encoding.
+ */
+BUILD_ASSERT(IPC_PROTOCOL_COUNT <= 0x0F);
+
+typedef void * ipc_handle_t;
+
+#define IPC_MAX_PAYLOAD_SIZE 128
+#define IPC_INVALID_HANDLE NULL
+
+/*
+ * Open ipc channel
+ *
+ * @param peer_id select peer to communicate.
+ * @param protocol select protocol
+ * @param event set event flag
+ *
+ * @return ipc handle or IPC_INVALID_HANDLE if there's error
+ */
+ipc_handle_t ipc_open(const enum ipc_peer_id peer_id,
+ const enum ipc_protocol protocol,
+ const uint32_t event);
+void ipc_close(const ipc_handle_t handle);
+
+/*
+ * Read message from ipc channel.
+ * The function should be call by the same task called ipc_open().
+ * The function waits until message is available.
+ * @param timeout_us if == -1, wait until message is available.
+ * if == 0, return immediately.
+ * if > 0, wait for the specified microsecond duration time
+ */
+int ipc_read(const ipc_handle_t handle, void *buf, const size_t buf_size,
+ int timeout_us);
+
+/* Write message to ipc channel. */
+int ipc_write(const ipc_handle_t handle, const void *buf,
+ const size_t buf_size);
+
+#endif /* __IPC_HECI_H */
diff --git a/chip/ish/ish_fwst.h b/chip/ish/ish_fwst.h
new file mode 100644
index 0000000000..c05caaefcb
--- /dev/null
+++ b/chip/ish/ish_fwst.h
@@ -0,0 +1,189 @@
+/* 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.
+ */
+/*
+ * ISH Firmware status register contains currnet ISH FW status.
+ * Communication protocol for Host(x64), CSME, and PMC uses this register.
+ */
+
+#ifndef __ISH_FWST_H
+#define __ISH_FWST_H
+
+#include "common.h"
+#include "registers.h"
+
+/*
+ * IPC link is up(ready)
+ * IPC can be used by other protocols
+ */
+#define IPC_ISH_FWSTS_ILUP_FIELD 0x01
+#define IPC_ISH_FWSTS_ILUP_SHIFT 0
+#define IPC_ISH_FWSTS_ILUP_MASK \
+ (IPC_ISH_FWSTS_ILUP_FIELD << IPC_ISH_FWSTS_ILUP_SHIFT)
+
+/*
+ * HECI layer is up(ready)
+ */
+#define IPC_ISH_FWSTS_HUP_FIELD 0x01
+#define IPC_ISH_FWSTS_HUP_SHIFT 1
+#define IPC_ISH_FWSTS_HUP_MASK \
+ (IPC_ISH_FWSTS_HUP_FIELD << IPC_ISH_FWSTS_HUP_SHIFT)
+
+/*
+ * ISH FW reason reason
+ */
+#define IPC_ISH_FWSTS_FAIL_REASON_FIELD 0x0F
+#define IPC_ISH_FWSTS_FAIL_REASON_SHIFT 2
+#define IPC_ISH_FWSTS_FAIL_REASON_MASK \
+ (IPC_ISH_FWSTS_FAIL_REASON_FIELD << IPC_ISH_FWSTS_FAIL_REASON_SHIFT)
+
+/*
+ * ISH FW reset ID
+ */
+#define IPC_ISH_FWSTS_RESET_ID_FIELD 0x0F
+#define IPC_ISH_FWSTS_RESET_ID_SHIFT 8
+#define IPC_ISH_FWSTS_RESET_ID_MASK \
+ (IPC_ISH_FWSTS_RESET_ID_FIELD << IPC_ISH_FWSTS_RESET_ID_SHIFT)
+
+/*
+ * ISH FW status type
+ */
+enum {
+ FWSTS_AFTER_RESET = 0,
+ FWSTS_WAIT_FOR_HOST = 4,
+ FWSTS_START_KERNEL_DMA = 5,
+ FWSTS_FW_IS_RUNNING = 7,
+ FWSTS_SENSOR_APP_LOADED = 8,
+ FWSTS_SENSOR_APP_RUNNING = 15
+};
+
+/*
+ * General ISH FW status
+ */
+#define IPC_ISH_FWSTS_FW_STATUS_FIELD 0x0F
+#define IPC_ISH_FWSTS_FW_STATUS_SHIFT 12
+#define IPC_ISH_FWSTS_FW_STATUS_MASK \
+ (IPC_ISH_FWSTS_FW_STATUS_FIELD << IPC_ISH_FWSTS_FW_STATUS_SHIFT)
+
+#define IPC_ISH_FWSTS_DMA0_IN_USE_FIELD 0x01
+#define IPC_ISH_FWSTS_DMA0_IN_USE_SHIFT 16
+#define IPC_ISH_FWSTS_DMA0_IN_USE_MASK \
+ (IPC_ISH_FWSTS_DMA0_IN_USE_FIELD << IPC_ISH_FWSTS_DMA0_IN_USE_SHIFT)
+
+#define IPC_ISH_FWSTS_DMA1_IN_USE_FIELD 0x01
+#define IPC_ISH_FWSTS_DMA1_IN_USE_SHIFT 17
+#define IPC_ISH_FWSTS_DMA1_IN_USE_MASK \
+ (IPC_ISH_FWSTS_DMA1_IN_USE_FIELD << IPC_ISH_FWSTS_DMA1_IN_USE_SHIFT)
+
+#define IPC_ISH_FWSTS_DMA2_IN_USE_FIELD 0x01
+#define IPC_ISH_FWSTS_DMA2_IN_USE_SHIFT 18
+#define IPC_ISH_FWSTS_DMA2_IN_USE_MASK \
+ (IPC_ISH_FWSTS_DMA2_IN_USE_FIELD << IPC_ISH_FWSTS_DMA2_IN_USE_SHIFT)
+
+#define IPC_ISH_FWSTS_DMA3_IN_USE_FIELD 0x01
+#define IPC_ISH_FWSTS_DMA3_IN_USE_SHIFT 19
+#define IPC_ISH_FWSTS_DMA3_IN_USE_MASK \
+ (IPC_ISH_FWSTS_DMA3_IN_USE_FIELD << IPC_ISH_FWSTS_DMA3_IN_USE_SHIFT)
+
+#define IPC_ISH_FWSTS_POWER_STATE_FIELD 0x0F
+#define IPC_ISH_FWSTS_POWER_STATE_SHIFT 20
+#define IPC_ISH_FWSTS_POWER_STATE_MASK \
+ (IPC_ISH_FWSTS_POWER_STATE_FIELD << IPC_ISH_FWSTS_POWER_STATE_SHIFT)
+
+#define IPC_ISH_FWSTS_AON_CHECK_FIELD 0x07
+#define IPC_ISH_FWSTS_AON_CHECK_SHIFT 24
+#define IPC_ISH_FWSTS_AON_CHECK_MASK \
+ (IPC_ISH_FWSTS_AON_CHECK_FIELD << IPC_ISH_FWSTS_AON_CHECK_SHIFT)
+
+/* get ISH FW status register */
+static inline uint32_t ish_fwst_get(void)
+{
+ return REG32(IPC_ISH_FWSTS);
+}
+
+/* set IPC link up */
+static inline void ish_fwst_set_ilup(void)
+{
+ REG32(IPC_ISH_FWSTS) |= (1<<IPC_ISH_FWSTS_ILUP_SHIFT);
+}
+
+/* clear IPC link up */
+static inline void ish_fwst_clear_ilup(void)
+{
+ REG32(IPC_ISH_FWSTS) &= ~IPC_ISH_FWSTS_ILUP_MASK;
+}
+
+/* return IPC link up state */
+static inline int ish_fwst_is_ilup_set(void)
+{
+ return !!(REG32(IPC_ISH_FWSTS) &= IPC_ISH_FWSTS_ILUP_MASK);
+}
+
+/* set HECI up */
+static inline void ish_fwst_set_hup(void)
+{
+ REG32(IPC_ISH_FWSTS) |= (1<<IPC_ISH_FWSTS_HUP_SHIFT);
+}
+
+/* clear HECI up */
+static inline void ish_fwst_clear_hup(void)
+{
+ REG32(IPC_ISH_FWSTS) &= ~IPC_ISH_FWSTS_HUP_MASK;
+}
+
+/* get HECI up status */
+static inline int ish_fwst_is_hup_set(void)
+{
+ return !!(REG32(IPC_ISH_FWSTS) &= IPC_ISH_FWSTS_HUP_MASK);
+}
+
+/* set fw failure reason */
+static inline void ish_fwst_set_fail_reason(uint32_t val)
+{
+ uint32_t fwst = REG32(IPC_ISH_FWSTS);
+
+ REG32(IPC_ISH_FWSTS) = (fwst & ~IPC_ISH_FWSTS_FAIL_REASON_MASK) |
+ (val << IPC_ISH_FWSTS_FAIL_REASON_SHIFT);
+}
+
+/* get fw failure reason */
+static inline uint32_t ish_fwst_get_fail_reason(void)
+{
+ return (REG32(IPC_ISH_FWSTS) & IPC_ISH_FWSTS_FAIL_REASON_MASK)
+ >> IPC_ISH_FWSTS_FAIL_REASON_SHIFT;
+}
+
+/* set reset id */
+static inline void ish_fwst_set_reset_id(uint32_t val)
+{
+ uint32_t fwst = REG32(IPC_ISH_FWSTS);
+
+ REG32(IPC_ISH_FWSTS) = (fwst & ~IPC_ISH_FWSTS_RESET_ID_MASK) |
+ (val << IPC_ISH_FWSTS_RESET_ID_SHIFT);
+}
+
+/* get reset id */
+static inline uint32_t ish_fwst_get_reset_id(void)
+{
+ return (REG32(IPC_ISH_FWSTS) & IPC_ISH_FWSTS_RESET_ID_MASK)
+ >> IPC_ISH_FWSTS_RESET_ID_SHIFT;
+}
+
+/* set general fw status */
+static inline void ish_fwst_set_fw_status(uint32_t val)
+{
+ uint32_t fwst = REG32(IPC_ISH_FWSTS);
+
+ REG32(IPC_ISH_FWSTS) = (fwst & ~IPC_ISH_FWSTS_FW_STATUS_MASK) |
+ (val << IPC_ISH_FWSTS_FW_STATUS_SHIFT);
+}
+
+/* get general fw status */
+static inline uint32_t ish_fwst_get_fw_status(void)
+{
+ return (REG32(IPC_ISH_FWSTS) & IPC_ISH_FWSTS_FW_STATUS_MASK)
+ >> IPC_ISH_FWSTS_FW_STATUS_SHIFT;
+}
+
+#endif /* __ISH_FWST_H */
diff --git a/include/config.h b/include/config.h
index 48f642fe59..39f7048ca9 100644
--- a/include/config.h
+++ b/include/config.h
@@ -2334,6 +2334,13 @@
#undef CONFIG_LOW_POWER_S0
/*
+ * Enable inter-processor communication between ISH(Intel Sensor Hub) and
+ * other modules in Intel SoC(listed below).
+ * - HOST(x64), CSME, PMC, cAVS, and ISP
+ */
+#undef CONFIG_ISH_IPC
+
+/*
* EC supports x86 host communication with AP. This can either be through LPC
* or eSPI. The CONFIG_HOSTCMD_X86 will get automatically defined if either
* CONFIG_HOSTCMD_LPC or CONFIG_HOSTCMD_ESPI are defined. LPC and eSPI are