730 lines
19 KiB
C
730 lines
19 KiB
C
/* 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"
|
|
#include "hwtimer.h"
|
|
|
|
#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)
|
|
|
|
/*
|
|
* 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
|
|
|
|
/* 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 BIT(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;
|
|
uint32_t *timestamp_of_outgoing_doorbell;
|
|
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 {
|
|
volatile uint8_t *in_msg_reg;
|
|
volatile uint8_t *out_msg_reg;
|
|
volatile uint32_t *in_drbl_reg;
|
|
volatile 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_BASE,
|
|
.out_msg_reg = IPC_ISH2HOST_MSG_BASE,
|
|
.in_drbl_reg = IPC_HOST2ISH_DOORBELL_ADDR,
|
|
.out_drbl_reg = IPC_ISH2HOST_DOORBELL_ADDR,
|
|
.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)
|
|
{
|
|
IPC_PIMR |= ctx->pimr_2ish_bit;
|
|
}
|
|
|
|
static inline void ipc_disable_pimr_db_interrupt(const struct ipc_if_ctx *ctx)
|
|
{
|
|
IPC_PIMR &= ~ctx->pimr_2ish_bit;
|
|
}
|
|
|
|
static inline void ipc_enable_pimr_clearing_interrupt(
|
|
const struct ipc_if_ctx *ctx)
|
|
{
|
|
IPC_PIMR |= ctx->pimr_2host_clearing_bit;
|
|
}
|
|
|
|
static inline void ipc_disable_pimr_clearing_interrupt(
|
|
const struct ipc_if_ctx *ctx)
|
|
{
|
|
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)
|
|
{
|
|
memcpy((void *)(ctx->out_msg_reg), payload, payload_size);
|
|
*(ctx->out_drbl_reg) = drbl;
|
|
}
|
|
|
|
static int ipc_write_raw_timestamp(struct ipc_if_ctx *ctx, uint32_t drbl,
|
|
const uint8_t *payload, size_t payload_size,
|
|
uint32_t *timestamp)
|
|
{
|
|
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;
|
|
msg->timestamp_of_outgoing_doorbell = timestamp;
|
|
memcpy(msg->payload, payload, payload_size);
|
|
queue_advance_tail(q, 1);
|
|
} else {
|
|
CPRINTS("tx queue is full");
|
|
res = -IPC_ERR_TX_QUEUE_FULL;
|
|
}
|
|
|
|
ipc_enable_pimr_clearing_interrupt(ctx);
|
|
goto write_unlock;
|
|
}
|
|
ctx->is_tx_ipc_busy = 1;
|
|
ipc_enable_pimr_clearing_interrupt(ctx);
|
|
|
|
write_payload_and_ring_drbl(ctx, drbl, payload, payload_size);
|
|
|
|
/* We wrote inline, take timestamp now */
|
|
if (timestamp)
|
|
*timestamp = __hw_clock_source_read();
|
|
|
|
write_unlock:
|
|
mutex_unlock(&ctx->write_lock);
|
|
return res;
|
|
}
|
|
|
|
static int ipc_write_raw(struct ipc_if_ctx *ctx, uint32_t drbl,
|
|
const uint8_t *payload, size_t payload_size)
|
|
{
|
|
return ipc_write_raw_timestamp(ctx, drbl, payload, payload_size, NULL);
|
|
}
|
|
|
|
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 = *(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");
|
|
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", protocol);
|
|
break;
|
|
}
|
|
|
|
if (len > buf_size) {
|
|
CPRINTS("buffer is smaller than payload");
|
|
return -IPC_ERR_TOO_SMALL_BUFFER;
|
|
}
|
|
|
|
if (IS_ENABLED(IPC_HECI_DEBUG))
|
|
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;
|
|
}
|
|
|
|
if (src && dest)
|
|
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 = *(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) : %d", protocol, invalid_msg);
|
|
|
|
*(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);
|
|
|
|
/*
|
|
* Resetting interrupt status bit should be done
|
|
* before sending an item in tx_queue.
|
|
*/
|
|
IPC_BUSY_CLEAR = ctx->clr_busy_bit;
|
|
|
|
/*
|
|
* 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));
|
|
if (msg->timestamp_of_outgoing_doorbell)
|
|
*msg->timestamp_of_outgoing_doorbell =
|
|
__hw_clock_source_read();
|
|
|
|
queue_advance_head(q, 1);
|
|
} else {
|
|
ctx->is_tx_ipc_busy = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 = IPC_PISR;
|
|
uint32_t pimr = IPC_PIMR;
|
|
|
|
/*
|
|
* Ensure that the host IPC write power is requested after getting an
|
|
* interrupt otherwise the resume message will never get delivered (via
|
|
* host ipc communication). Resume is where we would like to restore all
|
|
* power settings, but that is too late for this power request.
|
|
*/
|
|
if (IS_ENABLED(CHIP_FAMILY_ISH5))
|
|
PMU_VNN_REQ = VNN_REQ_IPC_HOST_WRITE & ~PMU_VNN_REQ;
|
|
|
|
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 = IPC_BUSY_CLEAR;
|
|
uint32_t pimr = 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_timestamp(const ipc_handle_t handle, const void *buf,
|
|
const size_t buf_size, uint32_t *timestamp)
|
|
{
|
|
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");
|
|
return -EC_ERROR_INVAL;
|
|
}
|
|
|
|
if (!ctx->msg_events[protocol].enabled) {
|
|
CPRINTS("call open_ipc() for the protocol first");
|
|
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_timestamp(ctx, drbl, payload, payload_size,
|
|
timestamp);
|
|
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", 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:
|
|
/* Not supported currently, but kernel sends this about ~20s */
|
|
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);
|
|
|
|
*(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 = *(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_BIT(0)
|
|
|
|
/*
|
|
* This task handles MNG messages
|
|
*/
|
|
void ipc_mng_task(void)
|
|
{
|
|
int payload_size;
|
|
struct ipc_msg msg;
|
|
ipc_handle_t handle;
|
|
|
|
/*
|
|
* Ensure that power for host IPC writes is requested and ack'ed
|
|
*/
|
|
if (IS_ENABLED(CHIP_FAMILY_ISH5)) {
|
|
PMU_VNN_REQ = VNN_REQ_IPC_HOST_WRITE & ~PMU_VNN_REQ;
|
|
while (!(PMU_VNN_REQ_ACK & PMU_VNN_REQ_ACK_STATUS))
|
|
continue;
|
|
}
|
|
|
|
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");
|
|
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);
|
|
}
|
|
|
|
/* inform host firmware is running */
|
|
ish_fwst_set_fw_status(FWSTS_FW_IS_RUNNING);
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, ipc_init, HOOK_PRIO_DEFAULT);
|