KVM: PPC: Book3S HV: Add a function to filter guest LPCR bits
Guest LPCR depends on hardware type, and future changes will add restrictions based on errata and guest MMU mode. Move this logic to a common function and use it for the cases where the guest wants to update its LPCR (or the LPCR of a nested guest). This also adds a warning in other places that set or update LPCR if we try to set something that would have been disallowed by the filter, as a sanity check. Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Reviewed-by: Fabiano Rosas <farosas@linux.ibm.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20210412014845.1517916-4-npiggin@gmail.com
This commit is contained in:
parent
a19b70abc6
commit
67145ef496
@ -258,6 +258,8 @@ extern long kvmppc_hv_get_dirty_log_hpt(struct kvm *kvm,
|
||||
extern void kvmppc_harvest_vpa_dirty(struct kvmppc_vpa *vpa,
|
||||
struct kvm_memory_slot *memslot,
|
||||
unsigned long *map);
|
||||
extern unsigned long kvmppc_filter_lpcr_hv(struct kvm *kvm,
|
||||
unsigned long lpcr);
|
||||
extern void kvmppc_update_lpcr(struct kvm *kvm, unsigned long lpcr,
|
||||
unsigned long mask);
|
||||
extern void kvmppc_set_fscr(struct kvm_vcpu *vcpu, u64 fscr);
|
||||
|
@ -1635,6 +1635,35 @@ static int kvm_arch_vcpu_ioctl_set_sregs_hv(struct kvm_vcpu *vcpu,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enforce limits on guest LPCR values based on hardware availability,
|
||||
* guest configuration, and possibly hypervisor support and security
|
||||
* concerns.
|
||||
*/
|
||||
unsigned long kvmppc_filter_lpcr_hv(struct kvm *kvm, unsigned long lpcr)
|
||||
{
|
||||
/* On POWER8 and above, userspace can modify AIL */
|
||||
if (!cpu_has_feature(CPU_FTR_ARCH_207S))
|
||||
lpcr &= ~LPCR_AIL;
|
||||
|
||||
/*
|
||||
* On POWER9, allow userspace to enable large decrementer for the
|
||||
* guest, whether or not the host has it enabled.
|
||||
*/
|
||||
if (!cpu_has_feature(CPU_FTR_ARCH_300))
|
||||
lpcr &= ~LPCR_LD;
|
||||
|
||||
return lpcr;
|
||||
}
|
||||
|
||||
static void verify_lpcr(struct kvm *kvm, unsigned long lpcr)
|
||||
{
|
||||
if (lpcr != kvmppc_filter_lpcr_hv(kvm, lpcr)) {
|
||||
WARN_ONCE(1, "lpcr 0x%lx differs from filtered 0x%lx\n",
|
||||
lpcr, kvmppc_filter_lpcr_hv(kvm, lpcr));
|
||||
}
|
||||
}
|
||||
|
||||
static void kvmppc_set_lpcr(struct kvm_vcpu *vcpu, u64 new_lpcr,
|
||||
bool preserve_top32)
|
||||
{
|
||||
@ -1643,6 +1672,23 @@ static void kvmppc_set_lpcr(struct kvm_vcpu *vcpu, u64 new_lpcr,
|
||||
u64 mask;
|
||||
|
||||
spin_lock(&vc->lock);
|
||||
|
||||
/*
|
||||
* Userspace can only modify
|
||||
* DPFD (default prefetch depth), ILE (interrupt little-endian),
|
||||
* TC (translation control), AIL (alternate interrupt location),
|
||||
* LD (large decrementer).
|
||||
* These are subject to restrictions from kvmppc_filter_lcpr_hv().
|
||||
*/
|
||||
mask = LPCR_DPFD | LPCR_ILE | LPCR_TC | LPCR_AIL | LPCR_LD;
|
||||
|
||||
/* Broken 32-bit version of LPCR must not clear top bits */
|
||||
if (preserve_top32)
|
||||
mask &= 0xFFFFFFFF;
|
||||
|
||||
new_lpcr = kvmppc_filter_lpcr_hv(kvm,
|
||||
(vc->lpcr & ~mask) | (new_lpcr & mask));
|
||||
|
||||
/*
|
||||
* If ILE (interrupt little-endian) has changed, update the
|
||||
* MSR_LE bit in the intr_msr for each vcpu in this vcore.
|
||||
@ -1661,25 +1707,8 @@ static void kvmppc_set_lpcr(struct kvm_vcpu *vcpu, u64 new_lpcr,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Userspace can only modify DPFD (default prefetch depth),
|
||||
* ILE (interrupt little-endian) and TC (translation control).
|
||||
* On POWER8 and POWER9 userspace can also modify AIL (alt. interrupt loc.).
|
||||
*/
|
||||
mask = LPCR_DPFD | LPCR_ILE | LPCR_TC;
|
||||
if (cpu_has_feature(CPU_FTR_ARCH_207S))
|
||||
mask |= LPCR_AIL;
|
||||
/*
|
||||
* On POWER9, allow userspace to enable large decrementer for the
|
||||
* guest, whether or not the host has it enabled.
|
||||
*/
|
||||
if (cpu_has_feature(CPU_FTR_ARCH_300))
|
||||
mask |= LPCR_LD;
|
||||
vc->lpcr = new_lpcr;
|
||||
|
||||
/* Broken 32-bit version of LPCR must not clear top bits */
|
||||
if (preserve_top32)
|
||||
mask &= 0xFFFFFFFF;
|
||||
vc->lpcr = (vc->lpcr & ~mask) | (new_lpcr & mask);
|
||||
spin_unlock(&vc->lock);
|
||||
}
|
||||
|
||||
@ -4644,8 +4673,10 @@ void kvmppc_update_lpcr(struct kvm *kvm, unsigned long lpcr, unsigned long mask)
|
||||
struct kvmppc_vcore *vc = kvm->arch.vcores[i];
|
||||
if (!vc)
|
||||
continue;
|
||||
|
||||
spin_lock(&vc->lock);
|
||||
vc->lpcr = (vc->lpcr & ~mask) | lpcr;
|
||||
verify_lpcr(kvm, vc->lpcr);
|
||||
spin_unlock(&vc->lock);
|
||||
if (++cores_done >= kvm->arch.online_vcores)
|
||||
break;
|
||||
@ -4973,6 +5004,7 @@ static int kvmppc_core_init_vm_hv(struct kvm *kvm)
|
||||
kvmppc_setup_partition_table(kvm);
|
||||
}
|
||||
|
||||
verify_lpcr(kvm, lpcr);
|
||||
kvm->arch.lpcr = lpcr;
|
||||
|
||||
/* Initialization for future HPT resizes */
|
||||
|
@ -151,7 +151,13 @@ static void sanitise_hv_regs(struct kvm_vcpu *vcpu, struct hv_guest_state *hr)
|
||||
*/
|
||||
mask = LPCR_DPFD | LPCR_ILE | LPCR_TC | LPCR_AIL | LPCR_LD |
|
||||
LPCR_LPES | LPCR_MER;
|
||||
hr->lpcr = (vc->lpcr & ~mask) | (hr->lpcr & mask);
|
||||
|
||||
/*
|
||||
* Additional filtering is required depending on hardware
|
||||
* and configuration.
|
||||
*/
|
||||
hr->lpcr = kvmppc_filter_lpcr_hv(vcpu->kvm,
|
||||
(vc->lpcr & ~mask) | (hr->lpcr & mask));
|
||||
|
||||
/*
|
||||
* Don't let L1 enable features for L2 which we've disabled for L1,
|
||||
|
Loading…
x
Reference in New Issue
Block a user