/* * xen/arch/arm/platforms/xilinx-zynqmp-eemi.c * * Xilinx ZynqMP EEMI API * * Copyright (c) 2018 Xilinx Inc. * Written by Edgar E. Iglesias * * This program is free software; you can redistribute it and/or * modify it under the terms and conditions of the GNU General Public * License, version 2, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include /* * EEMI firmware API: * https://www.xilinx.com/support/documentation/user_guides/ug1200-eemi-api.pdf * * IPI firmware API: * https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/xilinx/zynqmp/ipi_mailbox_service/ipi_mailbox_svc.h * * Power domain node_ids identify the area of effect of the power * management operations. They are the first parameter passed to power * management EEMI calls. * * Reset IDs identify the area of effect of a reset operation. They are * the first parameter passed to reset EEMI calls. * * For now, let the hardware domain access to all power domain nodes and * all reset lines. In the future, we'll check for ownership of * resources by specific virtual machines. */ static inline bool domain_has_node_access(struct domain *d, uint32_t nodeid) { return is_hardware_domain(d); } static inline bool domain_has_reset_access(struct domain *d, uint32_t rst) { return is_hardware_domain(d); } bool zynqmp_eemi(struct cpu_user_regs *regs) { struct arm_smccc_res res; uint32_t fid = get_user_reg(regs, 0); uint32_t nodeid = get_user_reg(regs, 1); unsigned int pm_fn = fid & 0xFFFF; enum pm_ret_status ret; switch ( fid ) { /* Mandatory SMC32 functions. */ case ARM_SMCCC_CALL_COUNT_FID(SIP): case ARM_SMCCC_CALL_UID_FID(SIP): case ARM_SMCCC_REVISION_FID(SIP): goto forward_to_fw; /* * We can't allow CPUs to suspend without Xen knowing about it. * We accept but ignore the request and wait for the guest to issue * a WFI or PSCI call which Xen will trap and act accordingly upon. */ case EEMI_FID(PM_SELF_SUSPEND): ret = XST_PM_SUCCESS; goto done; case EEMI_FID(PM_GET_NODE_STATUS): /* API for PUs. */ case EEMI_FID(PM_REQ_SUSPEND): case EEMI_FID(PM_FORCE_POWERDOWN): case EEMI_FID(PM_ABORT_SUSPEND): case EEMI_FID(PM_REQ_WAKEUP): case EEMI_FID(PM_SET_WAKEUP_SOURCE): /* API for slaves. */ case EEMI_FID(PM_REQ_NODE): case EEMI_FID(PM_RELEASE_NODE): case EEMI_FID(PM_SET_REQUIREMENT): case EEMI_FID(PM_SET_MAX_LATENCY): if ( !domain_has_node_access(current->domain, nodeid) ) { gprintk(XENLOG_WARNING, "zynqmp-pm: fn=%u No access to node %u\n", pm_fn, nodeid); ret = XST_PM_NO_ACCESS; goto done; } goto forward_to_fw; case EEMI_FID(PM_RESET_ASSERT): case EEMI_FID(PM_RESET_GET_STATUS): if ( !domain_has_reset_access(current->domain, nodeid) ) { gprintk(XENLOG_WARNING, "zynqmp-pm: fn=%u No access to reset %u\n", pm_fn, nodeid); ret = XST_PM_NO_ACCESS; goto done; } goto forward_to_fw; /* These calls are safe and always allowed. */ case EEMI_FID(PM_GET_TRUSTZONE_VERSION): case EEMI_FID(PM_GET_API_VERSION): case EEMI_FID(PM_GET_CHIPID): goto forward_to_fw; /* No MMIO access is allowed from non-secure domains */ case EEMI_FID(PM_MMIO_WRITE): case EEMI_FID(PM_MMIO_READ): gprintk(XENLOG_WARNING, "zynqmp-pm: fn=%u No MMIO access to %u\n", pm_fn, nodeid); ret = XST_PM_NO_ACCESS; goto done; /* Exclusive to the hardware domain. */ case EEMI_FID(PM_INIT): case EEMI_FID(PM_SET_CONFIGURATION): case EEMI_FID(PM_FPGA_LOAD): case EEMI_FID(PM_FPGA_GET_STATUS): case EEMI_FID(PM_SECURE_SHA): case EEMI_FID(PM_SECURE_RSA): case EEMI_FID(PM_PINCTRL_SET_FUNCTION): case EEMI_FID(PM_PINCTRL_REQUEST): case EEMI_FID(PM_PINCTRL_RELEASE): case EEMI_FID(PM_PINCTRL_GET_FUNCTION): case EEMI_FID(PM_PINCTRL_CONFIG_PARAM_GET): case EEMI_FID(PM_PINCTRL_CONFIG_PARAM_SET): case EEMI_FID(PM_IOCTL): case EEMI_FID(PM_QUERY_DATA): case EEMI_FID(PM_CLOCK_ENABLE): case EEMI_FID(PM_CLOCK_DISABLE): case EEMI_FID(PM_CLOCK_GETSTATE): case EEMI_FID(PM_CLOCK_GETDIVIDER): case EEMI_FID(PM_CLOCK_SETDIVIDER): case EEMI_FID(PM_CLOCK_SETRATE): case EEMI_FID(PM_CLOCK_GETRATE): case EEMI_FID(PM_CLOCK_SETPARENT): case EEMI_FID(PM_CLOCK_GETPARENT): if ( !is_hardware_domain(current->domain) ) { gprintk(XENLOG_WARNING, "eemi: fn=%u No access", pm_fn); ret = XST_PM_NO_ACCESS; goto done; } goto forward_to_fw; /* These calls are never allowed. */ case EEMI_FID(PM_SYSTEM_SHUTDOWN): ret = XST_PM_NO_ACCESS; goto done; case IPI_MAILBOX_FID(IPI_MAILBOX_OPEN): case IPI_MAILBOX_FID(IPI_MAILBOX_RELEASE): case IPI_MAILBOX_FID(IPI_MAILBOX_STATUS_ENQUIRY): case IPI_MAILBOX_FID(IPI_MAILBOX_NOTIFY): case IPI_MAILBOX_FID(IPI_MAILBOX_ACK): case IPI_MAILBOX_FID(IPI_MAILBOX_ENABLE_IRQ): case IPI_MAILBOX_FID(IPI_MAILBOX_DISABLE_IRQ): if ( !is_hardware_domain(current->domain) ) { gprintk(XENLOG_WARNING, "IPI mailbox: fn=%u No access", pm_fn); ret = XST_PM_NO_ACCESS; goto done; } goto forward_to_fw; default: gprintk(XENLOG_WARNING, "zynqmp-pm: Unhandled PM Call: %u\n", fid); return false; } forward_to_fw: /* * ZynqMP firmware calls (EEMI) take an argument that specifies the * area of effect of the function called. Specifically, node ids for * power management functions and reset ids for reset functions. * * The code above checks if a virtual machine has access rights over * the node id, reset id, etc. Now that the check has been done, we * can forward the whole command to firmware without additional * parameters checks. */ arm_smccc_1_1_smc(get_user_reg(regs, 0), get_user_reg(regs, 1), get_user_reg(regs, 2), get_user_reg(regs, 3), get_user_reg(regs, 4), get_user_reg(regs, 5), get_user_reg(regs, 6), get_user_reg(regs, 7), &res); set_user_reg(regs, 0, res.a0); set_user_reg(regs, 1, res.a1); set_user_reg(regs, 2, res.a2); set_user_reg(regs, 3, res.a3); return true; done: set_user_reg(regs, 0, ret); return true; } /* * Local variables: * mode: C * c-file-style: "BSD" * c-basic-offset: 4 * indent-tabs-mode: nil * End: */