[PATCH v7 55/68] KVM: arm64: nv: Invalidate TLBs based on shadow S2 TTL-like information
Marc Zyngier
maz at kernel.org
Thu Jan 12 11:19:14 PST 2023
In order to be able to make S2 TLB invalidations more performant on NV,
let's use a scheme derived from the ARMv8.4 TTL extension.
If bits [56:55] in the descriptor are non-zero, they indicate a level
which can be used as an invalidation range.
Signed-off-by: Marc Zyngier <maz at kernel.org>
---
arch/arm64/include/asm/kvm_nested.h | 4 ++
arch/arm64/kvm/nested.c | 99 +++++++++++++++++++++++++++++
arch/arm64/kvm/sys_regs.c | 9 ++-
3 files changed, 109 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h
index c5b661517cb6..63bb4898e2d9 100644
--- a/arch/arm64/include/asm/kvm_nested.h
+++ b/arch/arm64/include/asm/kvm_nested.h
@@ -122,6 +122,8 @@ extern bool __forward_traps(struct kvm_vcpu *vcpu, unsigned int reg,
extern bool forward_traps(struct kvm_vcpu *vcpu, u64 control_bit);
extern bool forward_nv_traps(struct kvm_vcpu *vcpu);
extern bool forward_nv1_traps(struct kvm_vcpu *vcpu);
+u8 get_guest_mapping_ttl(struct kvm_vcpu *vcpu, struct kvm_s2_mmu *mmu,
+ u64 addr);
unsigned int ttl_to_size(u8 ttl);
struct sys_reg_params;
@@ -130,4 +132,6 @@ struct sys_reg_desc;
void access_nested_id_reg(struct kvm_vcpu *v, struct sys_reg_params *p,
const struct sys_reg_desc *r);
+#define KVM_NV_GUEST_MAP_SZ (KVM_PGTABLE_PROT_SW1 | KVM_PGTABLE_PROT_SW0)
+
#endif /* __ARM64_KVM_NESTED_H */
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 8b22984a3112..f93f4248c6f3 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -4,6 +4,7 @@
* Author: Jintack Lim <jintack.lim at linaro.org>
*/
+#include <linux/bitfield.h>
#include <linux/kvm.h>
#include <linux/kvm_host.h>
@@ -357,6 +358,30 @@ int kvm_walk_nested_s2(struct kvm_vcpu *vcpu, phys_addr_t gipa,
return ret;
}
+static int read_host_s2_desc(phys_addr_t pa, u64 *desc, void *data)
+{
+ u64 *va = phys_to_virt(pa);
+
+ *desc = *va;
+
+ return 0;
+}
+
+static int kvm_walk_shadow_s2(struct kvm_s2_mmu *mmu, phys_addr_t gipa,
+ struct kvm_s2_trans *result)
+{
+ struct s2_walk_info wi = { };
+
+ wi.read_desc = read_host_s2_desc;
+ wi.baddr = mmu->pgd_phys;
+
+ vtcr_to_walk_info(mmu->arch->vtcr, &wi);
+
+ wi.be = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN);
+
+ return walk_nested_s2_pgd(gipa, &wi, result);
+}
+
unsigned int ttl_to_size(u8 ttl)
{
int level = ttl & 3;
@@ -413,6 +438,80 @@ unsigned int ttl_to_size(u8 ttl)
return max_size;
}
+/*
+ * Compute the equivalent of the TTL field by parsing the shadow PT.
+ * The granule size is extracted from VTCR_EL2.TG0 while the level is
+ * retrieved from first entry carrying the level as a tag.
+ */
+u8 get_guest_mapping_ttl(struct kvm_vcpu *vcpu, struct kvm_s2_mmu *mmu,
+ u64 addr)
+{
+ u64 tmp, sz = 0, vtcr = vcpu_read_sys_reg(vcpu, VTCR_EL2);
+ struct kvm_s2_trans out;
+ u8 ttl, level;
+
+ switch (vtcr & VTCR_EL2_TG0_MASK) {
+ case VTCR_EL2_TG0_4K:
+ ttl = (1 << 2);
+ break;
+ case VTCR_EL2_TG0_16K:
+ ttl = (2 << 2);
+ break;
+ case VTCR_EL2_TG0_64K:
+ ttl = (3 << 2);
+ break;
+ default:
+ BUG();
+ }
+
+ tmp = addr;
+
+again:
+ /* Iteratively compute the block sizes for a particular granule size */
+ switch (vtcr & VTCR_EL2_TG0_MASK) {
+ case VTCR_EL2_TG0_4K:
+ if (sz < SZ_4K) sz = SZ_4K;
+ else if (sz < SZ_2M) sz = SZ_2M;
+ else if (sz < SZ_1G) sz = SZ_1G;
+ else sz = 0;
+ break;
+ case VTCR_EL2_TG0_16K:
+ if (sz < SZ_16K) sz = SZ_16K;
+ else if (sz < SZ_32M) sz = SZ_32M;
+ else sz = 0;
+ break;
+ case VTCR_EL2_TG0_64K:
+ if (sz < SZ_64K) sz = SZ_64K;
+ else if (sz < SZ_512M) sz = SZ_512M;
+ else sz = 0;
+ break;
+ default:
+ BUG();
+ }
+
+ if (sz == 0)
+ return 0;
+
+ tmp &= ~(sz - 1);
+ out = (struct kvm_s2_trans) { };
+ kvm_walk_shadow_s2(mmu, tmp, &out);
+ level = FIELD_GET(KVM_NV_GUEST_MAP_SZ, out.upper_attr);
+ if (!level)
+ goto again;
+
+ ttl |= level;
+
+ /*
+ * We now have found some level information in the shadow S2. Check
+ * that the resulting range is actually including the original IPA.
+ */
+ sz = ttl_to_size(ttl);
+ if (addr < (tmp + sz))
+ return ttl;
+
+ return 0;
+}
+
/* Must be called with kvm->mmu_lock held */
struct kvm_s2_mmu *lookup_s2_mmu(struct kvm *kvm, u64 vttbr, u64 hcr)
{
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index aa15cf3b29eb..89fc11024786 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -2870,10 +2870,13 @@ static unsigned long compute_tlb_inval_range(struct kvm_vcpu *vcpu,
u64 val)
{
unsigned long max_size;
- u8 ttl = 0;
+ u8 ttl;
- if (cpus_have_const_cap(ARM64_HAS_ARMv8_4_TTL)) {
- ttl = FIELD_GET(GENMASK_ULL(47, 44), val);
+ ttl = FIELD_GET(GENMASK_ULL(47, 44), val);
+
+ if (!(cpus_have_const_cap(ARM64_HAS_ARMv8_4_TTL) && ttl)) {
+ u64 addr = (val & GENMASK_ULL(35, 0)) << 12;
+ ttl = get_guest_mapping_ttl(vcpu, mmu, addr);
}
max_size = ttl_to_size(ttl);
--
2.34.1
More information about the linux-arm-kernel
mailing list