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 <m...@kernel.org>
---
 arch/arm64/include/asm/kvm_nested.h |  4 ++
 arch/arm64/kvm/nested.c             | 98 +++++++++++++++++++++++++++++
 arch/arm64/kvm/sys_regs.c           | 13 ++--
 3 files changed, 110 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_nested.h 
b/arch/arm64/include/asm/kvm_nested.h
index 620296206483..debae814fdc5 100644
--- a/arch/arm64/include/asm/kvm_nested.h
+++ b/arch/arm64/include/asm/kvm_nested.h
@@ -67,6 +67,8 @@ extern bool __forward_traps(struct kvm_vcpu *vcpu, unsigned 
int reg,
                            u64 control_bit);
 extern bool forward_traps(struct kvm_vcpu *vcpu, u64 control_bit);
 extern bool forward_nv_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;
@@ -75,4 +77,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    GENMASK_ULL(56, 55)
+
 #endif /* __ARM64_KVM_NESTED_H */
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 4cb4831d4022..6adff9b433e0 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -16,6 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/bitfield.h>
 #include <linux/kvm.h>
 #include <linux/kvm_host.h>
 
@@ -351,6 +352,29 @@ 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->kvm->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)
 {
@@ -407,6 +431,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->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 1dcde0230b87..8d4e67594d87 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -2589,10 +2589,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);
@@ -2633,6 +2636,8 @@ static bool handle_ipas2e1is(struct kvm_vcpu *vcpu, 
struct sys_reg_params *p,
        u64 base_addr;
        unsigned long max_size;
 
+       spin_lock(&vcpu->kvm->mmu_lock);
+
        /*
         * We drop a number of things from the supplied value:
         *
@@ -2644,8 +2649,6 @@ static bool handle_ipas2e1is(struct kvm_vcpu *vcpu, 
struct sys_reg_params *p,
         */
        base_addr = (p->regval & GENMASK_ULL(35, 0)) << 12;
 
-       spin_lock(&vcpu->kvm->mmu_lock);
-
        mmu = lookup_s2_mmu(vcpu->kvm, vttbr, HCR_VM);
        if (mmu) {
                max_size = compute_tlb_inval_range(vcpu, mmu, p->regval);
-- 
2.20.1

_______________________________________________
kvmarm mailing list
kvmarm@lists.cs.columbia.edu
https://lists.cs.columbia.edu/mailman/listinfo/kvmarm

Reply via email to