diff --git a/docs/firmware-design.md b/docs/firmware-design.md index 95bb8f1..213c8ff 100644 --- a/docs/firmware-design.md +++ b/docs/firmware-design.md @@ -755,7 +755,7 @@ |`CPU_FREEZE` | No | | |`CPU_DEFAULT_SUSPEND` | No | | |`CPU_HW_STATE` | No | | -|`SYSTEM_SUSPEND` | No | | +|`SYSTEM_SUSPEND` | Yes* | | |`PSCI_SET_SUSPEND_MODE`| No | | |`PSCI_STAT_RESIDENCY` | No | | |`PSCI_STAT_COUNT` | No | | diff --git a/docs/porting-guide.md b/docs/porting-guide.md index 2f01353..0f10fd4 100644 --- a/docs/porting-guide.md +++ b/docs/porting-guide.md @@ -1238,8 +1238,8 @@ #### plat_pm_ops.affinst_suspend() Perform the platform specific setup to power off an affinity instance of the -calling CPU. It is called by the PSCI `CPU_SUSPEND` API -implementation. +calling CPU. It is called by the PSCI `CPU_SUSPEND` API and `SYSTEM_SUSPEND` +API implementation The `affinity level` (second argument) and `state` (third argument) have a similar meaning as described in the `affinst_on()` operation. They are used to @@ -1268,14 +1268,14 @@ similar meaning as described in the previous operations. The generic code expects the handler to succeed. -#### plat_pm_ops.affinst_on_suspend() +#### plat_pm_ops.affinst_suspend_finish() This function is called by the PSCI implementation after the calling CPU is powered on and released from reset in response to an asynchronous wakeup event, for example a timer interrupt that was programmed by the CPU during the -`CPU_SUSPEND` call. It performs the platform-specific setup required to -restore the saved state for this CPU to resume execution in the normal world -and also provide secure runtime firmware services. +`CPU_SUSPEND` call or `SYSTEM_SUSPEND` call. It performs the platform-specific +setup required to restore the saved state for this CPU to resume execution +in the normal world and also provide secure runtime firmware services. The `affinity level` (first argument) and `state` (second argument) have a similar meaning as described in the previous operations. The generic code @@ -1291,11 +1291,20 @@ #### plat_pm_ops.validate_ns_entrypoint() -This function is called by the PSCI implementation during the `CPU_SUSPEND` -and `CPU_ON` calls to validate the non-secure `entry_point` parameter passed -by the normal world. If the `entry_point` is known to be invalid, the platform -must return PSCI_E_INVALID_PARAMS as error, which is propagated back to the -normal world PSCI client. +This function is called by the PSCI implementation during the `CPU_SUSPEND`, +`SYSTEM_SUSPEND` and `CPU_ON` calls to validate the non-secure `entry_point` +parameter passed by the normal world. If the `entry_point` is known to be +invalid, the platform must return PSCI_E_INVALID_PARAMS as error, which is +propagated back to the normal world PSCI client. + +#### plat_pm_ops.get_sys_suspend_power_state() + +This function is called by the PSCI implementation during the `SYSTEM_SUSPEND` +call to return the `power_state` parameter. This allows the platform to encode +the appropriate State-ID field within the `power_state` parameter which can be +utilized in `affinst_suspend()` to suspend to system affinity level. The +`power_state` parameter should be in the same format as specified by the +PSCI specification for the CPU_SUSPEND API. BL3-1 platform initialization code must also detect the system topology and the state of each affinity instance in the topology. This information is diff --git a/include/bl31/services/psci.h b/include/bl31/services/psci.h index 80bc53b..dd1891c 100644 --- a/include/bl31/services/psci.h +++ b/include/bl31/services/psci.h @@ -62,6 +62,8 @@ #define PSCI_SYSTEM_OFF 0x84000008 #define PSCI_SYSTEM_RESET 0x84000009 #define PSCI_FEATURES 0x8400000A +#define PSCI_SYSTEM_SUSPEND_AARCH32 0x8400000E +#define PSCI_SYSTEM_SUSPEND_AARCH64 0xc400000E /* Macro to help build the psci capabilities bitfield */ #define define_psci_cap(x) (1 << (x & 0x1f)) @@ -69,7 +71,7 @@ /* * Number of PSCI calls (above) implemented */ -#define PSCI_NUM_CALLS 16 +#define PSCI_NUM_CALLS 18 /******************************************************************************* * PSCI Migrate and friends @@ -93,12 +95,16 @@ #define PSTATE_TYPE_STANDBY 0x0 #define PSTATE_TYPE_POWERDOWN 0x1 -#define psci_get_pstate_id(pstate) ((pstate >> PSTATE_ID_SHIFT) & \ +#define psci_get_pstate_id(pstate) (((pstate) >> PSTATE_ID_SHIFT) & \ PSTATE_ID_MASK) -#define psci_get_pstate_type(pstate) ((pstate >> PSTATE_TYPE_SHIFT) & \ +#define psci_get_pstate_type(pstate) (((pstate) >> PSTATE_TYPE_SHIFT) & \ PSTATE_TYPE_MASK) -#define psci_get_pstate_afflvl(pstate) ((pstate >> PSTATE_AFF_LVL_SHIFT) & \ +#define psci_get_pstate_afflvl(pstate) (((pstate) >> PSTATE_AFF_LVL_SHIFT) & \ PSTATE_AFF_LVL_MASK) +#define psci_make_powerstate(state_id, type, afflvl) \ + (((state_id) & PSTATE_ID_MASK) << PSTATE_ID_SHIFT) |\ + (((type) & PSTATE_TYPE_MASK) << PSTATE_TYPE_SHIFT) |\ + (((afflvl) & PSTATE_AFF_LVL_MASK) << PSTATE_AFF_LVL_SHIFT) /******************************************************************************* * PSCI CPU_FEATURES feature flag specific defines @@ -193,6 +199,7 @@ void (*system_reset)(void) __dead2; int (*validate_power_state)(unsigned int power_state); int (*validate_ns_entrypoint)(unsigned long ns_entrypoint); + unsigned int (*get_sys_suspend_power_state)(void); } plat_pm_ops_t; /******************************************************************************* diff --git a/services/std_svc/psci/psci_common.c b/services/std_svc/psci/psci_common.c index 63d8476..1b74ff2 100644 --- a/services/std_svc/psci/psci_common.c +++ b/services/std_svc/psci/psci_common.c @@ -92,6 +92,38 @@ } /******************************************************************************* + * This function verifies that the all the other cores in the system have been + * turned OFF and the current CPU is the last running CPU in the system. + * Returns 1 (true) if the current CPU is the last ON CPU or 0 (false) + * otherwise. + ******************************************************************************/ +unsigned int psci_is_last_on_cpu(void) +{ + unsigned long mpidr = read_mpidr_el1() & MPIDR_AFFINITY_MASK; + unsigned int i; + + for (i = psci_aff_limits[MPIDR_AFFLVL0].min; + i <= psci_aff_limits[MPIDR_AFFLVL0].max; i++) { + + assert(psci_aff_map[i].level == MPIDR_AFFLVL0); + + if (!(psci_aff_map[i].state & PSCI_AFF_PRESENT)) + continue; + + if (psci_aff_map[i].mpidr == mpidr) { + assert(psci_get_state(&psci_aff_map[i]) + == PSCI_STATE_ON); + continue; + } + + if (psci_get_state(&psci_aff_map[i]) != PSCI_STATE_OFF) + return 0; + } + + return 1; +} + +/******************************************************************************* * This function saves the highest affinity level which is in OFF state. The * affinity instance with which the level is associated is determined by the * caller. diff --git a/services/std_svc/psci/psci_main.c b/services/std_svc/psci/psci_main.c index fcd3b55..b389287 100644 --- a/services/std_svc/psci/psci_main.c +++ b/services/std_svc/psci/psci_main.c @@ -31,9 +31,10 @@ #include #include #include +#include +#include #include #include -#include #include "psci_private.h" /******************************************************************************* @@ -167,6 +168,62 @@ return PSCI_E_SUCCESS; } +int psci_system_suspend(unsigned long entrypoint, + unsigned long context_id) +{ + int rc; + unsigned int power_state; + entry_point_info_t ep; + + /* Validate the entrypoint using platform pm_ops */ + if (psci_plat_pm_ops->validate_ns_entrypoint) { + rc = psci_plat_pm_ops->validate_ns_entrypoint(entrypoint); + if (rc != PSCI_E_SUCCESS) { + assert(rc == PSCI_E_INVALID_PARAMS); + return PSCI_E_INVALID_PARAMS; + } + } + + /* Check if the current CPU is the last ON CPU in the system */ + if (!psci_is_last_on_cpu()) + return PSCI_E_DENIED; + + /* + * Verify and derive the re-entry information for + * the non-secure world from the non-secure state from + * where this call originated. + */ + rc = psci_get_ns_ep_info(&ep, entrypoint, context_id); + if (rc != PSCI_E_SUCCESS) + return rc; + + /* + * Assert that the required pm_ops hook is implemented to ensure that + * the capability detected during psci_setup() is valid. + */ + assert(psci_plat_pm_ops->get_sys_suspend_power_state); + + /* + * Query the platform for the power_state required for system suspend + */ + power_state = psci_plat_pm_ops->get_sys_suspend_power_state(); + + /* Save PSCI power state parameter for the core in suspend context */ + psci_set_suspend_power_state(power_state); + + /* + * Do what is needed to enter the power down state. Upon success, + * enter the final wfi which will power down this cpu. + */ + psci_afflvl_suspend(&ep, + MPIDR_AFFLVL0, + PLATFORM_MAX_AFFLVL); + + /* Reset PSCI power state parameter for the core. */ + psci_set_suspend_power_state(PSCI_INVALID_DATA); + return PSCI_E_SUCCESS; +} + int psci_cpu_off(void) { int rc; @@ -357,6 +414,9 @@ case PSCI_MIG_INFO_UP_CPU_AARCH32: SMC_RET1(handle, psci_migrate_info_up_cpu()); + case PSCI_SYSTEM_SUSPEND_AARCH32: + SMC_RET1(handle, psci_system_suspend(x1, x2)); + case PSCI_SYSTEM_OFF: psci_system_off(); /* We should never return from psci_system_off() */ @@ -390,6 +450,9 @@ case PSCI_MIG_INFO_UP_CPU_AARCH64: SMC_RET1(handle, psci_migrate_info_up_cpu()); + case PSCI_SYSTEM_SUSPEND_AARCH64: + SMC_RET1(handle, psci_system_suspend(x1, x2)); + default: break; } diff --git a/services/std_svc/psci/psci_private.h b/services/std_svc/psci/psci_private.h index 62a0efc..2955de7 100644 --- a/services/std_svc/psci/psci_private.h +++ b/services/std_svc/psci/psci_private.h @@ -69,7 +69,8 @@ define_psci_cap(PSCI_CPU_ON_AARCH64) | \ 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_MIG_INFO_UP_CPU_AARCH64) | \ + define_psci_cap(PSCI_SYSTEM_SUSPEND_AARCH64)) /******************************************************************************* @@ -102,6 +103,7 @@ ******************************************************************************/ extern const plat_pm_ops_t *psci_plat_pm_ops; extern aff_map_node_t psci_aff_map[PSCI_NUM_AFFS]; +extern aff_limits_node_t psci_aff_limits[MPIDR_MAX_AFFLVL + 1]; extern uint32_t psci_caps; /******************************************************************************* @@ -140,6 +142,7 @@ uint32_t psci_find_max_phys_off_afflvl(uint32_t start_afflvl, uint32_t end_afflvl, aff_map_node_t *mpidr_nodes[]); +unsigned int psci_is_last_on_cpu(void); int psci_spd_migrate_info(uint64_t *mpidr); /* Private exported functions from psci_setup.c */ diff --git a/services/std_svc/psci/psci_setup.c b/services/std_svc/psci/psci_setup.c index 5ff24d5..01b559c 100644 --- a/services/std_svc/psci/psci_setup.c +++ b/services/std_svc/psci/psci_setup.c @@ -55,7 +55,7 @@ * level i.e. start index and end index needs to be present. 'psci_aff_limits' * stores this information. ******************************************************************************/ -static aff_limits_node_t psci_aff_limits[MPIDR_MAX_AFFLVL + 1]; +aff_limits_node_t psci_aff_limits[MPIDR_MAX_AFFLVL + 1]; /****************************************************************************** * Define the psci capability variable. @@ -385,8 +385,12 @@ psci_caps |= define_psci_cap(PSCI_CPU_OFF); if (psci_plat_pm_ops->affinst_on && psci_plat_pm_ops->affinst_on_finish) psci_caps |= define_psci_cap(PSCI_CPU_ON_AARCH64); - if (psci_plat_pm_ops->affinst_suspend && psci_plat_pm_ops->affinst_suspend_finish) + if (psci_plat_pm_ops->affinst_suspend && + psci_plat_pm_ops->affinst_suspend_finish) { psci_caps |= define_psci_cap(PSCI_CPU_SUSPEND_AARCH64); + if (psci_plat_pm_ops->get_sys_suspend_power_state) + psci_caps |= define_psci_cap(PSCI_SYSTEM_SUSPEND_AARCH64); + } if (psci_plat_pm_ops->system_off) psci_caps |= define_psci_cap(PSCI_SYSTEM_OFF); if (psci_plat_pm_ops->system_reset)