/* 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 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; } /* 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): Evaluate if we can remove this once we have the * video/camera feature code base. */ if (wait && in_interrupt_context()) /* Prevent from infinity wait when be in ISR context. */ return EC_ERROR_BUSY; if (len > sizeof(scp_send_obj->buffer)) return EC_ERROR_INVAL; task_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); task_enable_irq(SCP_IRQ_IPC0); 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); task_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 = 0; 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, 1); /* * Get protocol information */ static int 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; task_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_GPIC_IN_CLEAR_ALL; } DECLARE_IRQ(SCP_IRQ_IPC0, ipc_handler, 4);