diff options
author | Hyungwoo Yang <hyungwoo.yang@intel.com> | 2018-10-12 17:17:21 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-12-27 13:26:58 -0800 |
commit | 737317a19e54bb4dfa4d646b11354b4a9d275791 (patch) | |
tree | e6c2f3900527c61c51b7702485b70e0136307cde | |
parent | db9a02ec4110769b7300f45ade770205e70934de (diff) | |
download | chrome-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.mk | 1 | ||||
-rw-r--r-- | chip/ish/ipc_heci.c | 729 | ||||
-rw-r--r-- | chip/ish/ipc_heci.h | 83 | ||||
-rw-r--r-- | chip/ish/ish_fwst.h | 189 | ||||
-rw-r--r-- | include/config.h | 7 |
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 |