PSCI: Add SYSTEM_SUSPEND API support
authorSoby Mathew <soby.mathew@arm.com>
Wed, 17 Dec 2014 14:47:57 +0000 (14:47 +0000)
committerSoby Mathew <soby.mathew@arm.com>
Mon, 22 Jun 2015 17:11:54 +0000 (18:11 +0100)
This patch adds support for SYSTEM_SUSPEND API as mentioned in the PSCI 1.0
specification. This API, on being invoked on the last running core on a
supported platform, will put the system into a low power mode with memory
retention.

The psci_afflvl_suspend() internal API has been reused as most of the actions
to suspend a system are the same as invoking the PSCI CPU_SUSPEND API with the
target affinity level as 'system'. This API needs the 'power state' parameter
for the target low power state. This parameter is not passed by the caller of
the SYSTEM_SUSPEND API. Hence, the platform needs to implement the
get_sys_suspend_power_state() platform function to provide this information.
Also, the platform also needs to add support for suspending the system to the
existing 'plat_pm_ops' functions: affinst_suspend() and
affinst_suspend_finish().

Change-Id: Ib6bf10809cb4e9b92f463755608889aedd83cef5

docs/firmware-design.md
docs/porting-guide.md
include/bl31/services/psci.h
services/std_svc/psci/psci_common.c
services/std_svc/psci/psci_main.c
services/std_svc/psci/psci_private.h
services/std_svc/psci/psci_setup.c

index 70737b531becb1cc12a854e35a2fc62c05f0b0e2..3977213045fa94cc3eee95caf103bd105e75872e 100644 (file)
@@ -759,7 +759,7 @@ required support.
 |`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      |                                           |
index 1e49deb882de211a45efa4319c8ecf9ed3cfd979..c09c2656c6280fa3cad87807e7e4a99b5992aece 100644 (file)
@@ -1211,8 +1211,8 @@ affinity level 0 (CPU), the platform port should power down affinity level 1
 #### 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
@@ -1241,14 +1241,14 @@ The `affinity level` (first argument) and `state` (second argument) have a
 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
@@ -1264,11 +1264,20 @@ world PSCI client.
 
 #### 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
index 80bc53b83506868082df521d53949881f3408e16..dd1891c6fe40b4ff948e7b615a7646b9b42b5553 100644 (file)
@@ -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
 #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 @@ typedef struct plat_pm_ops {
        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;
 
 /*******************************************************************************
index 237cf1e952fb69c99041b06db9d4588a5c7f1e73..e401609a6f0764be055b62c50152dc0ae22e1214 100644 (file)
@@ -91,6 +91,38 @@ uint32_t psci_find_max_phys_off_afflvl(uint32_t start_afflvl,
        return max_afflvl;
 }
 
+/*******************************************************************************
+ * 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
index fcd3b5526e1730eb58e211e32afd21fbe01eea76..b389287b003ea98279913afd28015dbe4047a757 100644 (file)
 #include <arch.h>
 #include <arch_helpers.h>
 #include <assert.h>
+#include <debug.h>
+#include <platform.h>
 #include <runtime_svc.h>
 #include <std_svc.h>
-#include <debug.h>
 #include "psci_private.h"
 
 /*******************************************************************************
@@ -167,6 +168,62 @@ int psci_cpu_suspend(unsigned int power_state,
        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 @@ uint64_t psci_smc_handler(uint32_t smc_fid,
                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 @@ uint64_t psci_smc_handler(uint32_t smc_fid,
                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;
                }
index 62a0efc864e71cfd453eb3601b411326e07d3592..2955de7a0f3532cf5ffcf76589f747619f86a1e5 100644 (file)
@@ -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 @@ typedef void (*afflvl_power_on_finisher_t)(aff_map_node_t *);
  ******************************************************************************/
 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 @@ void psci_set_max_phys_off_afflvl(uint32_t afflvl);
 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 */
index 5ff24d5bcaa1a19ccdc7f25d9a7c8ef5a85ab9e3..01b559cf38b5faa7708ee30a2ec2b901de4fc9ae 100644 (file)
@@ -55,7 +55,7 @@ static cpu_context_t psci_ns_context[PLATFORM_CORE_COUNT];
  * 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 @@ int32_t psci_setup(void)
                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)