diff options
Diffstat (limited to 'chip/mt_scp/mt8183/ipi.c')
-rw-r--r-- | chip/mt_scp/mt8183/ipi.c | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/chip/mt_scp/mt8183/ipi.c b/chip/mt_scp/mt8183/ipi.c new file mode 100644 index 0000000000..db371f02b4 --- /dev/null +++ b/chip/mt_scp/mt8183/ipi.c @@ -0,0 +1,394 @@ +/* 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 "clock_chip.h" +#include "console.h" +#include "hooks.h" +#include "host_command.h" +#include "ipi_chip.h" +#include "mkbp_event.h" +#include "power.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); +} + +__override void +power_chipset_handle_host_sleep_event(enum host_sleep_event state, + struct host_sleep_event_context *ctx) +{ + int i; + const task_id_t s3_suspend_tasks[] = { +#ifndef S3_SUSPEND_TASK_LIST +#define S3_SUSPEND_TASK_LIST +#endif +#define TASK(n, ...) TASK_ID_##n, + S3_SUSPEND_TASK_LIST + }; + + if (state == HOST_SLEEP_EVENT_S3_SUSPEND) { + ccprints("AP suspend"); + /* + * On AP suspend, Vcore is 0.6V, and we should not use ULPOSC2, + * which needs at least 0.7V. Switch to ULPOSC1 instead. + */ + scp_use_clock(SCP_CLK_ULPOSC1); + + for (i = 0; i < ARRAY_SIZE(s3_suspend_tasks); ++i) + task_disable_task(s3_suspend_tasks[i]); + } else if (state == HOST_SLEEP_EVENT_S3_RESUME) { + ccprints("AP resume"); + /* Vcore is raised to >=0.7V, switch back to ULPSOC2 */ + scp_use_clock(SCP_CLK_ULPOSC2); + + for (i = 0; i < ARRAY_SIZE(s3_suspend_tasks); ++i) + task_enable_task(s3_suspend_tasks[i]); + } +} + +/* 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; + } + + /* + * Only print IPI that is not host command channel, which will + * be printed by host command driver. + */ + if (scp_recv_obj->id != IPI_HOST_COMMAND) + CPRINTS("IPI %d", scp_recv_obj->id); + + /* + * 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(EC_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); |