[PATCH v2 06/30] KVM: arm64: Extract page table mapping in user_mem_abort()
Anshuman Khandual
anshuman.khandual at arm.com
Mon Mar 30 20:45:00 PDT 2026
On 27/03/26 5:05 PM, Marc Zyngier wrote:
> From: Fuad Tabba <tabba at google.com>
>
> Extract the code responsible for locking the KVM MMU and mapping the PFN
> into the stage-2 page tables into a new helper, kvm_s2_fault_map().
>
> This helper manages the kvm_fault_lock, checks for MMU invalidation
> retries, attempts to adjust for transparent huge pages (THP), handles
> MTE sanitization if needed, and finally maps or relaxes permissions on
> the stage-2 entries.
>
> With this change, the main user_mem_abort() function is now a sequential
> dispatcher that delegates to specialized helper functions.
>
> Signed-off-by: Fuad Tabba <tabba at google.com>
> Signed-off-by: Marc Zyngier <maz at kernel.org>
Reviewed-by: Anshuman Khandual <anshuman.khandual at arm.com>
> ---
> arch/arm64/kvm/mmu.c | 128 +++++++++++++++++++++++--------------------
> 1 file changed, 68 insertions(+), 60 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index d1ffdce18631a..164f1160ea33d 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1892,68 +1892,13 @@ static int kvm_s2_fault_compute_prot(struct kvm_s2_fault *fault)
> return 0;
> }
>
> -static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
> - struct kvm_s2_trans *nested,
> - struct kvm_memory_slot *memslot, unsigned long hva,
> - bool fault_is_perm)
> +static int kvm_s2_fault_map(struct kvm_s2_fault *fault, void *memcache)
> {
> - int ret = 0;
> - struct kvm_s2_fault fault_data = {
> - .vcpu = vcpu,
> - .fault_ipa = fault_ipa,
> - .nested = nested,
> - .memslot = memslot,
> - .hva = hva,
> - .fault_is_perm = fault_is_perm,
> - .ipa = fault_ipa,
> - .logging_active = memslot_is_logging(memslot),
> - .force_pte = memslot_is_logging(memslot),
> - .s2_force_noncacheable = false,
> - .vfio_allow_any_uc = false,
> - .prot = KVM_PGTABLE_PROT_R,
> - };
> - struct kvm_s2_fault *fault = &fault_data;
> - struct kvm *kvm = vcpu->kvm;
> - void *memcache;
> + struct kvm *kvm = fault->vcpu->kvm;
> struct kvm_pgtable *pgt;
> + int ret;
> enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
>
> - if (fault->fault_is_perm)
> - fault->fault_granule = kvm_vcpu_trap_get_perm_fault_granule(fault->vcpu);
> - fault->write_fault = kvm_is_write_fault(fault->vcpu);
> - fault->exec_fault = kvm_vcpu_trap_is_exec_fault(fault->vcpu);
> - VM_WARN_ON_ONCE(fault->write_fault && fault->exec_fault);
> -
> - /*
> - * Permission faults just need to update the existing leaf entry,
> - * and so normally don't require allocations from the memcache. The
> - * only exception to this is when dirty logging is enabled at runtime
> - * and a write fault needs to collapse a block entry into a table.
> - */
> - fault->topup_memcache = !fault->fault_is_perm ||
> - (fault->logging_active && fault->write_fault);
> - ret = prepare_mmu_memcache(fault->vcpu, fault->topup_memcache, &memcache);
> - if (ret)
> - return ret;
> -
> - /*
> - * Let's check if we will get back a huge page backed by hugetlbfs, or
> - * get block mapping for device MMIO region.
> - */
> - ret = kvm_s2_fault_pin_pfn(fault);
> - if (ret != 1)
> - return ret;
> -
> - ret = 0;
> -
> - ret = kvm_s2_fault_compute_prot(fault);
> - if (ret == 1) {
> - ret = 1; /* fault injected */
> - goto out_put_page;
> - }
> - if (ret)
> - goto out_put_page;
> -
> kvm_fault_lock(kvm);
> pgt = fault->vcpu->arch.hw_mmu->pgt;
> if (mmu_invalidate_retry(kvm, fault->mmu_seq)) {
> @@ -2001,8 +1946,8 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
> * PTE, which will be preserved.
> */
> fault->prot &= ~KVM_NV_GUEST_MAP_SZ;
> - ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, fault->fault_ipa, fault->prot,
> - flags);
> + ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, fault->fault_ipa,
> + fault->prot, flags);
> } else {
> ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, fault->fault_ipa, fault->vma_pagesize,
> __pfn_to_phys(fault->pfn), fault->prot,
> @@ -2018,6 +1963,69 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
> mark_page_dirty_in_slot(kvm, fault->memslot, fault->gfn);
>
> return ret != -EAGAIN ? ret : 0;
> +}
> +
> +static int user_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa,
> + struct kvm_s2_trans *nested,
> + struct kvm_memory_slot *memslot, unsigned long hva,
> + bool fault_is_perm)
> +{
> + int ret = 0;
> + struct kvm_s2_fault fault_data = {
> + .vcpu = vcpu,
> + .fault_ipa = fault_ipa,
> + .nested = nested,
> + .memslot = memslot,
> + .hva = hva,
> + .fault_is_perm = fault_is_perm,
> + .ipa = fault_ipa,
> + .logging_active = memslot_is_logging(memslot),
> + .force_pte = memslot_is_logging(memslot),
> + .s2_force_noncacheable = false,
> + .vfio_allow_any_uc = false,
> + .prot = KVM_PGTABLE_PROT_R,
> + };
> + struct kvm_s2_fault *fault = &fault_data;
> + void *memcache;
> +
> + if (fault->fault_is_perm)
> + fault->fault_granule = kvm_vcpu_trap_get_perm_fault_granule(fault->vcpu);
> + fault->write_fault = kvm_is_write_fault(fault->vcpu);
> + fault->exec_fault = kvm_vcpu_trap_is_exec_fault(fault->vcpu);
> + VM_WARN_ON_ONCE(fault->write_fault && fault->exec_fault);
> +
> + /*
> + * Permission faults just need to update the existing leaf entry,
> + * and so normally don't require allocations from the memcache. The
> + * only exception to this is when dirty logging is enabled at runtime
> + * and a write fault needs to collapse a block entry into a table.
> + */
> + fault->topup_memcache = !fault->fault_is_perm ||
> + (fault->logging_active && fault->write_fault);
> + ret = prepare_mmu_memcache(fault->vcpu, fault->topup_memcache, &memcache);
> + if (ret)
> + return ret;
> +
> + /*
> + * Let's check if we will get back a huge page backed by hugetlbfs, or
> + * get block mapping for device MMIO region.
> + */
> + ret = kvm_s2_fault_pin_pfn(fault);
> + if (ret != 1)
> + return ret;
> +
> + ret = 0;
> +
> + ret = kvm_s2_fault_compute_prot(fault);
> + if (ret == 1) {
> + ret = 1; /* fault injected */
> + goto out_put_page;
> + }
> + if (ret)
> + goto out_put_page;
> +
> + ret = kvm_s2_fault_map(fault, memcache);
> + return ret;
>
> out_put_page:
> kvm_release_page_unused(fault->page);
More information about the linux-arm-kernel
mailing list