diff mbox

[5/7] target-arm: add emulation of PSCI calls for system emulation

Message ID 1399305623-22016-6-git-send-email-robherring2@gmail.com
State New
Headers show

Commit Message

Rob Herring May 5, 2014, 4 p.m. UTC
From: Rob Herring <rob.herring@linaro.org>

Add support for handling PSCI calls in system emulation. Both version
0.1 and 0.2 of the PSCI spec are supported. Platforms can enable support
by setting "psci-method" QOM property on the cpus to SMC or HVC
emulation and having PSCI binding in their dtb.

Signed-off-by: Rob Herring <rob.herring@linaro.org>
---
 target-arm/Makefile.objs |   1 +
 target-arm/cpu-qom.h     |   6 ++
 target-arm/cpu.c         |   1 +
 target-arm/helper.c      |  16 ++---
 target-arm/kvm-consts.h  |   6 ++
 target-arm/psci.c        | 152 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 174 insertions(+), 8 deletions(-)
 create mode 100644 target-arm/psci.c

Comments

Peter Maydell May 14, 2014, 6:12 p.m. UTC | #1
On 5 May 2014 17:00, Rob Herring <robherring2@gmail.com> wrote:
> From: Rob Herring <rob.herring@linaro.org>
>
> Add support for handling PSCI calls in system emulation. Both version
> 0.1 and 0.2 of the PSCI spec are supported. Platforms can enable support
> by setting "psci-method" QOM property on the cpus to SMC or HVC
> emulation and having PSCI binding in their dtb.
> --- a/target-arm/cpu-qom.h
> +++ b/target-arm/cpu-qom.h
> @@ -98,6 +98,11 @@ typedef struct ARMCPU {
>      bool start_powered_off;
>      bool powered_off;
>
> +    /* PSCI emulation state
> +     * 0 - disabled, 1 - smc, 2 - hvc
> +     */
> +    uint32_t psci_method;
> +
>      /* [QEMU_]KVM_ARM_TARGET_* constant for this CPU, or
>       * QEMU_KVM_ARM_TARGET_NONE if the kernel doesn't support this CPU type.
>       */
> @@ -185,6 +190,7 @@ extern const struct VMStateDescription vmstate_arm_cpu;
>  void register_cp_regs_for_features(ARMCPU *cpu);
>  void init_cpreg_list(ARMCPU *cpu);
>
> +bool arm_handle_psci(CPUState *cs);
>  bool arm_cpu_do_hvc(CPUState *cs);
>  bool arm_cpu_do_smc(CPUState *cs);
>
> diff --git a/target-arm/cpu.c b/target-arm/cpu.c
> index 2d18a20..eb21a52 100644
> --- a/target-arm/cpu.c
> +++ b/target-arm/cpu.c
> @@ -1009,6 +1009,7 @@ static const ARMCPUInfo arm_cpus[] = {
>
>  static Property arm_cpu_properties[] = {
>      DEFINE_PROP_BOOL("start-powered-off", ARMCPU, start_powered_off, false),
> +    DEFINE_PROP_UINT32("psci-method", ARMCPU, psci_method, 0),
>      DEFINE_PROP_UINT32("midr", ARMCPU, midr, 0),
>      DEFINE_PROP_END_OF_LIST()
>  };

Andreas, does this seem reasonable, or is there a nicer way
to do an "enumeration" like QOM property I'm unaware of?
(in this case the values are "none"/"smc"/"hvc").

> diff --git a/target-arm/helper.c b/target-arm/helper.c
> index b5b4a17..637c46a 100644
> --- a/target-arm/helper.c
> +++ b/target-arm/helper.c
> @@ -3255,23 +3255,23 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
>
>  bool arm_cpu_do_hvc(CPUState *cs)
>  {
> -    bool ret;
> +    ARMCPU *cpu = ARM_CPU(cs);
>
> -    ret = arm_handle_psci(cs);
> -    if (ret) {
> -        return ret;
> +    if (cpu->psci_method == QEMU_PSCI_METHOD_HVC) {
> +        return arm_handle_psci(cs);
>      }
> +
>      return false;
>  }
>
>  bool arm_cpu_do_smc(CPUState *cs)
>  {
> -    bool ret;
> +    ARMCPU *cpu = ARM_CPU(cs);
>
> -    ret = arm_handle_psci(cs);
> -    if (ret) {
> -        return ret;
> +    if (cpu->psci_method == QEMU_PSCI_METHOD_SMC) {
> +        return arm_handle_psci(cs);
>      }
> +
>      return false;
>  }
>
> diff --git a/target-arm/kvm-consts.h b/target-arm/kvm-consts.h
> index 5cf93ab..d0a89c7 100644
> --- a/target-arm/kvm-consts.h
> +++ b/target-arm/kvm-consts.h
> @@ -91,6 +91,12 @@ MISMATCH_CHECK(QEMU_PSCI_0_2_FN64_MIGRATE, PSCI_0_2_FN64_MIGRATE)
>  MISMATCH_CHECK(QEMU_PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, \
>                 PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU)
>
> +enum {
> +    QEMU_PSCI_METHOD_DISABLED = 0,
> +    QEMU_PSCI_METHOD_SMC = 1,
> +    QEMU_PSCI_METHOD_HVC = 2,
> +};
> +
>  /* Note that KVM uses overlapping values for AArch32 and AArch64
>   * target CPU numbers. AArch32 targets:
>   */
> diff --git a/target-arm/psci.c b/target-arm/psci.c
> new file mode 100644
> index 0000000..5c66236
> --- /dev/null
> +++ b/target-arm/psci.c
> @@ -0,0 +1,152 @@
> +/*
> + * Copyright (C) 2014 - Linaro
> + * Author: Rob Herring <rob.herring@linaro.org>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; either version 2 of the License, or
> + *  (at your option) any later version.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +#include <cpu.h>
> +#include <cpu-qom.h>
> +#include <kvm-consts.h>
> +#include <sysemu/sysemu.h>
> +#include <linux/psci.h>
> +
> +#if !defined(CONFIG_USER_ONLY)

Rather than #ifdeffing the whole file out, I suggest
putting psci.o into an "obj-$(CONFIG_SOFTMMU)" line in
target-arm/Makefile.objs (the callsites are already inside
!defined(CONFIG_USER_ONLY) I think.)

> +
> +bool arm_handle_psci(CPUState *cs)
> +{

A brief comment here referencing the PSCI standard and
the "SMC calling conventions" doc would be nice, so readers
have an idea of where to look to find out what we're
implementing.

> +    ARMCPU *cpu = ARM_CPU(cs);
> +    CPUARMState *env = &cpu->env;
> +    uint64_t param[4];
> +    uint64_t context_id, mpidr;
> +    target_ulong entry;
> +    int32_t ret = 0;
> +    int i;
> +
> +    for (i = 0; i < 4; i++) {
> +        param[i] = is_a64(env) ? env->xregs[i] : env->regs[i];

This is implicitly assuming that it's always right to
zero-extend arguments in the case of a call from AArch32;
as it happens that's correct for all the current PSCI
functions, but I think it could use a comment.

> +    }
> +
> +    if ((param[0] & PSCI_0_2_64BIT) && !is_a64(env)) {
> +        ret = PSCI_RET_INVALID_PARAMS;
> +        goto err;
> +    }
> +
> +    switch (param[0]) {
> +    case PSCI_0_2_FN_PSCI_VERSION:
> +        ret = PSCI_VERSION_MAJOR(0) | PSCI_VERSION_MINOR(2);
> +        break;
> +    case PSCI_0_2_FN_MIGRATE_INFO_TYPE:
> +        ret = PSCI_0_2_TOS_MP;    /* No trusted OS */
> +        break;
> +    case PSCI_0_2_FN_AFFINITY_INFO:
> +    case PSCI_0_2_FN64_AFFINITY_INFO:
> +        mpidr = param[1];
> +
> +        switch (param[2]) {
> +        case 0:
> +            cs = qemu_get_cpu(mpidr & 0xff);
> +            if (!cs) {
> +                ret = PSCI_RET_INVALID_PARAMS;
> +                break;
> +            }
> +            cpu = ARM_CPU(cs);
> +            ret = cpu->powered_off ? 1 : 0;
> +            break;
> +        default:
> +            /* Everything above affinity level 0 is always on. */
> +            ret = 0;
> +        }
> +        break;
> +    case PSCI_0_2_FN_SYSTEM_RESET:
> +        qemu_system_reset_request();
> +        break;
> +    case PSCI_0_2_FN_SYSTEM_OFF:
> +        qemu_system_powerdown_request();
> +        break;
> +    case QEMU_PSCI_FN_CPU_ON:
> +    case PSCI_0_2_FN_CPU_ON:
> +    case PSCI_0_2_FN64_CPU_ON:
> +        mpidr = param[1];
> +        entry = param[2];
> +        context_id = param[3];
> +
> +        /* change to the cpu we are powering up */
> +        cs = qemu_get_cpu(mpidr & 0xff);

I think it would be clearer to use a different variable
for the other CPU, rather than changing cs.

> +        if (!cs) {
> +            ret = PSCI_RET_INVALID_PARAMS;
> +            break;
> +        }
> +        cpu = ARM_CPU(cs);
> +
> +        if (!cpu->powered_off) {
> +            ret = PSCI_RET_ALREADY_ON;
> +            break;
> +        }
> +
> +        /* Initialize the cpu we are turning on */
> +        cpu_reset(cs);
> +        arm_cpu_set_pc(cs, entry);
> +        cpu->powered_off = false;
> +        cs->interrupt_request |= CPU_INTERRUPT_EXITTB;
> +
> +        /* Set the context_id in r0/x0 */
> +        if (is_a64(env)) {
> +            cpu->env.xregs[0] = context_id;
> +        } else {
> +            cpu->env.regs[0] = context_id;
> +        }

If the calling CPU is in AArch32 then we're going
to restart the target CPU in AArch64 but with R0
set rather than X0. That doesn't seem right...

> +
> +        ret = 0;
> +        break;
> +    case QEMU_PSCI_FN_CPU_OFF:
> +    case PSCI_0_2_FN_CPU_OFF:
> +        cpu->powered_off = true;
> +        cs->exit_request = 1;

I need to check up on whether setting exit_request here
is right, but I don't have time just this instant. More
later :-)

> +        cs->halted = 1;
> +
> +        /* CPU_OFF should never return, but if it does return an error */
> +        ret = PSCI_RET_DENIED;
> +        break;
> +    case QEMU_PSCI_FN_CPU_SUSPEND:
> +    case PSCI_0_2_FN_CPU_SUSPEND:
> +    case PSCI_0_2_FN64_CPU_SUSPEND:
> +        /* Affinity levels are not supported in QEMU */
> +        if (param[1] & 0xfffe0000) {
> +            ret = PSCI_RET_INVALID_PARAMS;
> +            break;
> +        }
> +        /* Powerdown is not supported, we always go into WFI */
> +        cs->halted = 1;
> +        cs->exit_request = 1;
> +
> +        /* Return success when we wakeup */
> +        ret = 0;
> +        break;
> +    case QEMU_PSCI_FN_MIGRATE:
> +    case PSCI_0_2_FN_MIGRATE:
> +        ret = PSCI_RET_NOT_SUPPORTED;
> +        break;
> +    default:
> +        return false;
> +    }
> +
> +err:
> +    if (is_a64(env)) {
> +        env->xregs[0] = ret;
> +    } else {
> +        env->regs[0] = ret;
> +    }
> +    return true;
> +}
> +#endif
> --
> 1.9.1

thanks
-- PMM
Rob Herring May 15, 2014, 12:08 a.m. UTC | #2
On Wed, May 14, 2014 at 1:12 PM, Peter Maydell <peter.maydell@linaro.org> wrote:
> On 5 May 2014 17:00, Rob Herring <robherring2@gmail.com> wrote:
>> From: Rob Herring <rob.herring@linaro.org>
>>
>> Add support for handling PSCI calls in system emulation. Both version
>> 0.1 and 0.2 of the PSCI spec are supported. Platforms can enable support
>> by setting "psci-method" QOM property on the cpus to SMC or HVC
>> emulation and having PSCI binding in their dtb.

[...]

>> +        /* Initialize the cpu we are turning on */
>> +        cpu_reset(cs);
>> +        arm_cpu_set_pc(cs, entry);
>> +        cpu->powered_off = false;
>> +        cs->interrupt_request |= CPU_INTERRUPT_EXITTB;
>> +
>> +        /* Set the context_id in r0/x0 */
>> +        if (is_a64(env)) {
>> +            cpu->env.xregs[0] = context_id;
>> +        } else {
>> +            cpu->env.regs[0] = context_id;
>> +        }
>
> If the calling CPU is in AArch32 then we're going
> to restart the target CPU in AArch64 but with R0
> set rather than X0. That doesn't seem right...

Probably just one on many issues to support A32 EL1... Since the core
is being reset and register state is undefined, I can just do:

cpu->env.xregs[0] = cpu->env.regs[0] = context_id;

EL2/3 support will then further complicate things (or just remove all
this code).

>> +
>> +        ret = 0;
>> +        break;
>> +    case QEMU_PSCI_FN_CPU_OFF:
>> +    case PSCI_0_2_FN_CPU_OFF:
>> +        cpu->powered_off = true;
>> +        cs->exit_request = 1;
>
> I need to check up on whether setting exit_request here
> is right, but I don't have time just this instant. More
> later :-)

IIRC, things did not work without it.

Rob
diff mbox

Patch

diff --git a/target-arm/Makefile.objs b/target-arm/Makefile.objs
index dcd167e..deda9f4 100644
--- a/target-arm/Makefile.objs
+++ b/target-arm/Makefile.objs
@@ -7,5 +7,6 @@  obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
 obj-y += translate.o op_helper.o helper.o cpu.o
 obj-y += neon_helper.o iwmmxt_helper.o
 obj-y += gdbstub.o
+obj-y += psci.o
 obj-$(TARGET_AARCH64) += cpu64.o translate-a64.o helper-a64.o gdbstub64.o
 obj-y += crypto_helper.o
diff --git a/target-arm/cpu-qom.h b/target-arm/cpu-qom.h
index 88aaf6a..2905525 100644
--- a/target-arm/cpu-qom.h
+++ b/target-arm/cpu-qom.h
@@ -98,6 +98,11 @@  typedef struct ARMCPU {
     bool start_powered_off;
     bool powered_off;
 
+    /* PSCI emulation state
+     * 0 - disabled, 1 - smc, 2 - hvc
+     */
+    uint32_t psci_method;
+
     /* [QEMU_]KVM_ARM_TARGET_* constant for this CPU, or
      * QEMU_KVM_ARM_TARGET_NONE if the kernel doesn't support this CPU type.
      */
@@ -185,6 +190,7 @@  extern const struct VMStateDescription vmstate_arm_cpu;
 void register_cp_regs_for_features(ARMCPU *cpu);
 void init_cpreg_list(ARMCPU *cpu);
 
+bool arm_handle_psci(CPUState *cs);
 bool arm_cpu_do_hvc(CPUState *cs);
 bool arm_cpu_do_smc(CPUState *cs);
 
diff --git a/target-arm/cpu.c b/target-arm/cpu.c
index 2d18a20..eb21a52 100644
--- a/target-arm/cpu.c
+++ b/target-arm/cpu.c
@@ -1009,6 +1009,7 @@  static const ARMCPUInfo arm_cpus[] = {
 
 static Property arm_cpu_properties[] = {
     DEFINE_PROP_BOOL("start-powered-off", ARMCPU, start_powered_off, false),
+    DEFINE_PROP_UINT32("psci-method", ARMCPU, psci_method, 0),
     DEFINE_PROP_UINT32("midr", ARMCPU, midr, 0),
     DEFINE_PROP_END_OF_LIST()
 };
diff --git a/target-arm/helper.c b/target-arm/helper.c
index b5b4a17..637c46a 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -3255,23 +3255,23 @@  void arm_v7m_cpu_do_interrupt(CPUState *cs)
 
 bool arm_cpu_do_hvc(CPUState *cs)
 {
-    bool ret;
+    ARMCPU *cpu = ARM_CPU(cs);
 
-    ret = arm_handle_psci(cs);
-    if (ret) {
-        return ret;
+    if (cpu->psci_method == QEMU_PSCI_METHOD_HVC) {
+        return arm_handle_psci(cs);
     }
+
     return false;
 }
 
 bool arm_cpu_do_smc(CPUState *cs)
 {
-    bool ret;
+    ARMCPU *cpu = ARM_CPU(cs);
 
-    ret = arm_handle_psci(cs);
-    if (ret) {
-        return ret;
+    if (cpu->psci_method == QEMU_PSCI_METHOD_SMC) {
+        return arm_handle_psci(cs);
     }
+
     return false;
 }
 
diff --git a/target-arm/kvm-consts.h b/target-arm/kvm-consts.h
index 5cf93ab..d0a89c7 100644
--- a/target-arm/kvm-consts.h
+++ b/target-arm/kvm-consts.h
@@ -91,6 +91,12 @@  MISMATCH_CHECK(QEMU_PSCI_0_2_FN64_MIGRATE, PSCI_0_2_FN64_MIGRATE)
 MISMATCH_CHECK(QEMU_PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, \
                PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU)
 
+enum {
+    QEMU_PSCI_METHOD_DISABLED = 0,
+    QEMU_PSCI_METHOD_SMC = 1,
+    QEMU_PSCI_METHOD_HVC = 2,
+};
+
 /* Note that KVM uses overlapping values for AArch32 and AArch64
  * target CPU numbers. AArch32 targets:
  */
diff --git a/target-arm/psci.c b/target-arm/psci.c
new file mode 100644
index 0000000..5c66236
--- /dev/null
+++ b/target-arm/psci.c
@@ -0,0 +1,152 @@ 
+/*
+ * Copyright (C) 2014 - Linaro
+ * Author: Rob Herring <rob.herring@linaro.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <cpu.h>
+#include <cpu-qom.h>
+#include <kvm-consts.h>
+#include <sysemu/sysemu.h>
+#include <linux/psci.h>
+
+#if !defined(CONFIG_USER_ONLY)
+
+bool arm_handle_psci(CPUState *cs)
+{
+    ARMCPU *cpu = ARM_CPU(cs);
+    CPUARMState *env = &cpu->env;
+    uint64_t param[4];
+    uint64_t context_id, mpidr;
+    target_ulong entry;
+    int32_t ret = 0;
+    int i;
+
+    for (i = 0; i < 4; i++) {
+        param[i] = is_a64(env) ? env->xregs[i] : env->regs[i];
+    }
+
+    if ((param[0] & PSCI_0_2_64BIT) && !is_a64(env)) {
+        ret = PSCI_RET_INVALID_PARAMS;
+        goto err;
+    }
+
+    switch (param[0]) {
+    case PSCI_0_2_FN_PSCI_VERSION:
+        ret = PSCI_VERSION_MAJOR(0) | PSCI_VERSION_MINOR(2);
+        break;
+    case PSCI_0_2_FN_MIGRATE_INFO_TYPE:
+        ret = PSCI_0_2_TOS_MP;    /* No trusted OS */
+        break;
+    case PSCI_0_2_FN_AFFINITY_INFO:
+    case PSCI_0_2_FN64_AFFINITY_INFO:
+        mpidr = param[1];
+
+        switch (param[2]) {
+        case 0:
+            cs = qemu_get_cpu(mpidr & 0xff);
+            if (!cs) {
+                ret = PSCI_RET_INVALID_PARAMS;
+                break;
+            }
+            cpu = ARM_CPU(cs);
+            ret = cpu->powered_off ? 1 : 0;
+            break;
+        default:
+            /* Everything above affinity level 0 is always on. */
+            ret = 0;
+        }
+        break;
+    case PSCI_0_2_FN_SYSTEM_RESET:
+        qemu_system_reset_request();
+        break;
+    case PSCI_0_2_FN_SYSTEM_OFF:
+        qemu_system_powerdown_request();
+        break;
+    case QEMU_PSCI_FN_CPU_ON:
+    case PSCI_0_2_FN_CPU_ON:
+    case PSCI_0_2_FN64_CPU_ON:
+        mpidr = param[1];
+        entry = param[2];
+        context_id = param[3];
+
+        /* change to the cpu we are powering up */
+        cs = qemu_get_cpu(mpidr & 0xff);
+        if (!cs) {
+            ret = PSCI_RET_INVALID_PARAMS;
+            break;
+        }
+        cpu = ARM_CPU(cs);
+
+        if (!cpu->powered_off) {
+            ret = PSCI_RET_ALREADY_ON;
+            break;
+        }
+
+        /* Initialize the cpu we are turning on */
+        cpu_reset(cs);
+        arm_cpu_set_pc(cs, entry);
+        cpu->powered_off = false;
+        cs->interrupt_request |= CPU_INTERRUPT_EXITTB;
+
+        /* Set the context_id in r0/x0 */
+        if (is_a64(env)) {
+            cpu->env.xregs[0] = context_id;
+        } else {
+            cpu->env.regs[0] = context_id;
+        }
+
+        ret = 0;
+        break;
+    case QEMU_PSCI_FN_CPU_OFF:
+    case PSCI_0_2_FN_CPU_OFF:
+        cpu->powered_off = true;
+        cs->exit_request = 1;
+        cs->halted = 1;
+
+        /* CPU_OFF should never return, but if it does return an error */
+        ret = PSCI_RET_DENIED;
+        break;
+    case QEMU_PSCI_FN_CPU_SUSPEND:
+    case PSCI_0_2_FN_CPU_SUSPEND:
+    case PSCI_0_2_FN64_CPU_SUSPEND:
+        /* Affinity levels are not supported in QEMU */
+        if (param[1] & 0xfffe0000) {
+            ret = PSCI_RET_INVALID_PARAMS;
+            break;
+        }
+        /* Powerdown is not supported, we always go into WFI */
+        cs->halted = 1;
+        cs->exit_request = 1;
+
+        /* Return success when we wakeup */
+        ret = 0;
+        break;
+    case QEMU_PSCI_FN_MIGRATE:
+    case PSCI_0_2_FN_MIGRATE:
+        ret = PSCI_RET_NOT_SUPPORTED;
+        break;
+    default:
+        return false;
+    }
+
+err:
+    if (is_a64(env)) {
+        env->xregs[0] = ret;
+    } else {
+        env->regs[0] = ret;
+    }
+    return true;
+}
+#endif