[PATCH 20/20] KVM: arm64: Stage-2 huge mappings for protected VMs
Vincent Donnefort <[email protected]> Mon, 3 Aug 2026 11:09:04 +0100
| Newsgroups | dev.linux.lists.kvmarm,org.infradead.lists.linux-arm-kernel |
|---|---|
| Message-ID | <[email protected]> |
Enable PMD-sized stage-2 block mappings for protected VMs. This is possible whenever the stage-1 mapping allows it, that is if it itself backed by THPs. When a THP is found, an entire PMD_SIZE mapping is donated to the guest. This mapping can only be broken down via the HVC __pkvm_host_split_guest() which the hypervisor can request with PKVM_HYP_REQ_SPLIT. Signed-off-by: Vincent Donnefort <[email protected]> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 6c941aaa10c6..46820240d233 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -1690,21 +1690,6 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd) return ret != -EAGAIN ? ret : 0; } -struct kvm_s2_fault_vma_info { - unsigned long mmu_seq; - long vma_pagesize; - vm_flags_t vm_flags; - unsigned long max_map_size; - struct page *page; - kvm_pfn_t pfn; - gfn_t gfn; - bool device; - bool mte_allowed; - bool is_vma_cacheable; - bool map_writable; - bool map_non_cacheable; -}; - static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd) { unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE; @@ -1714,6 +1699,9 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd) struct kvm *kvm = vcpu->kvm; void *hyp_memcache; struct page *page; + long page_size; + kvm_pfn_t pfn; + gfn_t gfn; int ret; hyp_memcache = get_mmu_memcache(vcpu); @@ -1721,21 +1709,15 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd) if (ret) return -ENOMEM; - ret = account_locked_vm(mm, 1, true); - if (ret) - return ret; - mmap_read_lock(mm); ret = pin_user_pages(s2fd->hva, 1, flags, &page); mmap_read_unlock(mm); if (ret == -EHWPOISON) { kvm_send_hwpoison_signal(s2fd->hva, PAGE_SHIFT); - ret = 0; - goto dec_account; + return 0; } else if (ret != 1) { - ret = -EFAULT; - goto dec_account; + return -EFAULT; } else if (!folio_test_swapbacked(page_folio(page))) { /* * We really can't deal with page-cache pages returned by GUP @@ -1755,25 +1737,59 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd) goto unpin; } + pfn = page_to_pfn(page); + gfn = gpa_to_gfn(s2fd->fault_ipa); + + page_size = transparent_hugepage_adjust(kvm, s2fd->memslot, s2fd->hva, &pfn, &gfn); + if (page_size < 0) { + ret = page_size; + goto unpin; + } else if (page_size == PMD_SIZE) { + if (WARN_ON(!folio_test_large(page_folio(page)))) { + ret = -EINVAL; + goto unpin; + } + } + + ret = account_locked_vm(mm, page_size / PAGE_SIZE, true); + if (ret) + goto unpin; + write_lock(&kvm->mmu_lock); - ret = pkvm_pgtable_stage2_map(pgt, s2fd->fault_ipa, PAGE_SIZE, - page_to_phys(page), KVM_PGTABLE_PROT_RWX, + ret = pkvm_pgtable_stage2_map(pgt, gfn_to_gpa(gfn), page_size, + __pfn_to_phys(pfn), KVM_PGTABLE_PROT_RWX, hyp_memcache, 0); write_unlock(&kvm->mmu_lock); if (ret) { if (ret == -EAGAIN) ret = 0; - goto unpin; + goto dec_account; } return 0; + +dec_account: + account_locked_vm(mm, page_size / PAGE_SIZE, false); unpin: unpin_user_pages(&page, 1); -dec_account: - account_locked_vm(mm, 1, false); return ret; } +struct kvm_s2_fault_vma_info { + unsigned long mmu_seq; + long vma_pagesize; + vm_flags_t vm_flags; + unsigned long max_map_size; + struct page *page; + kvm_pfn_t pfn; + gfn_t gfn; + bool device; + bool mte_allowed; + bool is_vma_cacheable; + bool map_writable; + bool map_non_cacheable; +}; + static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd, struct kvm_s2_fault_vma_info *s2vi, struct vm_area_struct *vma) diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c index 089b77cf2f6a..0b591c44998d 100644 --- a/arch/arm64/kvm/pkvm.c +++ b/arch/arm64/kvm/pkvm.c @@ -454,9 +454,8 @@ static int __pkvm_pgtable_stage2_reclaim(struct kvm_pgtable *pgt, u64 start, u64 continue; page = pfn_to_page(mapping->pfn); - WARN_ON_ONCE(mapping->nr_pages != 1); unpin_user_pages_dirty_lock(&page, 1, true); - account_locked_vm(kvm->mm, 1, false); + account_locked_vm(kvm->mm, mapping->nr_pages, false); pkvm_mapping_remove(mapping, &pgt->pkvm_mappings); kfree(mapping); } @@ -521,14 +520,13 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size, u64 end = addr + size; int ret; + if (WARN_ON_ONCE(size != PAGE_SIZE && size != PMD_SIZE)) + return -EINVAL; + lockdep_assert_held_write(&kvm->mmu_lock); mapping = pkvm_mapping_iter_first(&pgt->pkvm_mappings, addr, end - 1); if (kvm_vm_is_protected(kvm)) { - /* Protected VMs are mapped using RWX page-granular mappings */ - if (WARN_ON_ONCE(size != PAGE_SIZE)) - return -EINVAL; - if (WARN_ON_ONCE(prot != KVM_PGTABLE_PROT_RWX)) return -EINVAL; @@ -541,10 +539,8 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size, return ret ? -EFAULT : -EAGAIN; } - ret = kvm_call_hyp_nvhe(__pkvm_host_donate_guest, pfn, gfn, 1); + ret = kvm_call_hyp_nvhe(__pkvm_host_donate_guest, pfn, gfn, size / PAGE_SIZE); } else { - if (WARN_ON_ONCE(size != PAGE_SIZE && size != PMD_SIZE)) - return -EINVAL; /* * We either raced with another vCPU or we're changing between -- 2.55.0.508.g3f0d502094-goog