]> www.infradead.org Git - users/hch/misc.git/commitdiff
KVM: arm64: Add S1 IPA to page table level walker
authorMarc Zyngier <maz@kernel.org>
Mon, 25 Aug 2025 10:31:33 +0000 (11:31 +0100)
committerMarc Zyngier <maz@kernel.org>
Sat, 20 Sep 2025 10:05:14 +0000 (11:05 +0100)
Use the filtering hook infrastructure to implement a new walker
that, for a given VA and an IPA, returns the level of the first
occurence of this IPA in the walk from that VA.

This will be used to improve our SEA syndrome reporting.

Reviewed-by: Oliver Upton <oliver.upton@linux.dev>
Signed-off-by: Marc Zyngier <maz@kernel.org>
arch/arm64/include/asm/kvm_nested.h
arch/arm64/kvm/at.c

index cce0e4cb54484952b9d63750f642329522934ced..2be6c3de74e3d5c733c1aede11313003ab09f02a 100644 (file)
@@ -353,6 +353,8 @@ struct s1_walk_result {
 
 int __kvm_translate_va(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
                       struct s1_walk_result *wr, u64 va);
+int __kvm_find_s1_desc_level(struct kvm_vcpu *vcpu, u64 va, u64 ipa,
+                            int *level);
 
 /* VNCR management */
 int kvm_vcpu_allocate_vncr_tlb(struct kvm_vcpu *vcpu);
index b70b777a3c2098c19dcc634e0430ecf81a5149e8..20bb9af125b173bc02443a0c8c8d4b508b838dc4 100644 (file)
@@ -1569,3 +1569,68 @@ int __kvm_translate_va(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 
        return 0;
 }
+
+struct desc_match {
+       u64     ipa;
+       int     level;
+};
+
+static int match_s1_desc(struct s1_walk_context *ctxt, void *priv)
+{
+       struct desc_match *dm = priv;
+       u64 ipa = dm->ipa;
+
+       /* Use S1 granule alignment */
+       ipa &= GENMASK(51, ctxt->wi->pgshift);
+
+       /* Not the IPA we're looking for? Continue. */
+       if (ipa != ctxt->table_ipa)
+               return 0;
+
+       /* Note the level and interrupt the walk */
+       dm->level = ctxt->level;
+       return -EINTR;
+}
+
+int __kvm_find_s1_desc_level(struct kvm_vcpu *vcpu, u64 va, u64 ipa, int *level)
+{
+       struct desc_match dm = {
+               .ipa    = ipa,
+       };
+       struct s1_walk_info wi = {
+               .filter = &(struct s1_walk_filter){
+                       .fn     = match_s1_desc,
+                       .priv   = &dm,
+               },
+               .regime = TR_EL10,
+               .as_el0 = false,
+               .pan    = false,
+       };
+       struct s1_walk_result wr = {};
+       int ret;
+
+       ret = setup_s1_walk(vcpu, &wi, &wr, va);
+       if (ret)
+               return ret;
+
+       /* We really expect the S1 MMU to be on here... */
+       if (WARN_ON_ONCE(wr.level == S1_MMU_DISABLED)) {
+               *level = 0;
+               return 0;
+       }
+
+       /* Walk the guest's PT, looking for a match along the way */
+       ret = walk_s1(vcpu, &wi, &wr, va);
+       switch (ret) {
+       case -EINTR:
+               /* We interrupted the walk on a match, return the level */
+               *level = dm.level;
+               return 0;
+       case 0:
+               /* The walk completed, we failed to find the entry */
+               return -ENOENT;
+       default:
+               /* Any other error... */
+               return ret;
+       }
+}