| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * KVM guest fault handling. |
| * |
| * Copyright IBM Corp. 2025 |
| * Author(s): Claudio Imbrenda <imbrenda@linux.ibm.com> |
| */ |
| #include <linux/kvm_types.h> |
| #include <linux/kvm_host.h> |
| |
| #include "gmap.h" |
| #include "trace.h" |
| #include "faultin.h" |
| |
| bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu); |
| |
| /* |
| * kvm_s390_faultin_gfn() - handle a dat fault. |
| * @vcpu: The vCPU whose gmap is to be fixed up, or NULL if operating on the VM. |
| * @kvm: The VM whose gmap is to be fixed up, or NULL if operating on a vCPU. |
| * @f: The guest fault that needs to be resolved. |
| * |
| * Return: |
| * * 0 on success |
| * * < 0 in case of error |
| * * > 0 in case of guest exceptions |
| * |
| * Context: |
| * * The mm lock must not be held before calling |
| * * kvm->srcu must be held |
| * * may sleep |
| */ |
| int kvm_s390_faultin_gfn(struct kvm_vcpu *vcpu, struct kvm *kvm, struct guest_fault *f) |
| { |
| struct kvm_s390_mmu_cache *local_mc __free(kvm_s390_mmu_cache) = NULL; |
| struct kvm_s390_mmu_cache *mc = NULL; |
| struct kvm_memory_slot *slot; |
| unsigned long inv_seq; |
| int foll, rc = 0; |
| |
| foll = f->write_attempt ? FOLL_WRITE : 0; |
| foll |= f->attempt_pfault ? FOLL_NOWAIT : 0; |
| |
| if (vcpu) { |
| kvm = vcpu->kvm; |
| mc = vcpu->arch.mc; |
| } |
| |
| lockdep_assert_held(&kvm->srcu); |
| |
| scoped_guard(read_lock, &kvm->mmu_lock) { |
| if (gmap_try_fixup_minor(kvm->arch.gmap, f) == 0) |
| return 0; |
| } |
| |
| while (1) { |
| f->valid = false; |
| inv_seq = kvm->mmu_invalidate_seq; |
| /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */ |
| smp_rmb(); |
| |
| if (vcpu) |
| slot = kvm_vcpu_gfn_to_memslot(vcpu, f->gfn); |
| else |
| slot = gfn_to_memslot(kvm, f->gfn); |
| f->pfn = __kvm_faultin_pfn(slot, f->gfn, foll, &f->writable, &f->page); |
| |
| /* Needs I/O, try to setup async pfault (only possible with FOLL_NOWAIT). */ |
| if (f->pfn == KVM_PFN_ERR_NEEDS_IO) { |
| if (unlikely(!f->attempt_pfault)) |
| return -EAGAIN; |
| if (unlikely(!vcpu)) |
| return -EINVAL; |
| trace_kvm_s390_major_guest_pfault(vcpu); |
| if (kvm_arch_setup_async_pf(vcpu)) |
| return 0; |
| vcpu->stat.pfault_sync++; |
| /* Could not setup async pfault, try again synchronously. */ |
| foll &= ~FOLL_NOWAIT; |
| f->pfn = __kvm_faultin_pfn(slot, f->gfn, foll, &f->writable, &f->page); |
| } |
| |
| /* Access outside memory, addressing exception. */ |
| if (is_noslot_pfn(f->pfn)) |
| return PGM_ADDRESSING; |
| /* Signal pending: try again. */ |
| if (f->pfn == KVM_PFN_ERR_SIGPENDING) |
| return -EAGAIN; |
| /* Check if it's read-only memory; don't try to actually handle that case. */ |
| if (f->pfn == KVM_PFN_ERR_RO_FAULT) |
| return -EOPNOTSUPP; |
| /* Any other error. */ |
| if (is_error_pfn(f->pfn)) |
| return -EFAULT; |
| |
| if (!mc) { |
| local_mc = kvm_s390_new_mmu_cache(); |
| if (!local_mc) |
| return -ENOMEM; |
| mc = local_mc; |
| } |
| |
| /* Loop, will automatically release the faulted page. */ |
| if (mmu_invalidate_retry_gfn_unsafe(kvm, inv_seq, f->gfn)) { |
| kvm_release_faultin_page(kvm, f->page, true, false); |
| continue; |
| } |
| |
| scoped_guard(read_lock, &kvm->mmu_lock) { |
| if (!mmu_invalidate_retry_gfn(kvm, inv_seq, f->gfn)) { |
| f->valid = true; |
| rc = gmap_link(mc, kvm->arch.gmap, f); |
| kvm_release_faultin_page(kvm, f->page, !!rc, f->write_attempt); |
| f->page = NULL; |
| } |
| } |
| kvm_release_faultin_page(kvm, f->page, true, false); |
| |
| if (rc == -ENOMEM) { |
| rc = kvm_s390_mmu_cache_topup(mc); |
| if (rc) |
| return rc; |
| } else if (rc != -EAGAIN) { |
| return rc; |
| } |
| } |
| } |
| |
| int kvm_s390_get_guest_page(struct kvm *kvm, struct guest_fault *f, gfn_t gfn, bool w) |
| { |
| struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn); |
| int foll = w ? FOLL_WRITE : 0; |
| |
| f->write_attempt = w; |
| f->gfn = gfn; |
| f->pfn = __kvm_faultin_pfn(slot, gfn, foll, &f->writable, &f->page); |
| if (is_noslot_pfn(f->pfn)) |
| return PGM_ADDRESSING; |
| if (is_sigpending_pfn(f->pfn)) |
| return -EINTR; |
| if (f->pfn == KVM_PFN_ERR_NEEDS_IO) |
| return -EAGAIN; |
| if (is_error_pfn(f->pfn)) |
| return -EFAULT; |
| |
| f->valid = true; |
| return 0; |
| } |