]> www.infradead.org Git - users/hch/misc.git/commitdiff
KVM: arm64: Report faults from S1 walk setup at the expected start level
authorMarc Zyngier <maz@kernel.org>
Mon, 28 Jul 2025 16:20:29 +0000 (17:20 +0100)
committerMarc Zyngier <maz@kernel.org>
Sat, 20 Sep 2025 10:05:13 +0000 (11:05 +0100)
Translation faults from TTBR must be reported on the start level,
and not level-0. Enforcing this requires moving quite a lot of
code around so that the start level can be computed early enough
that it is usable.

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

index e740df0d82f85befeda1333f4f763c1c5435e609..7b9711021db0d69a21f5f826b2440d5f3dedda69 100644 (file)
@@ -154,9 +154,6 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 
        va55 = va & BIT(55);
 
-       if (wi->regime == TR_EL2 && va55)
-               goto addrsz;
-
        wi->s2 = wi->regime == TR_EL10 && (hcr & (HCR_VM | HCR_DC));
 
        switch (wi->regime) {
@@ -179,6 +176,46 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
                BUG();
        }
 
+       /* Someone was silly enough to encode TG0/TG1 differently */
+       if (va55 && wi->regime != TR_EL2) {
+               wi->txsz = FIELD_GET(TCR_T1SZ_MASK, tcr);
+               tg = FIELD_GET(TCR_TG1_MASK, tcr);
+
+               switch (tg << TCR_TG1_SHIFT) {
+               case TCR_TG1_4K:
+                       wi->pgshift = 12;        break;
+               case TCR_TG1_16K:
+                       wi->pgshift = 14;        break;
+               case TCR_TG1_64K:
+               default:            /* IMPDEF: treat any other value as 64k */
+                       wi->pgshift = 16;        break;
+               }
+       } else {
+               wi->txsz = FIELD_GET(TCR_T0SZ_MASK, tcr);
+               tg = FIELD_GET(TCR_TG0_MASK, tcr);
+
+               switch (tg << TCR_TG0_SHIFT) {
+               case TCR_TG0_4K:
+                       wi->pgshift = 12;        break;
+               case TCR_TG0_16K:
+                       wi->pgshift = 14;        break;
+               case TCR_TG0_64K:
+               default:            /* IMPDEF: treat any other value as 64k */
+                       wi->pgshift = 16;        break;
+               }
+       }
+
+       wi->pa52bit = has_52bit_pa(vcpu, wi, tcr);
+
+       ia_bits = get_ia_size(wi);
+
+       /* AArch64.S1StartLevel() */
+       stride = wi->pgshift - 3;
+       wi->sl = 3 - (((ia_bits - 1) - wi->pgshift) / stride);
+
+       if (wi->regime == TR_EL2 && va55)
+               goto addrsz;
+
        tbi = (wi->regime == TR_EL2 ?
               FIELD_GET(TCR_EL2_TBI, tcr) :
               (va55 ?
@@ -248,46 +285,15 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
        /* R_BVXDG */
        wi->hpd |= (wi->poe || wi->e0poe);
 
-       /* Someone was silly enough to encode TG0/TG1 differently */
-       if (va55) {
-               wi->txsz = FIELD_GET(TCR_T1SZ_MASK, tcr);
-               tg = FIELD_GET(TCR_TG1_MASK, tcr);
-
-               switch (tg << TCR_TG1_SHIFT) {
-               case TCR_TG1_4K:
-                       wi->pgshift = 12;        break;
-               case TCR_TG1_16K:
-                       wi->pgshift = 14;        break;
-               case TCR_TG1_64K:
-               default:            /* IMPDEF: treat any other value as 64k */
-                       wi->pgshift = 16;        break;
-               }
-       } else {
-               wi->txsz = FIELD_GET(TCR_T0SZ_MASK, tcr);
-               tg = FIELD_GET(TCR_TG0_MASK, tcr);
-
-               switch (tg << TCR_TG0_SHIFT) {
-               case TCR_TG0_4K:
-                       wi->pgshift = 12;        break;
-               case TCR_TG0_16K:
-                       wi->pgshift = 14;        break;
-               case TCR_TG0_64K:
-               default:            /* IMPDEF: treat any other value as 64k */
-                       wi->pgshift = 16;        break;
-               }
-       }
-
        /* R_PLCGL, R_YXNYW */
        if (!kvm_has_feat_enum(vcpu->kvm, ID_AA64MMFR2_EL1, ST, 48_47)) {
                if (wi->txsz > 39)
-                       goto transfault_l0;
+                       goto transfault;
        } else {
                if (wi->txsz > 48 || (BIT(wi->pgshift) == SZ_64K && wi->txsz > 47))
-                       goto transfault_l0;
+                       goto transfault;
        }
 
-       wi->pa52bit = has_52bit_pa(vcpu, wi, tcr);
-
        /* R_GTJBY, R_SXWGM */
        switch (BIT(wi->pgshift)) {
        case SZ_4K:
@@ -300,28 +306,22 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
        }
 
        if ((lva && wi->txsz < 12) || (!lva && wi->txsz < 16))
-               goto transfault_l0;
-
-       ia_bits = get_ia_size(wi);
+               goto transfault;
 
        /* R_YYVYV, I_THCZK */
        if ((!va55 && va > GENMASK(ia_bits - 1, 0)) ||
            (va55 && va < GENMASK(63, ia_bits)))
-               goto transfault_l0;
+               goto transfault;
 
        /* I_ZFSYQ */
        if (wi->regime != TR_EL2 &&
            (tcr & (va55 ? TCR_EPD1_MASK : TCR_EPD0_MASK)))
-               goto transfault_l0;
+               goto transfault;
 
        /* R_BNDVG and following statements */
        if (kvm_has_feat(vcpu->kvm, ID_AA64MMFR2_EL1, E0PD, IMP) &&
            wi->as_el0 && (tcr & (va55 ? TCR_E0PD1 : TCR_E0PD0)))
-               goto transfault_l0;
-
-       /* AArch64.S1StartLevel() */
-       stride = wi->pgshift - 3;
-       wi->sl = 3 - (((ia_bits - 1) - wi->pgshift) / stride);
+               goto transfault;
 
        ps = (wi->regime == TR_EL2 ?
              FIELD_GET(TCR_EL2_PS_MASK, tcr) : FIELD_GET(TCR_IPS_MASK, tcr));
@@ -351,12 +351,17 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 
        return 0;
 
-addrsz:                                /* Address Size Fault level 0 */
+addrsz:
+       /*
+        * Address Size Fault level 0 to indicate it comes from TTBR.
+        * yes, this is an oddity.
+        */
        fail_s1_walk(wr, ESR_ELx_FSC_ADDRSZ_L(0), false);
        return -EFAULT;
 
-transfault_l0:                 /* Translation Fault level 0 */
-       fail_s1_walk(wr, ESR_ELx_FSC_FAULT_L(0), false);
+transfault:
+       /* Translation Fault on start level */
+       fail_s1_walk(wr, ESR_ELx_FSC_FAULT_L(wi->sl), false);
        return -EFAULT;
 }