forked from Minki/linux
arm/arm64: KVM: Add PSCI version selection API
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>
This commit is contained in:
parent
bf9a41377d
commit
85bd0ba1ff
@ -1960,6 +1960,9 @@ ARM 32-bit VFP control registers have the following id bit patterns:
|
|||||||
ARM 64-bit FP registers have the following id bit patterns:
|
ARM 64-bit FP registers have the following id bit patterns:
|
||||||
0x4030 0000 0012 0 <regno:12>
|
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
|
arm64 registers are mapped using the lower 32 bits. The upper 16 of
|
||||||
that is the register group type, or coprocessor number:
|
that is the register group type, or coprocessor number:
|
||||||
@ -1976,6 +1979,9 @@ arm64 CCSIDR registers are demultiplexed by CSSELR value:
|
|||||||
arm64 system registers have the following id bit patterns:
|
arm64 system registers have the following id bit patterns:
|
||||||
0x6030 0000 0013 <op0:2> <op1:3> <crn:4> <crm:4> <op2:3>
|
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
|
MIPS registers are mapped using the lower 32 bits. The upper 16 of that is
|
||||||
the register group type:
|
the register group type:
|
||||||
@ -2510,7 +2516,8 @@ Possible features:
|
|||||||
and execute guest code when KVM_RUN is called.
|
and execute guest code when KVM_RUN is called.
|
||||||
- KVM_ARM_VCPU_EL1_32BIT: Starts the CPU in a 32bit mode.
|
- KVM_ARM_VCPU_EL1_32BIT: Starts the CPU in a 32bit mode.
|
||||||
Depends on KVM_CAP_ARM_EL1_32BIT (arm64 only).
|
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.
|
Depends on KVM_CAP_ARM_PSCI_0_2.
|
||||||
- KVM_ARM_VCPU_PMU_V3: Emulate PMUv3 for the CPU.
|
- KVM_ARM_VCPU_PMU_V3: Emulate PMUv3 for the CPU.
|
||||||
Depends on KVM_CAP_ARM_PMU_V3.
|
Depends on KVM_CAP_ARM_PMU_V3.
|
||||||
|
30
Documentation/virtual/kvm/arm/psci.txt
Normal file
30
Documentation/virtual/kvm/arm/psci.txt
Normal file
@ -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)
|
@ -77,6 +77,9 @@ struct kvm_arch {
|
|||||||
/* Interrupt controller */
|
/* Interrupt controller */
|
||||||
struct vgic_dist vgic;
|
struct vgic_dist vgic;
|
||||||
int max_vcpus;
|
int max_vcpus;
|
||||||
|
|
||||||
|
/* Mandated version of PSCI */
|
||||||
|
u32 psci_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define KVM_NR_MEM_OBJS 40
|
#define KVM_NR_MEM_OBJS 40
|
||||||
|
@ -195,6 +195,12 @@ struct kvm_arch_memory_slot {
|
|||||||
#define KVM_REG_ARM_VFP_FPINST 0x1009
|
#define KVM_REG_ARM_VFP_FPINST 0x1009
|
||||||
#define KVM_REG_ARM_VFP_FPINST2 0x100A
|
#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 */
|
/* Device Control API: ARM VGIC */
|
||||||
#define KVM_DEV_ARM_VGIC_GRP_ADDR 0
|
#define KVM_DEV_ARM_VGIC_GRP_ADDR 0
|
||||||
#define KVM_DEV_ARM_VGIC_GRP_DIST_REGS 1
|
#define KVM_DEV_ARM_VGIC_GRP_DIST_REGS 1
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/vmalloc.h>
|
#include <linux/vmalloc.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
|
#include <kvm/arm_psci.h>
|
||||||
#include <asm/cputype.h>
|
#include <asm/cputype.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <asm/kvm.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)
|
unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
return num_core_regs() + kvm_arm_num_coproc_regs(vcpu)
|
return num_core_regs() + kvm_arm_num_coproc_regs(vcpu)
|
||||||
|
+ kvm_arm_get_fw_num_regs(vcpu)
|
||||||
+ NUM_TIMER_REGS;
|
+ NUM_TIMER_REGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +198,11 @@ int kvm_arm_copy_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices)
|
|||||||
uindices++;
|
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);
|
ret = copy_timer_indices(vcpu, uindices);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
@ -214,6 +221,9 @@ int kvm_arm_get_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
|
|||||||
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
||||||
return get_core_reg(vcpu, reg);
|
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))
|
if (is_timer_reg(reg->id))
|
||||||
return get_timer_reg(vcpu, reg);
|
return get_timer_reg(vcpu, reg);
|
||||||
|
|
||||||
@ -230,6 +240,9 @@ int kvm_arm_set_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
|
|||||||
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
||||||
return set_core_reg(vcpu, reg);
|
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))
|
if (is_timer_reg(reg->id))
|
||||||
return set_timer_reg(vcpu, reg);
|
return set_timer_reg(vcpu, reg);
|
||||||
|
|
||||||
|
@ -75,6 +75,9 @@ struct kvm_arch {
|
|||||||
|
|
||||||
/* Interrupt controller */
|
/* Interrupt controller */
|
||||||
struct vgic_dist vgic;
|
struct vgic_dist vgic;
|
||||||
|
|
||||||
|
/* Mandated version of PSCI */
|
||||||
|
u32 psci_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define KVM_NR_MEM_OBJS 40
|
#define KVM_NR_MEM_OBJS 40
|
||||||
|
@ -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_CNT ARM64_SYS_REG(3, 3, 14, 3, 2)
|
||||||
#define KVM_REG_ARM_TIMER_CVAL ARM64_SYS_REG(3, 3, 14, 0, 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 */
|
/* Device Control API: ARM VGIC */
|
||||||
#define KVM_DEV_ARM_VGIC_GRP_ADDR 0
|
#define KVM_DEV_ARM_VGIC_GRP_ADDR 0
|
||||||
#define KVM_DEV_ARM_VGIC_GRP_DIST_REGS 1
|
#define KVM_DEV_ARM_VGIC_GRP_DIST_REGS 1
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/vmalloc.h>
|
#include <linux/vmalloc.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
|
#include <kvm/arm_psci.h>
|
||||||
#include <asm/cputype.h>
|
#include <asm/cputype.h>
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
#include <asm/kvm.h>
|
#include <asm/kvm.h>
|
||||||
@ -205,7 +206,7 @@ static int get_timer_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
|
|||||||
unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu)
|
unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu)
|
||||||
{
|
{
|
||||||
return num_core_regs() + kvm_arm_num_sys_reg_descs(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_vcpu *vcpu, u64 __user *uindices)
|
|||||||
uindices++;
|
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);
|
ret = copy_timer_indices(vcpu, uindices);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
@ -243,6 +249,9 @@ int kvm_arm_get_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
|
|||||||
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
||||||
return get_core_reg(vcpu, reg);
|
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))
|
if (is_timer_reg(reg->id))
|
||||||
return get_timer_reg(vcpu, reg);
|
return get_timer_reg(vcpu, reg);
|
||||||
|
|
||||||
@ -259,6 +268,9 @@ int kvm_arm_set_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
|
|||||||
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
|
||||||
return set_core_reg(vcpu, reg);
|
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))
|
if (is_timer_reg(reg->id))
|
||||||
return set_timer_reg(vcpu, reg);
|
return set_timer_reg(vcpu, reg);
|
||||||
|
|
||||||
|
@ -37,10 +37,15 @@ static inline int kvm_psci_version(struct kvm_vcpu *vcpu, struct kvm *kvm)
|
|||||||
* Our PSCI implementation stays the same across versions from
|
* Our PSCI implementation stays the same across versions from
|
||||||
* v0.2 onward, only adding the few mandatory functions (such
|
* v0.2 onward, only adding the few mandatory functions (such
|
||||||
* as FEATURES with 1.0) that are required by newer
|
* 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_LATEST;
|
||||||
|
}
|
||||||
|
|
||||||
return KVM_ARM_PSCI_0_1;
|
return KVM_ARM_PSCI_0_1;
|
||||||
}
|
}
|
||||||
@ -48,4 +53,11 @@ static inline int kvm_psci_version(struct kvm_vcpu *vcpu, struct kvm *kvm)
|
|||||||
|
|
||||||
int kvm_hvc_call_handler(struct kvm_vcpu *vcpu);
|
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__ */
|
#endif /* __KVM_ARM_PSCI_H__ */
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <linux/arm-smccc.h>
|
#include <linux/arm-smccc.h>
|
||||||
#include <linux/preempt.h>
|
#include <linux/preempt.h>
|
||||||
#include <linux/kvm_host.h>
|
#include <linux/kvm_host.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
#include <linux/wait.h>
|
#include <linux/wait.h>
|
||||||
|
|
||||||
#include <asm/cputype.h>
|
#include <asm/cputype.h>
|
||||||
@ -427,3 +428,62 @@ int kvm_hvc_call_handler(struct kvm_vcpu *vcpu)
|
|||||||
smccc_set_retval(vcpu, val, 0, 0, 0);
|
smccc_set_retval(vcpu, val, 0, 0, 0);
|
||||||
return 1;
|
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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user