]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: arm64: Propagate PTW errors up to AT emulation
authorOliver Upton <oupton@kernel.org>
Mon, 24 Nov 2025 19:01:53 +0000 (11:01 -0800)
committerOliver Upton <oupton@kernel.org>
Mon, 1 Dec 2025 08:44:02 +0000 (00:44 -0800)
KVM's software PTW will soon support 'hardware' updates to the access
flag. Similar to fault handling, races to update the descriptor will be
handled by restarting the instruction. Prepare for this by propagating
errors up to the AT emulation, only retiring the instruction if the walk
succeeds.

Reviewed-by: Marc Zyngier <maz@kernel.org>
Tested-by: Marc Zyngier <maz@kernel.org>
Link: https://msgid.link/20251124190158.177318-12-oupton@kernel.org
Signed-off-by: Oliver Upton <oupton@kernel.org>
arch/arm64/include/asm/kvm_asm.h
arch/arm64/kvm/at.c
arch/arm64/kvm/sys_regs.c

index 9da54d4ee49ee7b4f98baf9dd779f8409223b602..090f7b740bdceb9fcacb9e7cad5cf8dcd324c687 100644 (file)
@@ -246,9 +246,9 @@ extern void __kvm_tlb_flush_vmid(struct kvm_s2_mmu *mmu);
 extern int __kvm_tlbi_s1e2(struct kvm_s2_mmu *mmu, u64 va, u64 sys_encoding);
 
 extern void __kvm_timer_set_cntvoff(u64 cntvoff);
-extern void __kvm_at_s1e01(struct kvm_vcpu *vcpu, u32 op, u64 vaddr);
-extern void __kvm_at_s1e2(struct kvm_vcpu *vcpu, u32 op, u64 vaddr);
-extern void __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr);
+extern int __kvm_at_s1e01(struct kvm_vcpu *vcpu, u32 op, u64 vaddr);
+extern int __kvm_at_s1e2(struct kvm_vcpu *vcpu, u32 op, u64 vaddr);
+extern int __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr);
 
 extern int __kvm_vcpu_run(struct kvm_vcpu *vcpu);
 
index 581c4c49d9cd12132a613aba27d5c32df4c21e93..2a99380ada6f37dd7d599f13e80e6be225d04926 100644 (file)
@@ -1234,7 +1234,7 @@ static void compute_s1_permissions(struct kvm_vcpu *vcpu,
        wr->pr &= !pan;
 }
 
-static u64 handle_at_slow(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
+static int handle_at_slow(struct kvm_vcpu *vcpu, u32 op, u64 vaddr, u64 *par)
 {
        struct s1_walk_result wr = {};
        struct s1_walk_info wi = {};
@@ -1259,6 +1259,11 @@ static u64 handle_at_slow(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
 
        srcu_read_unlock(&vcpu->kvm->srcu, idx);
 
+       /*
+        * Race to update a descriptor -- restart the walk.
+        */
+       if (ret == -EAGAIN)
+               return ret;
        if (ret)
                goto compute_par;
 
@@ -1292,7 +1297,8 @@ static u64 handle_at_slow(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
                fail_s1_walk(&wr, ESR_ELx_FSC_PERM_L(wr.level), false);
 
 compute_par:
-       return compute_par_s1(vcpu, &wi, &wr);
+       *par = compute_par_s1(vcpu, &wi, &wr);
+       return 0;
 }
 
 /*
@@ -1420,9 +1426,10 @@ static bool par_check_s1_access_fault(u64 par)
                 !(par & SYS_PAR_EL1_S));
 }
 
-void __kvm_at_s1e01(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
+int __kvm_at_s1e01(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
 {
        u64 par = __kvm_at_s1e01_fast(vcpu, op, vaddr);
+       int ret;
 
        /*
         * If PAR_EL1 reports that AT failed on a S1 permission or access
@@ -1434,15 +1441,20 @@ void __kvm_at_s1e01(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
         */
        if ((par & SYS_PAR_EL1_F) &&
            !par_check_s1_perm_fault(par) &&
-           !par_check_s1_access_fault(par))
-               par = handle_at_slow(vcpu, op, vaddr);
+           !par_check_s1_access_fault(par)) {
+               ret = handle_at_slow(vcpu, op, vaddr, &par);
+               if (ret)
+                       return ret;
+       }
 
        vcpu_write_sys_reg(vcpu, par, PAR_EL1);
+       return 0;
 }
 
-void __kvm_at_s1e2(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
+int __kvm_at_s1e2(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
 {
        u64 par;
+       int ret;
 
        /*
         * We've trapped, so everything is live on the CPU. As we will be
@@ -1489,13 +1501,17 @@ void __kvm_at_s1e2(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
        }
 
        /* We failed the translation, let's replay it in slow motion */
-       if ((par & SYS_PAR_EL1_F) && !par_check_s1_perm_fault(par))
-               par = handle_at_slow(vcpu, op, vaddr);
+       if ((par & SYS_PAR_EL1_F) && !par_check_s1_perm_fault(par)) {
+               ret = handle_at_slow(vcpu, op, vaddr, &par);
+               if (ret)
+                       return ret;
+       }
 
        vcpu_write_sys_reg(vcpu, par, PAR_EL1);
+       return 0;
 }
 
-void __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
+int __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
 {
        struct kvm_s2_trans out = {};
        u64 ipa, par;
@@ -1522,13 +1538,13 @@ void __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
                break;
        default:
                WARN_ON_ONCE(1);
-               return;
+               return 0;
        }
 
        __kvm_at_s1e01(vcpu, op, vaddr);
        par = vcpu_read_sys_reg(vcpu, PAR_EL1);
        if (par & SYS_PAR_EL1_F)
-               return;
+               return 0;
 
        /*
         * If we only have a single stage of translation (EL2&0), exit
@@ -1536,14 +1552,14 @@ void __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
         */
        if (compute_translation_regime(vcpu, op) == TR_EL20 ||
            !(vcpu_read_sys_reg(vcpu, HCR_EL2) & (HCR_VM | HCR_DC)))
-               return;
+               return 0;
 
        /* Do the stage-2 translation */
        ipa = (par & GENMASK_ULL(47, 12)) | (vaddr & GENMASK_ULL(11, 0));
        out.esr = 0;
        ret = kvm_walk_nested_s2(vcpu, ipa, &out);
        if (ret < 0)
-               return;
+               return ret;
 
        /* Check the access permission */
        if (!out.esr &&
@@ -1552,6 +1568,7 @@ void __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
 
        par = compute_par_s12(vcpu, par, &out);
        vcpu_write_sys_reg(vcpu, par, PAR_EL1);
+       return 0;
 }
 
 /*
index e67eb39ddc11844f0bc4c862c1784f95f8a34676..61830eb3607c5a5f36a1a6876ff89bdbff576c61 100644 (file)
@@ -3767,7 +3767,8 @@ static bool handle_at_s1e01(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 {
        u32 op = sys_insn(p->Op0, p->Op1, p->CRn, p->CRm, p->Op2);
 
-       __kvm_at_s1e01(vcpu, op, p->regval);
+       if (__kvm_at_s1e01(vcpu, op, p->regval))
+               return false;
 
        return true;
 }
@@ -3784,7 +3785,8 @@ static bool handle_at_s1e2(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
                return false;
        }
 
-       __kvm_at_s1e2(vcpu, op, p->regval);
+       if (__kvm_at_s1e2(vcpu, op, p->regval))
+               return false;
 
        return true;
 }
@@ -3794,7 +3796,8 @@ static bool handle_at_s12(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 {
        u32 op = sys_insn(p->Op0, p->Op1, p->CRn, p->CRm, p->Op2);
 
-       __kvm_at_s12(vcpu, op, p->regval);
+       if (__kvm_at_s12(vcpu, op, p->regval))
+               return false;
 
        return true;
 }