/* 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_RES_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); DECLARE_IRQ(SCP_IRQ_IPC0, ipc_handler, 4); 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)); }