diff --git a/src/arch/arm64/armv8/secmon/psci.c b/src/arch/arm64/armv8/secmon/psci.c index 93c5bddcb9..3ba7e73e61 100644 --- a/src/arch/arm64/armv8/secmon/psci.c +++ b/src/arch/arm64/armv8/secmon/psci.c @@ -174,15 +174,18 @@ static void psci_cpu_on_callback(void *arg) e->cpu_state.startup.arg, &state); } -static void psci_cpu_on_prepare(struct psci_node *e, const struct cpu_action *a) +static void psci_cpu_on_prepare(struct psci_cmd *cmd, + const struct cpu_action *a) { struct psci_node *ancestor; + struct psci_node *e; int state = PSCI_STATE_ON_PENDING; + e = cmd->target; e->cpu_state.startup = *a; ancestor = psci_find_ancestor(e, PSCI_AFFINITY_LEVEL_HIGHEST, state); e->cpu_state.ancestor = ancestor; - psci_set_hierarchy_state(e, ancestor, state); + cmd->ancestor = ancestor; } static int psci_schedule_cpu_on(struct psci_node *e) @@ -206,6 +209,9 @@ static int psci_schedule_cpu_on(struct psci_node *e) void psci_turn_on_self(const struct cpu_action *action) { struct psci_node *e = node_self(); + struct psci_cmd cmd = { + .type = PSCI_CMD_ON, + }; if (e == NULL) { printk(BIOS_ERR, "Couldn't turn on self: mpidr %llx\n", @@ -213,8 +219,11 @@ void psci_turn_on_self(const struct cpu_action *action) return; } + cmd.target = e; + psci_lock(); - psci_cpu_on_prepare(e, action); + psci_cpu_on_prepare(&cmd, action); + psci_set_hierarchy_state(e, cmd.ancestor, PSCI_STATE_ON_PENDING); psci_unlock(); psci_schedule_cpu_on(e); @@ -235,8 +244,12 @@ static void psci_cpu_on(struct psci_func *pf) uint64_t target_mpidr; uint64_t context_id; int cpu_state; + int ret; struct psci_node *e; struct cpu_action action; + struct psci_cmd cmd = { + .type = PSCI_CMD_ON, + }; target_mpidr = psci64_arg(pf, PSCI_PARAM_0); entry = psci64_arg(pf, PSCI_PARAM_1); @@ -262,28 +275,71 @@ static void psci_cpu_on(struct psci_func *pf) return; } + cmd.target = e; action.run = (void *)entry; action.arg = (void *)context_id; - psci_cpu_on_prepare(e, &action); + psci_cpu_on_prepare(&cmd, &action); + + ret = soc_psci_ops.cmd_prepare(&cmd); + + if (ret == PSCI_RET_SUCCESS) + psci_set_hierarchy_state(e, cmd.ancestor, + PSCI_STATE_ON_PENDING); + psci_unlock(); + if (ret != PSCI_RET_SUCCESS) + return psci32_return(pf, ret); + + ret = soc_psci_ops.cmd_commit(&cmd); + + if (ret != PSCI_RET_SUCCESS) { + psci_lock(); + psci_set_hierarchy_state(e, cmd.ancestor, PSCI_STATE_OFF); + psci_unlock(); + return psci32_return(pf, ret); + } + psci32_return(pf, psci_schedule_cpu_on(e)); } static int psci_turn_off_node(struct psci_node *e, int level, int state_id) { - struct psci_node *ancestor; + int ret; + struct psci_cmd cmd = { + .type = PSCI_CMD_OFF, + .state_id = state_id, + .target = e, + }; psci_lock(); - ancestor = psci_find_ancestor(e, level, PSCI_STATE_OFF); - psci_set_hierarchy_state(e, ancestor, PSCI_STATE_OFF); + + cmd.ancestor = psci_find_ancestor(e, level, PSCI_STATE_OFF); + + ret = soc_psci_ops.cmd_prepare(&cmd); + + if (ret == PSCI_RET_SUCCESS) + psci_set_hierarchy_state(e, cmd.ancestor, PSCI_STATE_OFF); + psci_unlock(); - /* TODO(adurbin): writeback cache and actually turn off CPU. */ - secmon_trampoline(&secmon_wait_for_action, NULL); + if (ret != PSCI_RET_SUCCESS) + return ret; - return PSCI_RET_SUCCESS; + /* Should never return. */ + ret = soc_psci_ops.cmd_commit(&cmd); + + /* Adjust ret to be an error. */ + if (ret == PSCI_RET_SUCCESS) + ret = PSCI_RET_INTERNAL_FAILURE; + + /* Turn things back on. */ + psci_lock(); + psci_set_hierarchy_state(e, cmd.ancestor, PSCI_STATE_ON); + psci_unlock(); + + return ret; } int psci_turn_off_self(void) diff --git a/src/arch/arm64/include/arch/psci.h b/src/arch/arm64/include/arch/psci.h index 555333a44d..b408f3d28c 100644 --- a/src/arch/arm64/include/arch/psci.h +++ b/src/arch/arm64/include/arch/psci.h @@ -104,12 +104,47 @@ static inline int psci_root_node(const struct psci_node *n) return psci_node_parent(n) == NULL; } +enum { + PSCI_CMD_ON, + PSCI_CMD_OFF, + PSCI_CMD_STANDBY, +}; + +/* + * PSCI actions are serialized into a command for the SoC to process. There are + * 2 phases of a command being processed: prepare and commit. The prepare() is + * called with the PSCI locks held for the state of the PSCI nodes. If + * successful, the appropriate locks will be dropped and commit() will be + * called with the same structure. It is permissible for the SoC support code + * to modify the struture passed in (e.g. to update the requested state_id to + * reflect dynamic constraints on how deep of a state to enter). + */ +struct psci_cmd { + /* Command type. */ + int type; + /* + * PSCI state id for PSCI_CMD_OFF and PSCI_CMD_STANDBY commands. + * A value of -1 indicates a CPU_OFF request. + */ + int state_id; + /* + * target is the command's target, but it can affect up to the + * ancestor entity. If target == ancestor then it only affects + * target, otherwise all entites up the hierarchy including ancestor. + */ + struct psci_node *target; + struct psci_node *ancestor; +}; + struct psci_soc_ops { /* * Return number of entities one level below given parent affinitly * level and mpidr. */ size_t (*children_at_level)(int parent_level, uint64_t mpidr); + + int (*cmd_prepare)(struct psci_cmd *cmd); + int (*cmd_commit)(struct psci_cmd *cmd); }; /* Each SoC needs to provide the functions in the psci_soc_ops structure. */ diff --git a/src/soc/nvidia/tegra132/psci.c b/src/soc/nvidia/tegra132/psci.c index 6ba39e09b5..0b5d793aaf 100644 --- a/src/soc/nvidia/tegra132/psci.c +++ b/src/soc/nvidia/tegra132/psci.c @@ -45,6 +45,18 @@ static size_t children_at_level(int parent_level, uint64_t mpidr) } } +static int cmd_prepare(struct psci_cmd *cmd) +{ + return PSCI_RET_NOT_SUPPORTED; +} + +static int cmd_commit(struct psci_cmd *cmd) +{ + return PSCI_RET_NOT_SUPPORTED; +} + struct psci_soc_ops soc_psci_ops = { .children_at_level = &children_at_level, + .cmd_prepare = &cmd_prepare, + .cmd_commit = &cmd_commit, };