| From 85bd0ba1ff9875798fad94218b627ea9f768f3c3 Mon Sep 17 00:00:00 2001 |
| From: Marc Zyngier <marc.zyngier@arm.com> |
| Date: Sun, 21 Jan 2018 16:42:56 +0000 |
| Subject: arm/arm64: KVM: Add PSCI version selection API |
| |
| From: Marc Zyngier <marc.zyngier@arm.com> |
| |
| commit 85bd0ba1ff9875798fad94218b627ea9f768f3c3 upstream. |
| |
| Although we've implemented PSCI 0.1, 0.2 and 1.0, we expose either 0.1 |
| or 1.0 to a guest, defaulting to the latest version of the PSCI |
| implementation that is compatible with the requested version. This is |
| no different from doing a firmware upgrade on KVM. |
| |
| But in order to give a chance to hypothetical badly implemented guests |
| that would have a fit by discovering something other than PSCI 0.2, |
| let's provide a new API that allows userspace to pick one particular |
| version of the API. |
| |
| This is implemented as a new class of "firmware" registers, where |
| we expose the PSCI version. This allows the PSCI version to be |
| save/restored as part of a guest migration, and also set to |
| any supported version if the guest requires it. |
| |
| Cc: stable@vger.kernel.org #4.16 |
| Reviewed-by: Christoffer Dall <cdall@kernel.org> |
| Signed-off-by: Marc Zyngier <marc.zyngier@arm.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| Documentation/virtual/kvm/api.txt | 9 ++++ |
| Documentation/virtual/kvm/arm/psci.txt | 30 ++++++++++++++++ |
| arch/arm/include/asm/kvm_host.h | 3 + |
| arch/arm/include/uapi/asm/kvm.h | 6 +++ |
| arch/arm/kvm/guest.c | 13 +++++++ |
| arch/arm64/include/asm/kvm_host.h | 3 + |
| arch/arm64/include/uapi/asm/kvm.h | 6 +++ |
| arch/arm64/kvm/guest.c | 14 +++++++ |
| include/kvm/arm_psci.h | 16 +++++++- |
| virt/kvm/arm/psci.c | 60 +++++++++++++++++++++++++++++++++ |
| 10 files changed, 156 insertions(+), 4 deletions(-) |
| |
| --- a/Documentation/virtual/kvm/api.txt |
| +++ b/Documentation/virtual/kvm/api.txt |
| @@ -1960,6 +1960,9 @@ ARM 32-bit VFP control registers have th |
| ARM 64-bit FP registers have the following id bit patterns: |
| 0x4030 0000 0012 0 <regno:12> |
| |
| +ARM firmware pseudo-registers have the following bit pattern: |
| + 0x4030 0000 0014 <regno:16> |
| + |
| |
| arm64 registers are mapped using the lower 32 bits. The upper 16 of |
| that is the register group type, or coprocessor number: |
| @@ -1976,6 +1979,9 @@ arm64 CCSIDR registers are demultiplexed |
| arm64 system registers have the following id bit patterns: |
| 0x6030 0000 0013 <op0:2> <op1:3> <crn:4> <crm:4> <op2:3> |
| |
| +arm64 firmware pseudo-registers have the following bit pattern: |
| + 0x6030 0000 0014 <regno:16> |
| + |
| |
| MIPS registers are mapped using the lower 32 bits. The upper 16 of that is |
| the register group type: |
| @@ -2510,7 +2516,8 @@ Possible features: |
| and execute guest code when KVM_RUN is called. |
| - KVM_ARM_VCPU_EL1_32BIT: Starts the CPU in a 32bit mode. |
| Depends on KVM_CAP_ARM_EL1_32BIT (arm64 only). |
| - - KVM_ARM_VCPU_PSCI_0_2: Emulate PSCI v0.2 for the CPU. |
| + - KVM_ARM_VCPU_PSCI_0_2: Emulate PSCI v0.2 (or a future revision |
| + backward compatible with v0.2) for the CPU. |
| Depends on KVM_CAP_ARM_PSCI_0_2. |
| - KVM_ARM_VCPU_PMU_V3: Emulate PMUv3 for the CPU. |
| Depends on KVM_CAP_ARM_PMU_V3. |
| --- /dev/null |
| +++ b/Documentation/virtual/kvm/arm/psci.txt |
| @@ -0,0 +1,30 @@ |
| +KVM implements the PSCI (Power State Coordination Interface) |
| +specification in order to provide services such as CPU on/off, reset |
| +and power-off to the guest. |
| + |
| +The PSCI specification is regularly updated to provide new features, |
| +and KVM implements these updates if they make sense from a virtualization |
| +point of view. |
| + |
| +This means that a guest booted on two different versions of KVM can |
| +observe two different "firmware" revisions. This could cause issues if |
| +a given guest is tied to a particular PSCI revision (unlikely), or if |
| +a migration causes a different PSCI version to be exposed out of the |
| +blue to an unsuspecting guest. |
| + |
| +In order to remedy this situation, KVM exposes a set of "firmware |
| +pseudo-registers" that can be manipulated using the GET/SET_ONE_REG |
| +interface. These registers can be saved/restored by userspace, and set |
| +to a convenient value if required. |
| + |
| +The following register is defined: |
| + |
| +* KVM_REG_ARM_PSCI_VERSION: |
| + |
| + - Only valid if the vcpu has the KVM_ARM_VCPU_PSCI_0_2 feature set |
| + (and thus has already been initialized) |
| + - Returns the current PSCI version on GET_ONE_REG (defaulting to the |
| + highest PSCI version implemented by KVM and compatible with v0.2) |
| + - Allows any PSCI version implemented by KVM and compatible with |
| + v0.2 to be set with SET_ONE_REG |
| + - Affects the whole VM (even if the register view is per-vcpu) |
| --- a/arch/arm/include/asm/kvm_host.h |
| +++ b/arch/arm/include/asm/kvm_host.h |
| @@ -77,6 +77,9 @@ struct kvm_arch { |
| /* Interrupt controller */ |
| struct vgic_dist vgic; |
| int max_vcpus; |
| + |
| + /* Mandated version of PSCI */ |
| + u32 psci_version; |
| }; |
| |
| #define KVM_NR_MEM_OBJS 40 |
| --- a/arch/arm/include/uapi/asm/kvm.h |
| +++ b/arch/arm/include/uapi/asm/kvm.h |
| @@ -186,6 +186,12 @@ struct kvm_arch_memory_slot { |
| #define KVM_REG_ARM_VFP_FPINST 0x1009 |
| #define KVM_REG_ARM_VFP_FPINST2 0x100A |
| |
| +/* KVM-as-firmware specific pseudo-registers */ |
| +#define KVM_REG_ARM_FW (0x0014 << KVM_REG_ARM_COPROC_SHIFT) |
| +#define KVM_REG_ARM_FW_REG(r) (KVM_REG_ARM | KVM_REG_SIZE_U64 | \ |
| + KVM_REG_ARM_FW | ((r) & 0xffff)) |
| +#define KVM_REG_ARM_PSCI_VERSION KVM_REG_ARM_FW_REG(0) |
| + |
| /* Device Control API: ARM VGIC */ |
| #define KVM_DEV_ARM_VGIC_GRP_ADDR 0 |
| #define KVM_DEV_ARM_VGIC_GRP_DIST_REGS 1 |
| --- a/arch/arm/kvm/guest.c |
| +++ b/arch/arm/kvm/guest.c |
| @@ -22,6 +22,7 @@ |
| #include <linux/module.h> |
| #include <linux/vmalloc.h> |
| #include <linux/fs.h> |
| +#include <kvm/arm_psci.h> |
| #include <asm/cputype.h> |
| #include <linux/uaccess.h> |
| #include <asm/kvm.h> |
| @@ -176,6 +177,7 @@ static unsigned long num_core_regs(void) |
| unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu) |
| { |
| return num_core_regs() + kvm_arm_num_coproc_regs(vcpu) |
| + + kvm_arm_get_fw_num_regs(vcpu) |
| + NUM_TIMER_REGS; |
| } |
| |
| @@ -196,6 +198,11 @@ int kvm_arm_copy_reg_indices(struct kvm_ |
| uindices++; |
| } |
| |
| + ret = kvm_arm_copy_fw_reg_indices(vcpu, uindices); |
| + if (ret) |
| + return ret; |
| + uindices += kvm_arm_get_fw_num_regs(vcpu); |
| + |
| ret = copy_timer_indices(vcpu, uindices); |
| if (ret) |
| return ret; |
| @@ -214,6 +221,9 @@ int kvm_arm_get_reg(struct kvm_vcpu *vcp |
| if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE) |
| return get_core_reg(vcpu, reg); |
| |
| + if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_FW) |
| + return kvm_arm_get_fw_reg(vcpu, reg); |
| + |
| if (is_timer_reg(reg->id)) |
| return get_timer_reg(vcpu, reg); |
| |
| @@ -230,6 +240,9 @@ int kvm_arm_set_reg(struct kvm_vcpu *vcp |
| if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE) |
| return set_core_reg(vcpu, reg); |
| |
| + if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_FW) |
| + return kvm_arm_set_fw_reg(vcpu, reg); |
| + |
| if (is_timer_reg(reg->id)) |
| return set_timer_reg(vcpu, reg); |
| |
| --- a/arch/arm64/include/asm/kvm_host.h |
| +++ b/arch/arm64/include/asm/kvm_host.h |
| @@ -75,6 +75,9 @@ struct kvm_arch { |
| |
| /* Interrupt controller */ |
| struct vgic_dist vgic; |
| + |
| + /* Mandated version of PSCI */ |
| + u32 psci_version; |
| }; |
| |
| #define KVM_NR_MEM_OBJS 40 |
| --- a/arch/arm64/include/uapi/asm/kvm.h |
| +++ b/arch/arm64/include/uapi/asm/kvm.h |
| @@ -206,6 +206,12 @@ struct kvm_arch_memory_slot { |
| #define KVM_REG_ARM_TIMER_CNT ARM64_SYS_REG(3, 3, 14, 3, 2) |
| #define KVM_REG_ARM_TIMER_CVAL ARM64_SYS_REG(3, 3, 14, 0, 2) |
| |
| +/* KVM-as-firmware specific pseudo-registers */ |
| +#define KVM_REG_ARM_FW (0x0014 << KVM_REG_ARM_COPROC_SHIFT) |
| +#define KVM_REG_ARM_FW_REG(r) (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | \ |
| + KVM_REG_ARM_FW | ((r) & 0xffff)) |
| +#define KVM_REG_ARM_PSCI_VERSION KVM_REG_ARM_FW_REG(0) |
| + |
| /* Device Control API: ARM VGIC */ |
| #define KVM_DEV_ARM_VGIC_GRP_ADDR 0 |
| #define KVM_DEV_ARM_VGIC_GRP_DIST_REGS 1 |
| --- a/arch/arm64/kvm/guest.c |
| +++ b/arch/arm64/kvm/guest.c |
| @@ -25,6 +25,7 @@ |
| #include <linux/module.h> |
| #include <linux/vmalloc.h> |
| #include <linux/fs.h> |
| +#include <kvm/arm_psci.h> |
| #include <asm/cputype.h> |
| #include <linux/uaccess.h> |
| #include <asm/kvm.h> |
| @@ -205,7 +206,7 @@ static int get_timer_reg(struct kvm_vcpu |
| unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu) |
| { |
| return num_core_regs() + kvm_arm_num_sys_reg_descs(vcpu) |
| - + NUM_TIMER_REGS; |
| + + kvm_arm_get_fw_num_regs(vcpu) + NUM_TIMER_REGS; |
| } |
| |
| /** |
| @@ -225,6 +226,11 @@ int kvm_arm_copy_reg_indices(struct kvm_ |
| uindices++; |
| } |
| |
| + ret = kvm_arm_copy_fw_reg_indices(vcpu, uindices); |
| + if (ret) |
| + return ret; |
| + uindices += kvm_arm_get_fw_num_regs(vcpu); |
| + |
| ret = copy_timer_indices(vcpu, uindices); |
| if (ret) |
| return ret; |
| @@ -243,6 +249,9 @@ int kvm_arm_get_reg(struct kvm_vcpu *vcp |
| if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE) |
| return get_core_reg(vcpu, reg); |
| |
| + if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_FW) |
| + return kvm_arm_get_fw_reg(vcpu, reg); |
| + |
| if (is_timer_reg(reg->id)) |
| return get_timer_reg(vcpu, reg); |
| |
| @@ -259,6 +268,9 @@ int kvm_arm_set_reg(struct kvm_vcpu *vcp |
| if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE) |
| return set_core_reg(vcpu, reg); |
| |
| + if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_FW) |
| + return kvm_arm_set_fw_reg(vcpu, reg); |
| + |
| if (is_timer_reg(reg->id)) |
| return set_timer_reg(vcpu, reg); |
| |
| --- a/include/kvm/arm_psci.h |
| +++ b/include/kvm/arm_psci.h |
| @@ -37,10 +37,15 @@ static inline int kvm_psci_version(struc |
| * Our PSCI implementation stays the same across versions from |
| * v0.2 onward, only adding the few mandatory functions (such |
| * as FEATURES with 1.0) that are required by newer |
| - * revisions. It is thus safe to return the latest. |
| + * revisions. It is thus safe to return the latest, unless |
| + * userspace has instructed us otherwise. |
| */ |
| - if (test_bit(KVM_ARM_VCPU_PSCI_0_2, vcpu->arch.features)) |
| + if (test_bit(KVM_ARM_VCPU_PSCI_0_2, vcpu->arch.features)) { |
| + if (vcpu->kvm->arch.psci_version) |
| + return vcpu->kvm->arch.psci_version; |
| + |
| return KVM_ARM_PSCI_LATEST; |
| + } |
| |
| return KVM_ARM_PSCI_0_1; |
| } |
| @@ -48,4 +53,11 @@ static inline int kvm_psci_version(struc |
| |
| int kvm_hvc_call_handler(struct kvm_vcpu *vcpu); |
| |
| +struct kvm_one_reg; |
| + |
| +int kvm_arm_get_fw_num_regs(struct kvm_vcpu *vcpu); |
| +int kvm_arm_copy_fw_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices); |
| +int kvm_arm_get_fw_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg); |
| +int kvm_arm_set_fw_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg); |
| + |
| #endif /* __KVM_ARM_PSCI_H__ */ |
| --- a/virt/kvm/arm/psci.c |
| +++ b/virt/kvm/arm/psci.c |
| @@ -18,6 +18,7 @@ |
| #include <linux/arm-smccc.h> |
| #include <linux/preempt.h> |
| #include <linux/kvm_host.h> |
| +#include <linux/uaccess.h> |
| #include <linux/wait.h> |
| |
| #include <asm/cputype.h> |
| @@ -427,3 +428,62 @@ int kvm_hvc_call_handler(struct kvm_vcpu |
| smccc_set_retval(vcpu, val, 0, 0, 0); |
| return 1; |
| } |
| + |
| +int kvm_arm_get_fw_num_regs(struct kvm_vcpu *vcpu) |
| +{ |
| + return 1; /* PSCI version */ |
| +} |
| + |
| +int kvm_arm_copy_fw_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices) |
| +{ |
| + if (put_user(KVM_REG_ARM_PSCI_VERSION, uindices)) |
| + return -EFAULT; |
| + |
| + return 0; |
| +} |
| + |
| +int kvm_arm_get_fw_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg) |
| +{ |
| + if (reg->id == KVM_REG_ARM_PSCI_VERSION) { |
| + void __user *uaddr = (void __user *)(long)reg->addr; |
| + u64 val; |
| + |
| + val = kvm_psci_version(vcpu, vcpu->kvm); |
| + if (copy_to_user(uaddr, &val, KVM_REG_SIZE(reg->id))) |
| + return -EFAULT; |
| + |
| + return 0; |
| + } |
| + |
| + return -EINVAL; |
| +} |
| + |
| +int kvm_arm_set_fw_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg) |
| +{ |
| + if (reg->id == KVM_REG_ARM_PSCI_VERSION) { |
| + void __user *uaddr = (void __user *)(long)reg->addr; |
| + bool wants_02; |
| + u64 val; |
| + |
| + if (copy_from_user(&val, uaddr, KVM_REG_SIZE(reg->id))) |
| + return -EFAULT; |
| + |
| + wants_02 = test_bit(KVM_ARM_VCPU_PSCI_0_2, vcpu->arch.features); |
| + |
| + switch (val) { |
| + case KVM_ARM_PSCI_0_1: |
| + if (wants_02) |
| + return -EINVAL; |
| + vcpu->kvm->arch.psci_version = val; |
| + return 0; |
| + case KVM_ARM_PSCI_0_2: |
| + case KVM_ARM_PSCI_1_0: |
| + if (!wants_02) |
| + return -EINVAL; |
| + vcpu->kvm->arch.psci_version = val; |
| + return 0; |
| + } |
| + } |
| + |
| + return -EINVAL; |
| +} |