// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2020-21 Intel Corporation. */ #include "iosm_ipc_imem.h" #include "iosm_ipc_protocol.h" #include "iosm_ipc_protocol_ops.h" #include "iosm_ipc_pm.h" #include "iosm_ipc_task_queue.h" int ipc_protocol_tq_msg_send(struct iosm_protocol *ipc_protocol, enum ipc_msg_prep_type msg_type, union ipc_msg_prep_args *prep_args, struct ipc_rsp *response) { int index = ipc_protocol_msg_prep(ipc_protocol->imem, msg_type, prep_args); /* Store reference towards caller specified response in response ring * and signal CP */ if (index >= 0 && index < IPC_MEM_MSG_ENTRIES) { ipc_protocol->rsp_ring[index] = response; ipc_protocol_msg_hp_update(ipc_protocol->imem); } return index; } /* Callback for message send */ static int ipc_protocol_tq_msg_send_cb(struct iosm_imem *ipc_imem, int arg, void *msg, size_t size) { struct ipc_call_msg_send_args *send_args = msg; struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; return ipc_protocol_tq_msg_send(ipc_protocol, send_args->msg_type, send_args->prep_args, send_args->response); } /* Remove reference to a response. This is typically used when a requestor timed * out and is no longer interested in the response. */ static int ipc_protocol_tq_msg_remove(struct iosm_imem *ipc_imem, int arg, void *msg, size_t size) { struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; ipc_protocol->rsp_ring[arg] = NULL; return 0; } int ipc_protocol_msg_send(struct iosm_protocol *ipc_protocol, enum ipc_msg_prep_type prep, union ipc_msg_prep_args *prep_args) { struct ipc_call_msg_send_args send_args; unsigned int exec_timeout; struct ipc_rsp response; int index; exec_timeout = (ipc_protocol_get_ap_exec_stage(ipc_protocol) == IPC_MEM_EXEC_STAGE_RUN ? IPC_MSG_COMPLETE_RUN_DEFAULT_TIMEOUT : IPC_MSG_COMPLETE_BOOT_DEFAULT_TIMEOUT); /* Trap if called from non-preemptible context */ might_sleep(); response.status = IPC_MEM_MSG_CS_INVALID; init_completion(&response.completion); send_args.msg_type = prep; send_args.prep_args = prep_args; send_args.response = &response; /* Allocate and prepare message to be sent in tasklet context. * A positive index returned form tasklet_call references the message * in case it needs to be cancelled when there is a timeout. */ index = ipc_task_queue_send_task(ipc_protocol->imem, ipc_protocol_tq_msg_send_cb, 0, &send_args, 0, true); if (index < 0) { dev_err(ipc_protocol->dev, "msg %d failed", prep); return index; } /* Wait for the device to respond to the message */ switch (wait_for_completion_timeout(&response.completion, msecs_to_jiffies(exec_timeout))) { case 0: /* Timeout, there was no response from the device. * Remove the reference to the local response completion * object as we are no longer interested in the response. */ ipc_task_queue_send_task(ipc_protocol->imem, ipc_protocol_tq_msg_remove, index, NULL, 0, true); dev_err(ipc_protocol->dev, "msg timeout"); ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT); break; default: /* We got a response in time; check completion status: */ if (response.status != IPC_MEM_MSG_CS_SUCCESS) { dev_err(ipc_protocol->dev, "msg completion status error %d", response.status); return -EIO; } } return 0; } static int ipc_protocol_msg_send_host_sleep(struct iosm_protocol *ipc_protocol, u32 state) { union ipc_msg_prep_args prep_args = { .sleep.target = 0, .sleep.state = state, }; return ipc_protocol_msg_send(ipc_protocol, IPC_MSG_PREP_SLEEP, &prep_args); } void ipc_protocol_doorbell_trigger(struct iosm_protocol *ipc_protocol, u32 identifier) { ipc_pm_signal_hpda_doorbell(&ipc_protocol->pm, identifier, true); } bool ipc_protocol_pm_dev_sleep_handle(struct iosm_protocol *ipc_protocol) { u32 ipc_status = ipc_protocol_get_ipc_status(ipc_protocol); u32 requested; if (ipc_status != IPC_MEM_DEVICE_IPC_RUNNING) { dev_err(ipc_protocol->dev, "irq ignored, CP IPC state is %d, should be RUNNING", ipc_status); /* Stop further processing. */ return false; } /* Get a copy of the requested PM state by the device and the local * device PM state. */ requested = ipc_protocol_pm_dev_get_sleep_notification(ipc_protocol); return ipc_pm_dev_slp_notification(&ipc_protocol->pm, requested); } static int ipc_protocol_tq_wakeup_dev_slp(struct iosm_imem *ipc_imem, int arg, void *msg, size_t size) { struct iosm_pm *ipc_pm = &ipc_imem->ipc_protocol->pm; /* Wakeup from device sleep if it is not ACTIVE */ ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, true); ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, false); return 0; } void ipc_protocol_s2idle_sleep(struct iosm_protocol *ipc_protocol, bool sleep) { ipc_pm_set_s2idle_sleep(&ipc_protocol->pm, sleep); } bool ipc_protocol_suspend(struct iosm_protocol *ipc_protocol) { if (!ipc_pm_prepare_host_sleep(&ipc_protocol->pm)) goto err; ipc_task_queue_send_task(ipc_protocol->imem, ipc_protocol_tq_wakeup_dev_slp, 0, NULL, 0, true); if (!ipc_pm_wait_for_device_active(&ipc_protocol->pm)) { ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT); goto err; } /* Send the sleep message for sync sys calls. */ dev_dbg(ipc_protocol->dev, "send TARGET_HOST, ENTER_SLEEP"); if (ipc_protocol_msg_send_host_sleep(ipc_protocol, IPC_HOST_SLEEP_ENTER_SLEEP)) { /* Sending ENTER_SLEEP message failed, we are still active */ ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE; goto err; } ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP; return true; err: return false; } bool ipc_protocol_resume(struct iosm_protocol *ipc_protocol) { if (!ipc_pm_prepare_host_active(&ipc_protocol->pm)) return false; dev_dbg(ipc_protocol->dev, "send TARGET_HOST, EXIT_SLEEP"); if (ipc_protocol_msg_send_host_sleep(ipc_protocol, IPC_HOST_SLEEP_EXIT_SLEEP)) { ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP; return false; } ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE; return true; } struct iosm_protocol *ipc_protocol_init(struct iosm_imem *ipc_imem) { struct iosm_protocol *ipc_protocol = kzalloc(sizeof(*ipc_protocol), GFP_KERNEL); struct ipc_protocol_context_info *p_ci; u64 addr; if (!ipc_protocol) return NULL; ipc_protocol->dev = ipc_imem->dev; ipc_protocol->pcie = ipc_imem->pcie; ipc_protocol->imem = ipc_imem; ipc_protocol->p_ap_shm = NULL; ipc_protocol->phy_ap_shm = 0; ipc_protocol->old_msg_tail = 0; ipc_protocol->p_ap_shm = dma_alloc_coherent(&ipc_protocol->pcie->pci->dev, sizeof(*ipc_protocol->p_ap_shm), &ipc_protocol->phy_ap_shm, GFP_KERNEL); if (!ipc_protocol->p_ap_shm) { dev_err(ipc_protocol->dev, "pci shm alloc error"); kfree(ipc_protocol); return NULL; } /* Prepare the context info for CP. */ addr = ipc_protocol->phy_ap_shm; p_ci = &ipc_protocol->p_ap_shm->ci; p_ci->device_info_addr = addr + offsetof(struct ipc_protocol_ap_shm, device_info); p_ci->head_array = addr + offsetof(struct ipc_protocol_ap_shm, head_array); p_ci->tail_array = addr + offsetof(struct ipc_protocol_ap_shm, tail_array); p_ci->msg_head = addr + offsetof(struct ipc_protocol_ap_shm, msg_head); p_ci->msg_tail = addr + offsetof(struct ipc_protocol_ap_shm, msg_tail); p_ci->msg_ring_addr = addr + offsetof(struct ipc_protocol_ap_shm, msg_ring); p_ci->msg_ring_entries = cpu_to_le16(IPC_MEM_MSG_ENTRIES); p_ci->msg_irq_vector = IPC_MSG_IRQ_VECTOR; p_ci->device_info_irq_vector = IPC_DEVICE_IRQ_VECTOR; ipc_mmio_set_contex_info_addr(ipc_imem->mmio, addr); ipc_pm_init(ipc_protocol); return ipc_protocol; } void ipc_protocol_deinit(struct iosm_protocol *proto) { dma_free_coherent(&proto->pcie->pci->dev, sizeof(*proto->p_ap_shm), proto->p_ap_shm, proto->phy_ap_shm); ipc_pm_deinit(proto); kfree(proto); }