diff --git a/Makefile b/Makefile index c7930b127..5f4a93c94 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,8 @@ SPIN_ON_BL1_EXIT := 0 PL011_GENERIC_UART := 0 # Flag to enable Performance Measurement Framework ENABLE_PMF := 0 +# Flag to enable PSCI STATs functionality +ENABLE_PSCI_STAT := 0 ################################################################################ # Checkpatch script options @@ -374,6 +376,10 @@ ifneq (${GENERATE_COT},0) endif endif +# Make sure PMF is enabled if PSCI STAT is enabled. +ifeq (${ENABLE_PSCI_STAT},1) +ENABLE_PMF := 1 +endif ################################################################################ # Auxiliary tools (fip_create, cert_create, etc) @@ -412,6 +418,7 @@ $(eval $(call assert_boolean,ENABLE_PLAT_COMPAT)) $(eval $(call assert_boolean,SPIN_ON_BL1_EXIT)) $(eval $(call assert_boolean,PL011_GENERIC_UART)) $(eval $(call assert_boolean,ENABLE_PMF)) +$(eval $(call assert_boolean,ENABLE_PSCI_STAT)) ################################################################################ @@ -440,6 +447,7 @@ $(eval $(call add_define,ENABLE_PLAT_COMPAT)) $(eval $(call add_define,SPIN_ON_BL1_EXIT)) $(eval $(call add_define,PL011_GENERIC_UART)) $(eval $(call add_define,ENABLE_PMF)) +$(eval $(call add_define,ENABLE_PSCI_STAT)) # Define the EL3_PAYLOAD_BASE flag only if it is provided. ifdef EL3_PAYLOAD_BASE $(eval $(call add_define,EL3_PAYLOAD_BASE)) @@ -451,7 +459,6 @@ else endif endif - ################################################################################ # Include BL specific makefiles ################################################################################ diff --git a/bl31/bl31.mk b/bl31/bl31.mk index 0491a1a87..8a7fccb0d 100644 --- a/bl31/bl31.mk +++ b/bl31/bl31.mk @@ -63,6 +63,10 @@ ifeq (${ENABLE_PMF}, 1) BL31_SOURCES += lib/pmf/pmf_main.c endif +ifeq (${ENABLE_PSCI_STAT}, 1) +BL31_SOURCES += services/std_svc/psci/psci_stat.c +endif + BL31_LINKERFILE := bl31/bl31.ld.S # Flag used to indicate if Crash reporting via console should be included diff --git a/docs/firmware-design.md b/docs/firmware-design.md index 87bdb884c..294349d9c 100644 --- a/docs/firmware-design.md +++ b/docs/firmware-design.md @@ -718,8 +718,8 @@ required support. |`CPU_HW_STATE` | No | | |`SYSTEM_SUSPEND` | Yes* | | |`PSCI_SET_SUSPEND_MODE`| No | | -|`PSCI_STAT_RESIDENCY` | No | | -|`PSCI_STAT_COUNT` | No | | +|`PSCI_STAT_RESIDENCY` | Yes* | | +|`PSCI_STAT_COUNT` | Yes* | | *Note : These PSCI APIs require platform power management hooks to be registered with the generic PSCI code to be supported. diff --git a/docs/porting-guide.md b/docs/porting-guide.md index 759761e30..23033d520 100644 --- a/docs/porting-guide.md +++ b/docs/porting-guide.md @@ -189,9 +189,20 @@ platform port to define additional platform porting constants in Defines the local power state corresponding to the deepest retention state possible at every power domain level in the platform. This macro should be a value less than PLAT_MAX_OFF_STATE and greater than 0. It is used by the - PSCI implementation to distuiguish between retention and power down local + PSCI implementation to distinguish between retention and power down local power states within PSCI_CPU_SUSPEND call. +* **#define : PLAT_MAX_PWR_LVL_STATES** + + Defines the maximum number of local power states per power domain level + that the platform supports. The default value of this macro is 2 since + most platforms just support a maximum of two local power states at each + power domain level (power-down and retention). If the platform needs to + account for more local power states, then it must redefine this macro. + + Currently, this macro is used by the Generic PSCI implementation to size + the array used for PSCI_STAT_COUNT/RESIDENCY accounting. + * **#define : BL1_RO_BASE** Defines the base address in secure ROM where BL1 originally lives. Must be @@ -1792,6 +1803,34 @@ domain level specific local states to suspend to system affinity level. The `pwr_domain_suspend()` will be invoked with the coordinated target state to enter system suspend. +#### plat_psci_ops.get_pwr_lvl_state_idx() + +This is an optional function and, if implemented, is invoked by the PSCI +implementation to convert the `local_state` (first argument) at a specified +`pwr_lvl` (second argument) to an index between 0 and +`PLAT_MAX_PWR_LVL_STATES` - 1. This function is only needed if the platform +supports more than two local power states at each power domain level, that is +`PLAT_MAX_PWR_LVL_STATES` is greater than 2, and needs to account for these +local power states. + +#### plat_psci_ops.translate_power_state_by_mpidr() + +This is an optional function and, if implemented, verifies the `power_state` +(second argument) parameter of the PSCI API corresponding to a target power +domain. The target power domain is identified by using both `MPIDR` (first +argument) and the power domain level encoded in `power_state`. The power domain +level specific local states are to be extracted from `power_state` and be +populated in the `output_state` (third argument) array. The functionality +is similar to the `validate_power_state` function described above and is +envisaged to be used in case the validity of `power_state` depend on the +targeted power domain. If the `power_state` is invalid for the targeted power +domain, the platform must return PSCI_E_INVALID_PARAMS as error. If this +function is not implemented, then the generic implementation relies on +`validate_power_state` function to translate the `power_state`. + +This function can also be used in case the platform wants to support local +power state encoding for `power_state` parameter of PSCI_STAT_COUNT/RESIDENCY +APIs as described in Section 5.18 of [PSCI]. 3.6 Interrupt Management framework (in BL31) ---------------------------------------------- diff --git a/docs/user-guide.md b/docs/user-guide.md index f7fcf89bc..41a272fa3 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -411,6 +411,11 @@ performed. * `ENABLE_PMF`: Boolean option to enable support for optional Performance Measurement Framework(PMF). Default is 0. +* `ENABLE_PSCI_STAT`: Boolean option to enable support for optional PSCI + functions `PSCI_STAT_RESIDENCY` and `PSCI_STAT_COUNT`. Default is 0. + Enabling this option enables the `ENABLE_PMF` build option as well. + The PMF is used for collecting the statistics. + #### ARM development platform specific build options * `ARM_TSP_RAM_LOCATION`: location of the TSP binary. Options: diff --git a/include/bl31/services/psci.h b/include/bl31/services/psci.h index 95e77809b..20aa52e97 100644 --- a/include/bl31/services/psci.h +++ b/include/bl31/services/psci.h @@ -80,6 +80,10 @@ #define PSCI_FEATURES 0x8400000A #define PSCI_SYSTEM_SUSPEND_AARCH32 0x8400000E #define PSCI_SYSTEM_SUSPEND_AARCH64 0xc400000E +#define PSCI_STAT_RESIDENCY_AARCH32 0x84000010 +#define PSCI_STAT_RESIDENCY_AARCH64 0xc4000010 +#define PSCI_STAT_COUNT_AARCH32 0x84000011 +#define PSCI_STAT_COUNT_AARCH64 0xc4000011 /* Macro to help build the psci capabilities bitfield */ #define define_psci_cap(x) (1 << (x & 0x1f)) @@ -87,7 +91,11 @@ /* * Number of PSCI calls (above) implemented */ +#if ENABLE_PSCI_STAT +#define PSCI_NUM_CALLS 22 +#else #define PSCI_NUM_CALLS 18 +#endif /******************************************************************************* * PSCI Migrate and friends @@ -274,6 +282,11 @@ typedef struct plat_psci_ops { int (*validate_ns_entrypoint)(uintptr_t ns_entrypoint); void (*get_sys_suspend_power_state)( psci_power_state_t *req_state); + int (*get_pwr_lvl_state_idx)(plat_local_state_t pwr_domain_state, + int pwrlvl); + int (*translate_power_state_by_mpidr)(u_register_t mpidr, + unsigned int power_state, + psci_power_state_t *output_state); } plat_psci_ops_t; /******************************************************************************* diff --git a/services/std_svc/psci/psci_common.c b/services/std_svc/psci/psci_common.c index 8a2b81c31..5090037e4 100644 --- a/services/std_svc/psci/psci_common.c +++ b/services/std_svc/psci/psci_common.c @@ -711,6 +711,16 @@ void psci_power_up_finish(void) psci_acquire_pwr_domain_locks(end_pwrlvl, cpu_idx); +#if ENABLE_PSCI_STAT + /* + * Capture power up time-stamp. + * No cache maintenance is required as caches are off + * and writes are direct to the main memory. + */ + PMF_CAPTURE_TIMESTAMP(psci_svc, PSCI_STAT_ID_EXIT_LOW_PWR, + PMF_NO_CACHE_MAINT); +#endif + psci_get_target_local_pwr_states(end_pwrlvl, &state_info); /* @@ -736,6 +746,16 @@ void psci_power_up_finish(void) */ psci_set_pwr_domains_to_run(end_pwrlvl); +#if ENABLE_PSCI_STAT + /* + * Update PSCI stats. + * Caches are off when writing stats data on the power down path. + * Since caches are now enabled, it's necessary to do cache + * maintenance before reading that same data. + */ + psci_stats_update_pwr_up(end_pwrlvl, &state_info, PMF_CACHE_MAINT); +#endif + /* * This loop releases the lock corresponding to each power level * in the reverse order to which they were acquired. diff --git a/services/std_svc/psci/psci_main.c b/services/std_svc/psci/psci_main.c index 68ad5f632..86f45ca39 100644 --- a/services/std_svc/psci/psci_main.c +++ b/services/std_svc/psci/psci_main.c @@ -110,11 +110,32 @@ int psci_cpu_suspend(unsigned int power_state, */ cpu_pd_state = state_info.pwr_domain_state[PSCI_CPU_PWR_LVL]; psci_set_cpu_local_state(cpu_pd_state); + +#if ENABLE_PSCI_STAT + /* + * Capture time-stamp before CPU standby + * No cache maintenance is needed as caches + * are ON through out the CPU standby operation. + */ + PMF_CAPTURE_TIMESTAMP(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR, + PMF_NO_CACHE_MAINT); +#endif + psci_plat_pm_ops->cpu_standby(cpu_pd_state); /* Upon exit from standby, set the state back to RUN. */ psci_set_cpu_local_state(PSCI_LOCAL_STATE_RUN); +#if ENABLE_PSCI_STAT + /* Capture time-stamp after CPU standby */ + PMF_CAPTURE_TIMESTAMP(psci_svc, PSCI_STAT_ID_EXIT_LOW_PWR, + PMF_NO_CACHE_MAINT); + + /* Update PSCI stats */ + psci_stats_update_pwr_up(PSCI_CPU_PWR_LVL, &state_info, + PMF_NO_CACHE_MAINT); +#endif + return PSCI_E_SUCCESS; } @@ -368,6 +389,14 @@ uint64_t psci_smc_handler(uint32_t smc_fid, case PSCI_FEATURES: SMC_RET1(handle, psci_features(x1)); +#if ENABLE_PSCI_STAT + case PSCI_STAT_RESIDENCY_AARCH32: + SMC_RET1(handle, psci_stat_residency(x1, x2)); + + case PSCI_STAT_COUNT_AARCH32: + SMC_RET1(handle, psci_stat_count(x1, x2)); +#endif + default: break; } @@ -393,6 +422,14 @@ uint64_t psci_smc_handler(uint32_t smc_fid, case PSCI_SYSTEM_SUSPEND_AARCH64: SMC_RET1(handle, psci_system_suspend(x1, x2)); +#if ENABLE_PSCI_STAT + case PSCI_STAT_RESIDENCY_AARCH64: + SMC_RET1(handle, psci_stat_residency(x1, x2)); + + case PSCI_STAT_COUNT_AARCH64: + SMC_RET1(handle, psci_stat_count(x1, x2)); +#endif + default: break; } diff --git a/services/std_svc/psci/psci_off.c b/services/std_svc/psci/psci_off.c index 686666d54..36dab4972 100644 --- a/services/std_svc/psci/psci_off.c +++ b/services/std_svc/psci/psci_off.c @@ -100,6 +100,11 @@ int psci_do_cpu_off(unsigned int end_pwrlvl) */ psci_do_state_coordination(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 + /* * Arch. management. Perform the necessary steps to flush all * cpu caches. @@ -112,6 +117,16 @@ int psci_do_cpu_off(unsigned int end_pwrlvl) */ psci_plat_pm_ops->pwr_domain_off(&state_info); +#if ENABLE_PSCI_STAT + /* + * Capture time-stamp while entering low power state. + * No cache maintenance needed because caches are off + * and writes are direct to main memory. + */ + PMF_CAPTURE_TIMESTAMP(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR, + PMF_NO_CACHE_MAINT); +#endif + exit: /* * Release the locks corresponding to each power level in the diff --git a/services/std_svc/psci/psci_private.h b/services/std_svc/psci/psci_private.h index 8a671b39d..ffb073215 100644 --- a/services/std_svc/psci/psci_private.h +++ b/services/std_svc/psci/psci_private.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,9 @@ define_psci_cap(PSCI_AFFINITY_INFO_AARCH64) | \ define_psci_cap(PSCI_MIG_AARCH64) | \ define_psci_cap(PSCI_MIG_INFO_UP_CPU_AARCH64) | \ - define_psci_cap(PSCI_SYSTEM_SUSPEND_AARCH64)) + define_psci_cap(PSCI_SYSTEM_SUSPEND_AARCH64) | \ + define_psci_cap(PSCI_STAT_RESIDENCY_AARCH64) | \ + define_psci_cap(PSCI_STAT_COUNT_AARCH64)) /* * Helper macros to get/set the fields of PSCI per-cpu data. @@ -102,6 +105,15 @@ #define is_cpu_standby_req(is_power_down_state, retn_lvl) \ (((!(is_power_down_state)) && ((retn_lvl) == 0)) ? 1 : 0) +/* Following are used as ID's to capture time-stamp */ +#define PSCI_STAT_ID_ENTER_LOW_PWR 0 +#define PSCI_STAT_ID_EXIT_LOW_PWR 1 +#define PSCI_STAT_TOTAL_IDS 2 + +/* Declare PMF service functions for PSCI */ +PMF_DECLARE_CAPTURE_TIMESTAMP(psci_svc) +PMF_DECLARE_GET_TIMESTAMP(psci_svc) + /******************************************************************************* * The following two data structures implement the power domain tree. The tree * is used to track the state of all the nodes i.e. power domain instances @@ -228,4 +240,15 @@ void psci_do_pwrup_cache_maintenance(void); void __dead2 psci_system_off(void); void __dead2 psci_system_reset(void); +/* Private exported functions from psci_stat.c */ +void psci_stats_update_pwr_down(unsigned int end_pwrlvl, + const psci_power_state_t *state_info); +void psci_stats_update_pwr_up(unsigned int end_pwrlvl, + const psci_power_state_t *state_info, + unsigned int flags); +u_register_t psci_stat_residency(u_register_t target_cpu, + unsigned int power_state); +u_register_t psci_stat_count(u_register_t target_cpu, + unsigned int power_state); + #endif /* __PSCI_PRIVATE_H__ */ diff --git a/services/std_svc/psci/psci_setup.c b/services/std_svc/psci/psci_setup.c index cd1bb0923..975b25713 100644 --- a/services/std_svc/psci/psci_setup.c +++ b/services/std_svc/psci/psci_setup.c @@ -252,5 +252,10 @@ int psci_setup(void) if (psci_plat_pm_ops->system_reset) psci_caps |= define_psci_cap(PSCI_SYSTEM_RESET); +#if ENABLE_PSCI_STAT + psci_caps |= define_psci_cap(PSCI_STAT_RESIDENCY_AARCH64); + psci_caps |= define_psci_cap(PSCI_STAT_COUNT_AARCH64); +#endif + return 0; } diff --git a/services/std_svc/psci/psci_stat.c b/services/std_svc/psci/psci_stat.c new file mode 100644 index 000000000..155bbb07d --- /dev/null +++ b/services/std_svc/psci/psci_stat.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2016, ARM Limited and Contributors. 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 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 "psci_private.h" + +#ifndef PLAT_MAX_PWR_LVL_STATES +#define PLAT_MAX_PWR_LVL_STATES 2 +#endif + +/* Ticks elapsed in one second by a signal of 1 MHz */ +#define MHZ_TICKS_PER_SEC 1000000 + +/* Following structure is used for PSCI STAT */ +typedef struct psci_stat { + u_register_t residency; + u_register_t count; +} psci_stat_t; + +/* + * Following is used to keep track of the last cpu + * that goes to power down in non cpu power domains. + */ +static int last_cpu_in_non_cpu_pd[PSCI_NUM_NON_CPU_PWR_DOMAINS] = {-1}; + +/* + * Following are used to store PSCI STAT values for + * CPU and non CPU power domains. + */ +static psci_stat_t psci_cpu_stat[PLATFORM_CORE_COUNT] + [PLAT_MAX_PWR_LVL_STATES]; +static psci_stat_t psci_non_cpu_stat[PSCI_NUM_NON_CPU_PWR_DOMAINS] + [PLAT_MAX_PWR_LVL_STATES]; + +/* Register PMF PSCI service */ +PMF_REGISTER_SERVICE(psci_svc, PMF_PSCI_STAT_SVC_ID, + PSCI_STAT_TOTAL_IDS, PMF_STORE_ENABLE) + +/* The divisor to use to convert raw timestamp into microseconds */ +u_register_t residency_div; + +/* + * This macro calculates the stats residency in microseconds, + * taking in account the wrap around condition. + */ +#define calc_stat_residency(_pwrupts, _pwrdnts, _res) \ + do { \ + if (_pwrupts < _pwrdnts) \ + _res = UINT64_MAX - _pwrdnts + _pwrupts;\ + else \ + _res = _pwrupts - _pwrdnts; \ + /* Convert timestamp into microseconds */ \ + _res = _res/residency_div; \ + } while (0) + +/* + * This functions returns the index into the `psci_stat_t` array given the + * local power state and power domain level. If the platform implements the + * `get_pwr_lvl_state_idx` pm hook, then that will be used to return the index. + */ +static int get_stat_idx(plat_local_state_t local_state, int pwr_lvl) +{ + int idx; + + if (psci_plat_pm_ops->get_pwr_lvl_state_idx == NULL) { + assert(PLAT_MAX_PWR_LVL_STATES == 2); + if (is_local_state_retn(local_state)) + return 0; + + assert(is_local_state_off(local_state)); + return 1; + } + + idx = psci_plat_pm_ops->get_pwr_lvl_state_idx(local_state, pwr_lvl); + assert((idx >= 0) && (idx < PLAT_MAX_PWR_LVL_STATES)); + return idx; +} + +/******************************************************************************* + * This function is passed the target local power states for each power + * domain (state_info) between the current CPU domain and its ancestors until + * the target power level (end_pwrlvl). + * + * Then, for each level (apart from the CPU level) until the 'end_pwrlvl', it + * updates the `last_cpu_in_non_cpu_pd[]` with last power down cpu id. + * + * This function will only be invoked with data cache enabled and while + * powering down a core. + ******************************************************************************/ +void psci_stats_update_pwr_down(unsigned int end_pwrlvl, + const psci_power_state_t *state_info) +{ + int lvl, parent_idx, cpu_idx = plat_my_core_pos(); + + assert(end_pwrlvl <= PLAT_MAX_PWR_LVL); + assert(state_info); + + parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node; + + for (lvl = PSCI_CPU_PWR_LVL + 1; lvl <= end_pwrlvl; lvl++) { + + /* Break early if the target power state is RUN */ + if (is_local_state_run(state_info->pwr_domain_state[lvl])) + break; + + /* + * The power domain is entering a low power state, so this is + * the last CPU for this power domain + */ + last_cpu_in_non_cpu_pd[parent_idx] = cpu_idx; + + parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node; + } + +} + +/******************************************************************************* + * This function updates the PSCI STATS(residency time and count) for CPU + * and NON-CPU power domains. + * It is called with caches enabled and locks acquired(for NON-CPU domain) + ******************************************************************************/ +void psci_stats_update_pwr_up(unsigned int end_pwrlvl, + const psci_power_state_t *state_info, + unsigned int flags) +{ + int parent_idx, cpu_idx = plat_my_core_pos(); + int lvl, stat_idx; + plat_local_state_t local_state; + unsigned long long pwrup_ts = 0, pwrdn_ts = 0; + u_register_t residency; + + assert(end_pwrlvl <= PLAT_MAX_PWR_LVL); + assert(state_info); + + /* Initialize the residency divisor if not already initialized */ + if (!residency_div) { + /* Pre-calculate divisor so that it can be directly used to + convert time-stamp into microseconds */ + residency_div = read_cntfrq_el0() / MHZ_TICKS_PER_SEC; + assert(residency_div); + } + + /* Get power down time-stamp for current CPU */ + PMF_GET_TIMESTAMP_BY_INDEX(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR, + cpu_idx, flags, pwrdn_ts); + + /* In the case of 1st power on just return */ + if (!pwrdn_ts) + return; + + /* Get power up time-stamp for current CPU */ + PMF_GET_TIMESTAMP_BY_INDEX(psci_svc, PSCI_STAT_ID_EXIT_LOW_PWR, + cpu_idx, flags, pwrup_ts); + + /* Get the index into the stats array */ + local_state = state_info->pwr_domain_state[PSCI_CPU_PWR_LVL]; + stat_idx = get_stat_idx(local_state, PSCI_CPU_PWR_LVL); + + /* Calculate stats residency */ + calc_stat_residency(pwrup_ts, pwrdn_ts, residency); + + /* Update CPU stats. */ + psci_cpu_stat[cpu_idx][stat_idx].residency += residency; + psci_cpu_stat[cpu_idx][stat_idx].count++; + + /* + * Check what power domains above CPU were off + * prior to this CPU powering on. + */ + parent_idx = psci_cpu_pd_nodes[cpu_idx].parent_node; + for (lvl = PSCI_CPU_PWR_LVL + 1; lvl <= end_pwrlvl; lvl++) { + local_state = state_info->pwr_domain_state[lvl]; + if (is_local_state_run(local_state)) { + /* Break early */ + break; + } + + assert(last_cpu_in_non_cpu_pd[parent_idx] != -1); + + /* Get power down time-stamp for last CPU */ + PMF_GET_TIMESTAMP_BY_INDEX(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR, + last_cpu_in_non_cpu_pd[parent_idx], + flags, pwrdn_ts); + + /* Initialize back to reset value */ + last_cpu_in_non_cpu_pd[parent_idx] = -1; + + /* Get the index into the stats array */ + stat_idx = get_stat_idx(local_state, lvl); + + /* Calculate stats residency */ + calc_stat_residency(pwrup_ts, pwrdn_ts, residency); + + /* Update non cpu stats */ + psci_non_cpu_stat[parent_idx][stat_idx].residency += residency; + psci_non_cpu_stat[parent_idx][stat_idx].count++; + + parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node; + } + +} + +/******************************************************************************* + * This function returns the appropriate count and residency time of the + * local state for the highest power level expressed in the `power_state` + * for the node represented by `target_cpu`. + ******************************************************************************/ +int psci_get_stat(u_register_t target_cpu, unsigned int power_state, + psci_stat_t *psci_stat) +{ + int rc, pwrlvl, lvl, parent_idx, stat_idx, target_idx; + psci_power_state_t state_info = { {PSCI_LOCAL_STATE_RUN} }; + plat_local_state_t local_state; + + /* Validate the target_cpu parameter and determine the cpu index */ + target_idx = plat_core_pos_by_mpidr(target_cpu); + if (target_idx == -1) + return PSCI_E_INVALID_PARAMS; + + /* Validate the power_state parameter */ + if (!psci_plat_pm_ops->translate_power_state_by_mpidr) + rc = psci_validate_power_state(power_state, &state_info); + else + rc = psci_plat_pm_ops->translate_power_state_by_mpidr( + target_cpu, power_state, &state_info); + + if (rc != PSCI_E_SUCCESS) + return PSCI_E_INVALID_PARAMS; + + /* Find the highest power level */ + pwrlvl = psci_find_target_suspend_lvl(&state_info); + if (pwrlvl == PSCI_INVALID_PWR_LVL) + return PSCI_E_INVALID_PARAMS; + + /* Get the index into the stats array */ + local_state = state_info.pwr_domain_state[pwrlvl]; + stat_idx = get_stat_idx(local_state, pwrlvl); + + if (pwrlvl > PSCI_CPU_PWR_LVL) { + /* Get the power domain index */ + parent_idx = psci_cpu_pd_nodes[target_idx].parent_node; + for (lvl = PSCI_CPU_PWR_LVL + 1; lvl < pwrlvl; lvl++) + parent_idx = psci_non_cpu_pd_nodes[parent_idx].parent_node; + + /* Get the non cpu power domain stats */ + *psci_stat = psci_non_cpu_stat[parent_idx][stat_idx]; + } else { + /* Get the cpu power domain stats */ + *psci_stat = psci_cpu_stat[target_idx][stat_idx]; + } + + return PSCI_E_SUCCESS; +} + +/* This is the top level function for PSCI_STAT_RESIDENCY SMC. */ +u_register_t psci_stat_residency(u_register_t target_cpu, + unsigned int power_state) +{ + psci_stat_t psci_stat; + + int rc = psci_get_stat(target_cpu, power_state, &psci_stat); + if (rc == PSCI_E_SUCCESS) + return psci_stat.residency; + else + return 0; +} + +/* This is the top level function for PSCI_STAT_COUNT SMC. */ +u_register_t psci_stat_count(u_register_t target_cpu, + unsigned int power_state) +{ + psci_stat_t psci_stat; + + int rc = psci_get_stat(target_cpu, power_state, &psci_stat); + if (rc == PSCI_E_SUCCESS) + return psci_stat.count; + else + return 0; +} diff --git a/services/std_svc/psci/psci_suspend.c b/services/std_svc/psci/psci_suspend.c index 8c6ab6b40..e6c8cd992 100644 --- a/services/std_svc/psci/psci_suspend.c +++ b/services/std_svc/psci/psci_suspend.c @@ -168,6 +168,11 @@ void psci_cpu_suspend_start(entry_point_info_t *ep, */ psci_do_state_coordination(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) psci_suspend_to_pwrdown_start(end_pwrlvl, ep, state_info); @@ -179,6 +184,16 @@ void psci_cpu_suspend_start(entry_point_info_t *ep, */ psci_plat_pm_ops->pwr_domain_suspend(state_info); +#if ENABLE_PSCI_STAT + /* + * Capture time-stamp while entering low power state. + * No cache maintenance needed because caches are off + * and writes are direct to main memory. + */ + PMF_CAPTURE_TIMESTAMP(psci_svc, PSCI_STAT_ID_ENTER_LOW_PWR, + PMF_NO_CACHE_MAINT); +#endif + exit: /* * Release the locks corresponding to each power level in the