353 lines
9.0 KiB
C
353 lines
9.0 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.
|
||
|
*
|
||
|
* Inter-Processor Communication (IPC) and Inter-Processor Interrupt (IPI)
|
||
|
*
|
||
|
* IPC is a communication bridge between AP and SCP. AP/SCP sends an IPC
|
||
|
* interrupt to SCP/AP to inform to collect the commmunication mesesages in the
|
||
|
* shared buffer.
|
||
|
*
|
||
|
* There are 4 IPCs in the current architecture, from IPC0 to IPC3. The
|
||
|
* priority of IPC is proportional to its IPC index. IPC3 has the highest
|
||
|
* priority and IPC0 has the lowest one.
|
||
|
*
|
||
|
* IPC0 may contain zero or more IPIs. Each IPI represents a task or a service,
|
||
|
* e.g. host command, or video encoding. IPIs are recognized by IPI ID, which
|
||
|
* should sync across AP and SCP. Shared buffer should designated which IPI
|
||
|
* ID it talks to.
|
||
|
*
|
||
|
* Currently, we don't have IPC handlers for IPC1, IPC2, and IPC3.
|
||
|
*/
|
||
|
|
||
|
#include "console.h"
|
||
|
#include "hooks.h"
|
||
|
#include "host_command.h"
|
||
|
#include "ipi_chip.h"
|
||
|
#include "mkbp_event.h"
|
||
|
#include "system.h"
|
||
|
#include "task.h"
|
||
|
#include "util.h"
|
||
|
#include "hwtimer.h"
|
||
|
|
||
|
#define CPRINTF(format, args...) cprintf(CC_IPI, format, ##args)
|
||
|
#define CPRINTS(format, args...) cprints(CC_IPI, format, ##args)
|
||
|
|
||
|
#define IPI_MAX_REQUEST_SIZE CONFIG_IPC_SHARED_OBJ_BUF_SIZE
|
||
|
/* Reserve 1 extra byte for HOSTCMD_TYPE and 3 bytes for padding. */
|
||
|
#define IPI_MAX_RESPONSE_SIZE (CONFIG_IPC_SHARED_OBJ_BUF_SIZE - 4)
|
||
|
#define HOSTCMD_TYPE_HOSTCMD 1
|
||
|
#define HOSTCMD_TYPE_HOSTEVENT 2
|
||
|
|
||
|
static volatile int16_t ipc0_enabled_count;
|
||
|
static struct mutex ipc0_lock;
|
||
|
static struct mutex ipi_lock;
|
||
|
/* IPC0 shared objects, including send object and receive object. */
|
||
|
static struct ipc_shared_obj *const scp_send_obj =
|
||
|
(struct ipc_shared_obj *)CONFIG_IPC_SHARED_OBJ_ADDR;
|
||
|
static struct ipc_shared_obj *const scp_recv_obj =
|
||
|
(struct ipc_shared_obj *)(CONFIG_IPC_SHARED_OBJ_ADDR +
|
||
|
sizeof(struct ipc_shared_obj));
|
||
|
static char ipi_ready;
|
||
|
|
||
|
#ifdef HAS_TASK_HOSTCMD
|
||
|
/*
|
||
|
* hostcmd and hostevent share the same IPI ID, and use first byte type to
|
||
|
* indicate its type.
|
||
|
*/
|
||
|
static struct hostcmd_data {
|
||
|
const uint8_t type;
|
||
|
/* To be compatible with CONFIG_HOSTCMD_ALIGNED */
|
||
|
uint8_t response[IPI_MAX_RESPONSE_SIZE] __aligned(4);
|
||
|
} hc_cmd_obj = { .type = HOSTCMD_TYPE_HOSTCMD };
|
||
|
BUILD_ASSERT(sizeof(struct hostcmd_data) == CONFIG_IPC_SHARED_OBJ_BUF_SIZE);
|
||
|
|
||
|
static struct host_packet ipi_packet;
|
||
|
#endif
|
||
|
|
||
|
/* Check if SCP to AP IPI is in use. */
|
||
|
static inline int is_ipi_busy(void)
|
||
|
{
|
||
|
return SCP_HOST_INT & IPC_SCP2HOST_BIT;
|
||
|
}
|
||
|
|
||
|
/* If IPI is declared as a wake-up source, wake AP up. */
|
||
|
static inline void try_to_wakeup_ap(int32_t id)
|
||
|
{
|
||
|
#ifdef CONFIG_RPMSG_NAME_SERVICE
|
||
|
if (id == IPI_NS_SERVICE)
|
||
|
return;
|
||
|
#endif
|
||
|
|
||
|
if (*ipi_wakeup_table[id])
|
||
|
SCP_SPM_INT = SPM_INT_A2SPM;
|
||
|
}
|
||
|
|
||
|
void ipi_disable_irq(int irq)
|
||
|
{
|
||
|
/* Only support SCP_IRQ_IPC0 for now. */
|
||
|
if (irq != SCP_IRQ_IPC0)
|
||
|
return;
|
||
|
|
||
|
mutex_lock(&ipc0_lock);
|
||
|
|
||
|
if ((--ipc0_enabled_count) == 0)
|
||
|
task_disable_irq(irq);
|
||
|
|
||
|
mutex_unlock(&ipc0_lock);
|
||
|
}
|
||
|
|
||
|
void ipi_enable_irq(int irq)
|
||
|
{
|
||
|
/* Only support SCP_IRQ_IPC0 for now. */
|
||
|
if (irq != SCP_IRQ_IPC0)
|
||
|
return;
|
||
|
|
||
|
mutex_lock(&ipc0_lock);
|
||
|
|
||
|
if ((++ipc0_enabled_count) == 1) {
|
||
|
int pending_ipc = SCP_GIPC_IN & SCP_GPIC_IN_CLEAR_ALL;
|
||
|
|
||
|
task_enable_irq(irq);
|
||
|
|
||
|
if (ipi_ready && pending_ipc)
|
||
|
/*
|
||
|
* IPC may be triggered while SCP_IRQ_IPC0 was disabled.
|
||
|
* AP will still updates SCP_GIPC_IN.
|
||
|
* Trigger the IRQ handler if it has a
|
||
|
* pending IPC.
|
||
|
*/
|
||
|
task_trigger_irq(irq);
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&ipc0_lock);
|
||
|
}
|
||
|
|
||
|
/* Send data from SCP to AP. */
|
||
|
int ipi_send(int32_t id, const void *buf, uint32_t len, int wait)
|
||
|
{
|
||
|
if (!ipi_ready)
|
||
|
return EC_ERROR_BUSY;
|
||
|
|
||
|
/* TODO(b:117917141): Remove this check completely. */
|
||
|
if (in_interrupt_context()) {
|
||
|
CPRINTS("Err: invoke %s() in ISR CTX", __func__);
|
||
|
return EC_ERROR_BUSY;
|
||
|
}
|
||
|
|
||
|
if (len > sizeof(scp_send_obj->buffer))
|
||
|
return EC_ERROR_INVAL;
|
||
|
|
||
|
ipi_disable_irq(SCP_IRQ_IPC0);
|
||
|
mutex_lock(&ipi_lock);
|
||
|
|
||
|
/* Check if there is already an IPI pending in AP. */
|
||
|
if (is_ipi_busy()) {
|
||
|
/*
|
||
|
* If the following conditions meet,
|
||
|
* 1) There is an IPI pending in AP.
|
||
|
* 2) The incoming IPI is a wakeup IPI.
|
||
|
* then it assumes that AP is in suspend state.
|
||
|
* Send a AP wakeup request to SPM.
|
||
|
*
|
||
|
* The incoming IPI will be checked if it's a wakeup source.
|
||
|
*/
|
||
|
try_to_wakeup_ap(id);
|
||
|
|
||
|
mutex_unlock(&ipi_lock);
|
||
|
ipi_enable_irq(SCP_IRQ_IPC0);
|
||
|
CPRINTS("Err: IPI Busy, %d", id);
|
||
|
|
||
|
return EC_ERROR_BUSY;
|
||
|
}
|
||
|
|
||
|
|
||
|
scp_send_obj->id = id;
|
||
|
scp_send_obj->len = len;
|
||
|
memcpy(scp_send_obj->buffer, buf, len);
|
||
|
|
||
|
/* Send IPI to AP: interrutp AP to receive IPI messages. */
|
||
|
try_to_wakeup_ap(id);
|
||
|
SCP_HOST_INT = IPC_SCP2HOST_BIT;
|
||
|
|
||
|
while (wait && is_ipi_busy())
|
||
|
;
|
||
|
|
||
|
mutex_unlock(&ipi_lock);
|
||
|
ipi_enable_irq(SCP_IRQ_IPC0);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static void ipi_handler(void)
|
||
|
{
|
||
|
if (scp_recv_obj->id >= IPI_COUNT) {
|
||
|
CPRINTS("#ERR IPI %d", scp_recv_obj->id);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Pass the buffer to handler. Each handler should be in charge of
|
||
|
* the buffer copying/reading before returning from handler.
|
||
|
*/
|
||
|
ipi_handler_table[scp_recv_obj->id](
|
||
|
scp_recv_obj->id, scp_recv_obj->buffer, scp_recv_obj->len);
|
||
|
}
|
||
|
|
||
|
void ipi_inform_ap(void)
|
||
|
{
|
||
|
struct scp_run_t scp_run;
|
||
|
int ret;
|
||
|
#ifdef CONFIG_RPMSG_NAME_SERVICE
|
||
|
struct rpmsg_ns_msg ns_msg;
|
||
|
#endif
|
||
|
|
||
|
scp_run.signaled = 1;
|
||
|
strncpy(scp_run.fw_ver, system_get_version(SYSTEM_IMAGE_RW),
|
||
|
SCP_FW_VERSION_LEN);
|
||
|
scp_run.dec_capability = VCODEC_CAPABILITY_4K_DISABLED;
|
||
|
scp_run.enc_capability = 0;
|
||
|
|
||
|
ret = ipi_send(IPI_SCP_INIT, (void *)&scp_run, sizeof(scp_run), 1);
|
||
|
|
||
|
if (ret)
|
||
|
ccprintf("Failed to send initialization IPC messages.\n");
|
||
|
|
||
|
#ifdef CONFIG_RPMSG_NAME_SERVICE
|
||
|
ns_msg.id = IPI_HOST_COMMAND;
|
||
|
strncpy(ns_msg.name, "cros-ec-rpmsg", RPMSG_NAME_SIZE);
|
||
|
ret = ipi_send(IPI_NS_SERVICE, &ns_msg, sizeof(ns_msg), 1);
|
||
|
if (ret)
|
||
|
ccprintf("Failed to announce host command channel.\n");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#ifdef HAS_TASK_HOSTCMD
|
||
|
#if defined(CONFIG_MKBP_USE_CUSTOM)
|
||
|
int mkbp_set_host_active_via_custom(int active, uint32_t *timestamp)
|
||
|
{
|
||
|
static const uint8_t hc_evt_obj = HOSTCMD_TYPE_HOSTEVENT;
|
||
|
|
||
|
/* This should be moved into ipi_send for more accuracy */
|
||
|
if (timestamp)
|
||
|
*timestamp = __hw_clock_source_read();
|
||
|
|
||
|
if (active)
|
||
|
return ipi_send(IPI_HOST_COMMAND, &hc_evt_obj,
|
||
|
sizeof(hc_evt_obj), 1);
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void ipi_send_response_packet(struct host_packet *pkt)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = ipi_send(IPI_HOST_COMMAND, &hc_cmd_obj,
|
||
|
pkt->response_size +
|
||
|
offsetof(struct hostcmd_data, response),
|
||
|
1);
|
||
|
if (ret)
|
||
|
CPRINTS("#ERR IPI HOSTCMD %d", ret);
|
||
|
}
|
||
|
|
||
|
static void ipi_hostcmd_handler(int32_t id, void *buf, uint32_t len)
|
||
|
{
|
||
|
uint8_t *in_msg = buf;
|
||
|
struct ec_host_request *r = (struct ec_host_request *)in_msg;
|
||
|
int i;
|
||
|
|
||
|
if (in_msg[0] != EC_HOST_REQUEST_VERSION) {
|
||
|
CPRINTS("ERROR: Protocol V2 is not supported!");
|
||
|
CPRINTF("in_msg=[");
|
||
|
for (i = 0; i < len; i++)
|
||
|
CPRINTF("%02x ", in_msg[i]);
|
||
|
CPRINTF("]\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Protocol version 3 */
|
||
|
|
||
|
ipi_packet.send_response = ipi_send_response_packet;
|
||
|
|
||
|
/*
|
||
|
* Just assign the buffer to request, host_packet_receive
|
||
|
* handles the buffer copy.
|
||
|
*/
|
||
|
ipi_packet.request = (void *)r;
|
||
|
ipi_packet.request_temp = NULL;
|
||
|
ipi_packet.request_max = IPI_MAX_REQUEST_SIZE;
|
||
|
ipi_packet.request_size = host_request_expected_size(r);
|
||
|
|
||
|
ipi_packet.response = hc_cmd_obj.response;
|
||
|
/* Reserve space for the preamble and trailing byte */
|
||
|
ipi_packet.response_max = IPI_MAX_RESPONSE_SIZE;
|
||
|
ipi_packet.response_size = 0;
|
||
|
|
||
|
ipi_packet.driver_result = EC_RES_SUCCESS;
|
||
|
|
||
|
host_packet_receive(&ipi_packet);
|
||
|
}
|
||
|
DECLARE_IPI(IPI_HOST_COMMAND, ipi_hostcmd_handler, 0);
|
||
|
|
||
|
/*
|
||
|
* Get protocol information
|
||
|
*/
|
||
|
static enum ec_status ipi_get_protocol_info(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
struct ec_response_get_protocol_info *r = args->response;
|
||
|
|
||
|
memset(r, 0, sizeof(*r));
|
||
|
r->protocol_versions |= BIT(3);
|
||
|
r->max_request_packet_size = IPI_MAX_REQUEST_SIZE;
|
||
|
r->max_response_packet_size = IPI_MAX_RESPONSE_SIZE;
|
||
|
|
||
|
args->response_size = sizeof(*r);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO, ipi_get_protocol_info,
|
||
|
EC_VER_MASK(0));
|
||
|
#endif
|
||
|
|
||
|
static void ipi_enable_ipc0_deferred(void)
|
||
|
{
|
||
|
/* Clear IPC0 IRQs. */
|
||
|
SCP_GIPC_IN = SCP_GPIC_IN_CLEAR_ALL;
|
||
|
|
||
|
/* All tasks are up, we can safely enable IPC0 IRQ now. */
|
||
|
SCP_INTC_IRQ_ENABLE |= IPC0_IRQ_EN;
|
||
|
ipi_enable_irq(SCP_IRQ_IPC0);
|
||
|
|
||
|
ipi_ready = 1;
|
||
|
|
||
|
/* Inform AP that SCP is inited. */
|
||
|
ipi_inform_ap();
|
||
|
|
||
|
CPRINTS("ipi init");
|
||
|
}
|
||
|
DECLARE_DEFERRED(ipi_enable_ipc0_deferred);
|
||
|
|
||
|
/* Initialize IPI. */
|
||
|
static void ipi_init(void)
|
||
|
{
|
||
|
/* Clear send share buffer. */
|
||
|
memset(scp_send_obj, 0, sizeof(struct ipc_shared_obj));
|
||
|
|
||
|
/* Enable IRQ after all tasks are up. */
|
||
|
hook_call_deferred(&ipi_enable_ipc0_deferred_data, 0);
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, ipi_init, HOOK_PRIO_DEFAULT);
|
||
|
|
||
|
void ipc_handler(void)
|
||
|
{
|
||
|
/* TODO(b/117917141): We only support IPC_ID(0) for now. */
|
||
|
if (SCP_GIPC_IN & SCP_GIPC_IN_CLEAR_IPCN(0)) {
|
||
|
ipi_handler();
|
||
|
SCP_GIPC_IN &= SCP_GIPC_IN_CLEAR_IPCN(0);
|
||
|
}
|
||
|
|
||
|
SCP_GIPC_IN &= (SCP_GPIC_IN_CLEAR_ALL & ~SCP_GIPC_IN_CLEAR_IPCN(0));
|
||
|
}
|
||
|
DECLARE_IRQ(SCP_IRQ_IPC0, ipc_handler, 4);
|