]> exis.tech > repos - linux.git/blobdiff - arch/arm64/kvm/nested.c
Merge tag 'hwmon-for-v7.2-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/groec...
[linux.git] / arch / arm64 / kvm / nested.c
index fb54f6dad995c958f0f763e1f50b8a9c7e501a57..dfb96edbdc43c666ec90ec326cb645cac9c1a55c 100644 (file)
@@ -24,6 +24,7 @@ struct vncr_tlb {
        struct s1_walk_result   wr;
 
        u64                     hpa;
+       bool                    hpa_writable;
 
        /* -1 when not mapped on a CPU */
        int                     cpu;
@@ -1401,15 +1402,19 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
 
        gfn = vt->wr.pa >> PAGE_SHIFT;
        memslot = gfn_to_memslot(vcpu->kvm, gfn);
-       if (!memslot)
+       if (!memslot) {
+               fail_s1_walk(&vt->wr, ESR_ELx_FSC_EXTABT, false);
                return -EFAULT;
+       }
 
        *is_gmem = kvm_slot_has_gmem(memslot);
        if (!*is_gmem) {
                pfn = __kvm_faultin_pfn(memslot, gfn, write_fault ? FOLL_WRITE : 0,
                                        &writable, &page);
-               if (is_error_noslot_pfn(pfn) || (write_fault && !writable))
+               if (is_error_noslot_pfn(pfn)) {
+                       fail_s1_walk(&vt->wr, ESR_ELx_FSC_EXTABT, false);
                        return -EFAULT;
+               }
        } else {
                ret = kvm_gmem_get_pfn(vcpu->kvm, memslot, gfn, &pfn, &page, NULL);
                if (ret) {
@@ -1417,6 +1422,19 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
                                              write_fault, false, false);
                        return ret;
                }
+
+               writable = !(memslot->flags & KVM_MEM_READONLY);
+       }
+
+       /*
+        * FIXME: This check is too restrictive as KVM allows cacheable memory
+        * attributes for PFNMAP VMAs that have cacheable attributes in host
+        * stage-1.
+        */
+       if (!pfn_is_map_memory(pfn)) {
+               kvm_release_faultin_page(vcpu->kvm, page, true, false);
+               fail_s1_walk(&vt->wr, ESR_ELx_FSC_EXTABT, false);
+               return -EINVAL;
        }
 
        scoped_guard(write_lock, &vcpu->kvm->mmu_lock) {
@@ -1427,116 +1445,100 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
 
                vt->gva = va;
                vt->hpa = pfn << PAGE_SHIFT;
+               vt->hpa_writable = writable;
                vt->valid = true;
                vt->cpu = -1;
 
                kvm_make_request(KVM_REQ_MAP_L1_VNCR_EL2, vcpu);
-               kvm_release_faultin_page(vcpu->kvm, page, false, vt->wr.pw);
+               kvm_release_faultin_page(vcpu->kvm, page, false, vt->wr.pw && vt->hpa_writable);
        }
 
-       if (vt->wr.pw)
+       if (vt->wr.pw && vt->hpa_writable)
                mark_page_dirty(vcpu->kvm, gfn);
 
        return 0;
 }
 
-static void inject_vncr_perm(struct kvm_vcpu *vcpu)
+static void handle_vncr_perm(struct kvm_vcpu *vcpu)
 {
        struct vncr_tlb *vt = vcpu->arch.vncr_tlb;
        u64 esr = kvm_vcpu_get_esr(vcpu);
+       u64 fsc;
+
+       /*
+        * Promote to an external abort if the stage-1 permits writes but the
+        * HPA is read-only (e.g. RO memslot).
+        */
+       if (kvm_is_write_fault(vcpu) && vt->wr.pw && !vt->hpa_writable)
+               fsc = ESR_ELx_FSC_EXTABT;
+       /*
+        * Otherwise, inject a permission fault using the guest's translation
+        * level rather than the host's.
+        */
+       else
+               fsc = ESR_ELx_FSC_PERM_L(vt->wr.level);
 
-       /* Adjust the fault level to reflect that of the guest's */
        esr &= ~ESR_ELx_FSC;
-       esr |= FIELD_PREP(ESR_ELx_FSC,
-                         ESR_ELx_FSC_PERM_L(vt->wr.level));
+       esr |= FIELD_PREP(ESR_ELx_FSC, fsc);
 
        kvm_inject_nested_sync(vcpu, esr);
 }
 
-static bool kvm_vncr_tlb_lookup(struct kvm_vcpu *vcpu)
-{
-       struct vncr_tlb *vt = vcpu->arch.vncr_tlb;
-
-       lockdep_assert_held_read(&vcpu->kvm->mmu_lock);
-
-       if (!vt->valid)
-               return false;
-
-       if (read_vncr_el2(vcpu) != vt->gva)
-               return false;
-
-       if (vt->wr.nG)
-               return get_asid_by_regime(vcpu, TR_EL20) == vt->wr.asid;
-
-       return true;
-}
-
 int kvm_handle_vncr_abort(struct kvm_vcpu *vcpu)
 {
        struct vncr_tlb *vt = vcpu->arch.vncr_tlb;
        u64 esr = kvm_vcpu_get_esr(vcpu);
+       bool is_gmem = false;
+       bool perm;
+       int ret;
 
        WARN_ON_ONCE(!(esr & ESR_ELx_VNCR));
 
        if (kvm_vcpu_abt_issea(vcpu))
                return kvm_handle_guest_sea(vcpu);
 
-       if (esr_fsc_is_permission_fault(esr)) {
-               inject_vncr_perm(vcpu);
-       } else if (esr_fsc_is_translation_fault(esr)) {
-               bool valid, is_gmem = false;
-               int ret;
-
-               scoped_guard(read_lock, &vcpu->kvm->mmu_lock)
-                       valid = kvm_vncr_tlb_lookup(vcpu);
-
-               if (!valid)
-                       ret = kvm_translate_vncr(vcpu, &is_gmem);
-               else
-                       ret = -EPERM;
-
-               switch (ret) {
-               case -EAGAIN:
-                       /* Let's try again... */
-                       break;
-               case -ENOMEM:
-                       /*
-                        * For guest_memfd, this indicates that it failed to
-                        * create a folio to back the memory. Inform userspace.
-                        */
-                       if (is_gmem)
-                               return 0;
-                       /* Otherwise, let's try again... */
-                       break;
-               case -EFAULT:
-               case -EIO:
-               case -EHWPOISON:
-                       if (is_gmem)
-                               return 0;
-                       fallthrough;
-               case -EINVAL:
-               case -ENOENT:
-               case -EACCES:
-                       /*
-                        * Translation failed, inject the corresponding
-                        * exception back to EL2.
-                        */
-                       BUG_ON(!vt->wr.failed);
+       if (!esr_fsc_is_translation_fault(esr) && !esr_fsc_is_permission_fault(esr)) {
+               KVM_BUG(1, vcpu->kvm, "Unhandled VNCR abort, ESR=%llx\n", esr);
+               return -EIO;
+       }
 
-                       esr &= ~ESR_ELx_FSC;
-                       esr |= FIELD_PREP(ESR_ELx_FSC, vt->wr.fst);
+       ret = kvm_translate_vncr(vcpu, &is_gmem);
+       switch (ret) {
+       case -EAGAIN:
+               /* Let's try again... */
+               return 1;
+       case -ENOMEM:
+               /*
+                * For guest_memfd, this indicates that it failed to
+                * create a folio to back the memory. Inform userspace.
+                */
+               if (is_gmem)
+                       return 0;
+               /* Otherwise, let's try again... */
+               break;
+       case -EFAULT:
+       case -EIO:
+       case -EHWPOISON:
+               if (is_gmem)
+                       return 0;
+               fallthrough;
+       case -EINVAL:
+       case -ENOENT:
+       case -EACCES:
+               /*
+                * Translation failed, inject the corresponding
+                * exception back to EL2.
+                */
+               esr &= ~ESR_ELx_FSC;
+               esr |= FIELD_PREP(ESR_ELx_FSC, vt->wr.fst);
 
-                       kvm_inject_nested_sync(vcpu, esr);
-                       break;
-               case -EPERM:
-                       /* Hack to deal with POE until we get kernel support */
-                       inject_vncr_perm(vcpu);
-                       break;
-               case 0:
-                       break;
-               }
-       } else {
-               WARN_ONCE(1, "Unhandled VNCR abort, ESR=%llx\n", esr);
+               kvm_inject_nested_sync(vcpu, esr);
+               break;
+       case 0:
+               perm = kvm_is_write_fault(vcpu) ? vt->wr.pw && vt->hpa_writable : vt->wr.pr;
+               if (!perm)
+                       handle_vncr_perm(vcpu);
+               break;
        }
 
        return 1;
@@ -1574,7 +1576,7 @@ static void kvm_map_l1_vncr(struct kvm_vcpu *vcpu)
 
        vt->cpu = smp_processor_id();
 
-       if (vt->wr.pw && vt->wr.pr)
+       if (vt->hpa_writable && vt->wr.pw && vt->wr.pr)
                prot = PAGE_KERNEL;
        else if (vt->wr.pr)
                prot = PAGE_KERNEL_RO;