123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /*
- * Copyright (c) 2013-2022, ARM Limited and Contributors. All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- #include <assert.h>
- #include <stddef.h>
- #include <arch.h>
- #include <arch_helpers.h>
- #include <common/bl_common.h>
- #include <common/debug.h>
- #include <context.h>
- #include <lib/el3_runtime/context_mgmt.h>
- #include <lib/el3_runtime/cpu_data.h>
- #include <lib/el3_runtime/pubsub_events.h>
- #include <lib/pmf/pmf.h>
- #include <lib/runtime_instr.h>
- #include <plat/common/platform.h>
- #include "psci_private.h"
- /*******************************************************************************
- * This function does generic and platform specific operations after a wake-up
- * from standby/retention states at multiple power levels.
- ******************************************************************************/
- static void psci_suspend_to_standby_finisher(unsigned int cpu_idx,
- unsigned int end_pwrlvl)
- {
- unsigned int parent_nodes[PLAT_MAX_PWR_LVL] = {0};
- psci_power_state_t state_info;
- /* Get the parent nodes */
- psci_get_parent_pwr_domain_nodes(cpu_idx, end_pwrlvl, parent_nodes);
- psci_acquire_pwr_domain_locks(end_pwrlvl, parent_nodes);
- /*
- * Find out which retention states this CPU has exited from until the
- * 'end_pwrlvl'. The exit retention state could be deeper than the entry
- * state as a result of state coordination amongst other CPUs post wfi.
- */
- psci_get_target_local_pwr_states(end_pwrlvl, &state_info);
- #if ENABLE_PSCI_STAT
- plat_psci_stat_accounting_stop(&state_info);
- psci_stats_update_pwr_up(end_pwrlvl, &state_info);
- #endif
- /*
- * Plat. management: Allow the platform to do operations
- * on waking up from retention.
- */
- psci_plat_pm_ops->pwr_domain_suspend_finish(&state_info);
- /*
- * Set the requested and target state of this CPU and all the higher
- * power domain levels for this CPU to run.
- */
- psci_set_pwr_domains_to_run(end_pwrlvl);
- psci_release_pwr_domain_locks(end_pwrlvl, parent_nodes);
- }
- /*******************************************************************************
- * This function does generic and platform specific suspend to power down
- * operations.
- ******************************************************************************/
- static void psci_suspend_to_pwrdown_start(unsigned int end_pwrlvl,
- const entry_point_info_t *ep,
- const psci_power_state_t *state_info)
- {
- unsigned int max_off_lvl = psci_find_max_off_lvl(state_info);
- PUBLISH_EVENT(psci_suspend_pwrdown_start);
- #if PSCI_OS_INIT_MODE
- #ifdef PLAT_MAX_CPU_SUSPEND_PWR_LVL
- end_pwrlvl = PLAT_MAX_CPU_SUSPEND_PWR_LVL;
- #else
- end_pwrlvl = PLAT_MAX_PWR_LVL;
- #endif
- #endif
- /* Save PSCI target power level for the suspend finisher handler */
- psci_set_suspend_pwrlvl(end_pwrlvl);
- /*
- * Flush the target power level as it might be accessed on power up with
- * Data cache disabled.
- */
- psci_flush_cpu_data(psci_svc_cpu_data.target_pwrlvl);
- /*
- * Call the cpu suspend handler registered by the Secure Payload
- * Dispatcher to let it do any book-keeping. If the handler encounters an
- * error, it's expected to assert within
- */
- if ((psci_spd_pm != NULL) && (psci_spd_pm->svc_suspend != NULL))
- psci_spd_pm->svc_suspend(max_off_lvl);
- #if !HW_ASSISTED_COHERENCY
- /*
- * Plat. management: Allow the platform to perform any early
- * actions required to power down the CPU. This might be useful for
- * HW_ASSISTED_COHERENCY = 0 platforms that can safely perform these
- * actions with data caches enabled.
- */
- if (psci_plat_pm_ops->pwr_domain_suspend_pwrdown_early != NULL)
- psci_plat_pm_ops->pwr_domain_suspend_pwrdown_early(state_info);
- #endif
- /*
- * Store the re-entry information for the non-secure world.
- */
- cm_init_my_context(ep);
- #if ENABLE_RUNTIME_INSTRUMENTATION
- /*
- * Flush cache line so that even if CPU power down happens
- * the timestamp update is reflected in memory.
- */
- PMF_CAPTURE_TIMESTAMP(rt_instr_svc,
- RT_INSTR_ENTER_CFLUSH,
- PMF_CACHE_MAINT);
- #endif
- /*
- * Arch. management. Initiate power down sequence.
- * TODO : Introduce a mechanism to query the cache level to flush
- * and the cpu-ops power down to perform from the platform.
- */
- psci_pwrdown_cpu(max_off_lvl);
- #if ENABLE_RUNTIME_INSTRUMENTATION
- PMF_CAPTURE_TIMESTAMP(rt_instr_svc,
- RT_INSTR_EXIT_CFLUSH,
- PMF_NO_CACHE_MAINT);
- #endif
- }
- /*******************************************************************************
- * Top level handler which is called when a cpu wants to suspend its execution.
- * It is assumed that along with suspending the cpu power domain, power domains
- * at higher levels until the target power level will be suspended as well. It
- * coordinates with the platform to negotiate the target state for each of
- * the power domain level till the target power domain level. It then performs
- * generic, architectural, platform setup and state management required to
- * suspend that power domain level and power domain levels below it.
- * e.g. For a cpu that's to be suspended, it could mean programming the
- * power controller whereas for a cluster that's to be suspended, it will call
- * the platform specific code which will disable coherency at the interconnect
- * level if the cpu is the last in the cluster and also the program the power
- * controller.
- *
- * All the required parameter checks are performed at the beginning and after
- * the state transition has been done, no further error is expected and it is
- * not possible to undo any of the actions taken beyond that point.
- ******************************************************************************/
- int psci_cpu_suspend_start(const entry_point_info_t *ep,
- unsigned int end_pwrlvl,
- psci_power_state_t *state_info,
- unsigned int is_power_down_state)
- {
- int rc = PSCI_E_SUCCESS;
- bool skip_wfi = false;
- unsigned int idx = plat_my_core_pos();
- unsigned int parent_nodes[PLAT_MAX_PWR_LVL] = {0};
- /*
- * This function must only be called on platforms where the
- * CPU_SUSPEND platform hooks have been implemented.
- */
- assert((psci_plat_pm_ops->pwr_domain_suspend != NULL) &&
- (psci_plat_pm_ops->pwr_domain_suspend_finish != NULL));
- /* Get the parent nodes */
- psci_get_parent_pwr_domain_nodes(idx, end_pwrlvl, parent_nodes);
- /*
- * This function acquires the lock corresponding to each power
- * level so that by the time all locks are taken, the system topology
- * is snapshot and state management can be done safely.
- */
- psci_acquire_pwr_domain_locks(end_pwrlvl, parent_nodes);
- /*
- * We check if there are any pending interrupts after the delay
- * introduced by lock contention to increase the chances of early
- * detection that a wake-up interrupt has fired.
- */
- if (read_isr_el1() != 0U) {
- skip_wfi = true;
- goto exit;
- }
- #if PSCI_OS_INIT_MODE
- if (psci_suspend_mode == OS_INIT) {
- /*
- * This function validates the requested state info for
- * OS-initiated mode.
- */
- rc = psci_validate_state_coordination(end_pwrlvl, state_info);
- if (rc != PSCI_E_SUCCESS) {
- skip_wfi = true;
- goto exit;
- }
- } else {
- #endif
- /*
- * This function is passed the requested state info and
- * it returns the negotiated state info for each power level upto
- * the end level specified.
- */
- psci_do_state_coordination(end_pwrlvl, state_info);
- #if PSCI_OS_INIT_MODE
- }
- #endif
- #if PSCI_OS_INIT_MODE
- if (psci_plat_pm_ops->pwr_domain_validate_suspend != NULL) {
- rc = psci_plat_pm_ops->pwr_domain_validate_suspend(state_info);
- if (rc != PSCI_E_SUCCESS) {
- skip_wfi = true;
- goto exit;
- }
- }
- #endif
- /* Update the target state in the power domain nodes */
- psci_set_target_local_pwr_states(end_pwrlvl, state_info);
- #if ENABLE_PSCI_STAT
- /* Update the last cpu for each level till end_pwrlvl */
- psci_stats_update_pwr_down(end_pwrlvl, state_info);
- #endif
- if (is_power_down_state != 0U)
- psci_suspend_to_pwrdown_start(end_pwrlvl, ep, state_info);
- /*
- * Plat. management: Allow the platform to perform the
- * necessary actions to turn off this cpu e.g. set the
- * platform defined mailbox with the psci entrypoint,
- * program the power controller etc.
- */
- psci_plat_pm_ops->pwr_domain_suspend(state_info);
- #if ENABLE_PSCI_STAT
- plat_psci_stat_accounting_start(state_info);
- #endif
- exit:
- /*
- * Release the locks corresponding to each power level in the
- * reverse order to which they were acquired.
- */
- psci_release_pwr_domain_locks(end_pwrlvl, parent_nodes);
- if (skip_wfi) {
- return rc;
- }
- if (is_power_down_state != 0U) {
- #if ENABLE_RUNTIME_INSTRUMENTATION
- /*
- * Update the timestamp with cache off. We assume this
- * timestamp can only be read from the current CPU and the
- * timestamp cache line will be flushed before return to
- * normal world on wakeup.
- */
- PMF_CAPTURE_TIMESTAMP(rt_instr_svc,
- RT_INSTR_ENTER_HW_LOW_PWR,
- PMF_NO_CACHE_MAINT);
- #endif
- /* The function calls below must not return */
- if (psci_plat_pm_ops->pwr_domain_pwr_down_wfi != NULL)
- psci_plat_pm_ops->pwr_domain_pwr_down_wfi(state_info);
- else
- psci_power_down_wfi();
- }
- #if ENABLE_RUNTIME_INSTRUMENTATION
- PMF_CAPTURE_TIMESTAMP(rt_instr_svc,
- RT_INSTR_ENTER_HW_LOW_PWR,
- PMF_NO_CACHE_MAINT);
- #endif
- /*
- * We will reach here if only retention/standby states have been
- * requested at multiple power levels. This means that the cpu
- * context will be preserved.
- */
- wfi();
- #if ENABLE_RUNTIME_INSTRUMENTATION
- PMF_CAPTURE_TIMESTAMP(rt_instr_svc,
- RT_INSTR_EXIT_HW_LOW_PWR,
- PMF_NO_CACHE_MAINT);
- #endif
- /*
- * After we wake up from context retaining suspend, call the
- * context retaining suspend finisher.
- */
- psci_suspend_to_standby_finisher(idx, end_pwrlvl);
- return rc;
- }
- /*******************************************************************************
- * The following functions finish an earlier suspend request. They
- * are called by the common finisher routine in psci_common.c. The `state_info`
- * is the psci_power_state from which this CPU has woken up from.
- ******************************************************************************/
- void psci_cpu_suspend_finish(unsigned int cpu_idx, const psci_power_state_t *state_info)
- {
- unsigned int counter_freq;
- unsigned int max_off_lvl;
- /* Ensure we have been woken up from a suspended state */
- assert((psci_get_aff_info_state() == AFF_STATE_ON) &&
- (is_local_state_off(
- state_info->pwr_domain_state[PSCI_CPU_PWR_LVL]) != 0));
- /*
- * Plat. management: Perform the platform specific actions
- * before we change the state of the cpu e.g. enabling the
- * gic or zeroing the mailbox register. If anything goes
- * wrong then assert as there is no way to recover from this
- * situation.
- */
- psci_plat_pm_ops->pwr_domain_suspend_finish(state_info);
- #if !(HW_ASSISTED_COHERENCY || WARMBOOT_ENABLE_DCACHE_EARLY)
- /* Arch. management: Enable the data cache, stack memory maintenance. */
- psci_do_pwrup_cache_maintenance();
- #endif
- /* Re-init the cntfrq_el0 register */
- counter_freq = plat_get_syscnt_freq2();
- write_cntfrq_el0(counter_freq);
- #if ENABLE_PAUTH
- /* Store APIAKey_EL1 key */
- set_cpu_data(apiakey[0], read_apiakeylo_el1());
- set_cpu_data(apiakey[1], read_apiakeyhi_el1());
- #endif /* ENABLE_PAUTH */
- /*
- * Call the cpu suspend finish handler registered by the Secure Payload
- * Dispatcher to let it do any bookeeping. If the handler encounters an
- * error, it's expected to assert within
- */
- if ((psci_spd_pm != NULL) && (psci_spd_pm->svc_suspend_finish != NULL)) {
- max_off_lvl = psci_find_max_off_lvl(state_info);
- assert(max_off_lvl != PSCI_INVALID_PWR_LVL);
- psci_spd_pm->svc_suspend_finish(max_off_lvl);
- }
- /* Invalidate the suspend level for the cpu */
- psci_set_suspend_pwrlvl(PSCI_INVALID_PWR_LVL);
- PUBLISH_EVENT(psci_suspend_pwrdown_finish);
- }
|