coreboot-libre-fam15h-rdimm/3rdparty/chromeec/chip/mt_scp/ipi.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);