/* * Copyright (c) 2014-2016, ARM Limited. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the ARM nor the names of its contributors may be used * to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include static unsigned int pstate_format_detected; static unsigned int pstate_format; static unsigned int is_state_id_null; const psci_function_t psci_functions[PSCI_NUM_CALLS] = { DEFINE_PSCI_FUNC(PSCI_FEATURES, true), DEFINE_PSCI_FUNC(PSCI_VERSION, true), DEFINE_PSCI_FUNC(PSCI_CPU_SUSPEND_AARCH32, true), DEFINE_PSCI_FUNC(PSCI_CPU_SUSPEND_AARCH64, true), DEFINE_PSCI_FUNC(PSCI_CPU_OFF, true), DEFINE_PSCI_FUNC(PSCI_CPU_ON_AARCH32, true), DEFINE_PSCI_FUNC(PSCI_CPU_ON_AARCH64, true), DEFINE_PSCI_FUNC(PSCI_AFFINITY_INFO_AARCH32, true), DEFINE_PSCI_FUNC(PSCI_AFFINITY_INFO_AARCH64, true), DEFINE_PSCI_FUNC(PSCI_SYSTEM_OFF, true), DEFINE_PSCI_FUNC(PSCI_SYSTEM_RESET, true), DEFINE_PSCI_FUNC(PSCI_MIG_INFO_TYPE, false), DEFINE_PSCI_FUNC(PSCI_MIG_INFO_UP_CPU_AARCH32, false), DEFINE_PSCI_FUNC(PSCI_MIG_INFO_UP_CPU_AARCH64, false), DEFINE_PSCI_FUNC(PSCI_MIG_AARCH32, false), DEFINE_PSCI_FUNC(PSCI_MIG_AARCH64, false), DEFINE_PSCI_FUNC(PSCI_CPU_FREEZE, false), DEFINE_PSCI_FUNC(PSCI_CPU_DEFAULT_SUSPEND32, false), DEFINE_PSCI_FUNC(PSCI_CPU_DEFAULT_SUSPEND64, false), DEFINE_PSCI_FUNC(PSCI_CPU_HW_STATE32, false), DEFINE_PSCI_FUNC(PSCI_CPU_HW_STATE64, false), DEFINE_PSCI_FUNC(PSCI_SYSTEM_SUSPEND32, false), DEFINE_PSCI_FUNC(PSCI_SYSTEM_SUSPEND64, false), DEFINE_PSCI_FUNC(PSCI_SET_SUSPEND_MODE, false), DEFINE_PSCI_FUNC(PSCI_STAT_RESIDENCY32, false), DEFINE_PSCI_FUNC(PSCI_STAT_RESIDENCY64, false), DEFINE_PSCI_FUNC(PSCI_STAT_COUNT32, false), DEFINE_PSCI_FUNC(PSCI_STAT_COUNT64, false), DEFINE_PSCI_FUNC(PSCI_MEM_PROTECT, false), DEFINE_PSCI_FUNC(PSCI_MEM_PROTECT_CHECK_RANGE32, false), DEFINE_PSCI_FUNC(PSCI_MEM_PROTECT_CHECK_RANGE64, false), DEFINE_PSCI_FUNC(PSCI_RESET2_AARCH32, false), DEFINE_PSCI_FUNC(PSCI_RESET2_AARCH64, false), }; int32_t tftf_psci_cpu_on(u_register_t target_cpu, uintptr_t entry_point_address, u_register_t context_id) { smc_args args = { SMC_PSCI_CPU_ON, target_cpu, entry_point_address, context_id }; smc_ret_values ret_vals; ret_vals = tftf_smc(&args); return ret_vals.ret0; } int32_t tftf_psci_cpu_off(void) { smc_args args = { SMC_PSCI_CPU_OFF }; smc_ret_values ret_vals; ret_vals = tftf_smc(&args); return ret_vals.ret0; } u_register_t tftf_psci_stat_residency(u_register_t target_cpu, uint32_t power_state) { smc_args args = { SMC_PSCI_STAT_RESIDENCY, target_cpu, power_state, }; smc_ret_values ret_vals; ret_vals = tftf_smc(&args); return ret_vals.ret0; } u_register_t tftf_psci_stat_count(u_register_t target_cpu, uint32_t power_state) { smc_args args = { SMC_PSCI_STAT_COUNT, target_cpu, power_state, }; smc_ret_values ret_vals; ret_vals = tftf_smc(&args); return ret_vals.ret0; } int32_t tftf_psci_affinity_info(u_register_t target_affinity, uint32_t lowest_affinity_level) { smc_ret_values ret_vals; smc_args args = { SMC_PSCI_AFFINITY_INFO, target_affinity, lowest_affinity_level }; ret_vals = tftf_smc(&args); return ret_vals.ret0; } int32_t tftf_psci_node_hw_state(u_register_t target_cpu, uint32_t power_level) { smc_args args = { SMC_PSCI_CPU_HW_STATE, target_cpu, power_level }; smc_ret_values ret; ret = tftf_smc(&args); return ret.ret0; } int32_t tftf_get_psci_feature_info(uint32_t psci_func_id) { smc_args args = { SMC_PSCI_FEATURES, psci_func_id }; smc_ret_values ret; ret = tftf_smc(&args); return ret.ret0; } int tftf_psci_make_composite_state_id(uint32_t affinity_level, uint32_t state_type, uint32_t *state_id) { unsigned int found_entry, i; int ret = PSCI_E_SUCCESS; const plat_state_prop_t *state_prop; assert(state_id); *state_id = 0; for (i = 0; i <= affinity_level; i++) { state_prop = plat_get_state_prop(i); if (!state_prop) { *state_id |= psci_make_local_state_id(i, PLAT_PSCI_DUMMY_STATE_ID); ret = PSCI_E_INVALID_PARAMS; continue; } found_entry = 0; while (state_prop->state_ID) { if (state_type == state_prop->is_pwrdown) { *state_id |= psci_make_local_state_id(i, state_prop->state_ID); found_entry = 1; break; } state_prop++; } if (!found_entry) { *state_id |= psci_make_local_state_id(i, PLAT_PSCI_DUMMY_STATE_ID); ret = PSCI_E_INVALID_PARAMS; } } return ret; } static unsigned int tftf_psci_get_pstate_format(void) { int ret; ret = tftf_get_psci_feature_info(SMC_PSCI_CPU_SUSPEND); /* * If error is returned, then it probably means that the PSCI version * is less than 1.0 and only the original format is supported. In case * the version is 1.0 or higher, then PSCI FEATURES which is a * mandatory API is not implemented which implies that only the * original format is supported. */ if (ret == PSCI_E_NOT_SUPPORTED) return CPU_SUSPEND_FEAT_PSTATE_FORMAT_ORIGINAL; /* Treat the invalid return value as PSCI FEATURES not supported */ if ((ret & ~CPU_SUSPEND_FEAT_VALID_MASK) != 0) return CPU_SUSPEND_FEAT_PSTATE_FORMAT_ORIGINAL; return (ret >> CPU_SUSPEND_FEAT_PSTATE_FORMAT_SHIFT) & 0x1; } /* Make the power state in the original format */ uint32_t tftf_make_psci_pstate(uint32_t affinity_level, uint32_t state_type, uint32_t state_id) { uint32_t power_state; assert(psci_state_type_valid(state_type)); assert(pstate_format_detected); if (pstate_format == CPU_SUSPEND_FEAT_PSTATE_FORMAT_EXTENDED) { assert(psci_state_id_ext_valid(state_id)); power_state = (state_type << PSTATE_TYPE_SHIFT_EXT) | (state_id << PSTATE_ID_SHIFT_EXT); } else { assert(psci_affinity_level_valid(affinity_level)); assert(psci_state_id_valid(state_id)); power_state = (affinity_level << PSTATE_AFF_LVL_SHIFT) | (state_type << PSTATE_TYPE_SHIFT); if (!is_state_id_null) power_state |= (state_id << PSTATE_ID_SHIFT); } return power_state; } void tftf_detect_psci_pstate_format(void) { uint32_t power_state; unsigned int ret; pstate_format = tftf_psci_get_pstate_format(); /* * If the power state format is extended format, then the state-ID * must use recommended encoding. */ if (pstate_format == CPU_SUSPEND_FEAT_PSTATE_FORMAT_EXTENDED) { pstate_format_detected = 1; INFO("Extended PSCI power state format detected\n"); return; } tftf_irq_enable(IRQ_NS_SGI_0, GIC_HIGHEST_NS_PRIORITY); /* * Mask IRQ to prevent the interrupt handler being invoked * and clearing the interrupt. A pending interrupt will cause this * CPU to wake-up from suspend. */ disable_irq(); /* Configure an SGI to wake-up from suspend */ tftf_send_sgi(IRQ_NS_SGI_0, platform_get_core_pos(read_mpidr_el1() & MPID_MASK)); /* * Try to detect if the platform uses NULL State-ID encoding by sending * PSCI_SUSPEND call with the NULL State-ID encoding. If the call * succeeds then the platform uses NULL State-ID encoding. Else it * uses the recommended encoding for State-ID. */ power_state = (PSTATE_AFF_LVL_0 << PSTATE_AFF_LVL_SHIFT) | (PSTATE_TYPE_STANDBY << PSTATE_TYPE_SHIFT); ret = tftf_cpu_suspend(power_state); /* Unmask the IRQ to let the interrupt handler to execute */ enable_irq(); isb(); tftf_irq_disable(IRQ_NS_SGI_0); /* * The NULL State-ID returned SUCCESS. Hence State-ID is NULL * for the power state format. */ if (ret == PSCI_E_SUCCESS) { is_state_id_null = 1; INFO("Original PSCI power state format with NULL State-ID detected\n"); } else INFO("Original PSCI power state format detected\n"); pstate_format_detected = 1; } unsigned int tftf_is_psci_state_id_null(void) { assert(pstate_format_detected); if (pstate_format == CPU_SUSPEND_FEAT_PSTATE_FORMAT_ORIGINAL) return is_state_id_null; else return 0; /* Extended state ID does not support null format */ } unsigned int tftf_is_psci_pstate_format_original(void) { assert(pstate_format_detected); return pstate_format == CPU_SUSPEND_FEAT_PSTATE_FORMAT_ORIGINAL; } unsigned int tftf_get_psci_version(void) { smc_args args = { SMC_PSCI_VERSION }; smc_ret_values ret; ret = tftf_smc(&args); return ret.ret0; } int tftf_is_valid_psci_version(unsigned int version) { if (version != PSCI_VERSION(1, 1) && version != PSCI_VERSION(1, 0) && version != PSCI_VERSION(0, 2) && version != PSCI_VERSION(0, 1)) { return 0; } return 1; }