* [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K
@ 2026-09-22 13:08 Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 1/6] KVM: arm64: Fix MMFR0 TGRAN advertisement for pVMs Vincent Donnefort
` (5 more replies)
0 siblings, 6 replies; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Vincent Donnefort
This series allows booting protected VMs and guest_memfd-back VMs on
systems with a page size larger than 4K.
Currently, the fault handling assumes 4K alignment, as HPFAR_EL2
provides the IPA minus the bottom 12 bits regardless of the system page
size. This leads to mapping failures on 16K and 64K systems.
This series addresses the alignment mismatch by relying on struct
kvm_s2_fault_vma_info in both gmem_abort() and pkvm_mem_abort() to
retrieve correctly page-aligned addresses.
This series bundles the following previously discussed fixes:
1. https://lore.kernel.org/all/20260915091606.2111217-1-vdonnefort@google.com
2. https://lore.kernel.org/all/20260915084438.2076072-1-vdonnefort@google.com
Fuad Tabba (2):
KVM: arm64: Move gmem_abort()
KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort()
Vincent Donnefort (4):
KVM: arm64: Fix MMFR0 TGRAN advertisement for pVMs
KVM: arm64: Pass kvm_s2_fault_desc to
fault_supports_stage2_huge_mapping()
KVM: arm64: Move pkvm_mem_abort()
KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort()
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 3 +
arch/arm64/kvm/mmu.c | 357 +++++++++++++++--------------
arch/arm64/kvm/pkvm.c | 3 +
3 files changed, 186 insertions(+), 177 deletions(-)
base-commit: 93f51579e7df248780214094418f205253383cc5
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v1 1/6] KVM: arm64: Fix MMFR0 TGRAN advertisement for pVMs
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
@ 2026-09-22 13:08 ` Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping() Vincent Donnefort
` (4 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Vincent Donnefort, stable
Protected VM ID register emulation leaves unlisted fields at 0. This
creates two issues for TGRAN:
* TGRAN16: 0 means non-implemented. It prevents 16KB protected VMs
from booting even when the hardware supports it.
* TGRAN4|TGRAN64: 0 means implemented. This may falsely advertise the
feature even when the hardware does not support it.
Advertise all the TGRAN fields in pvmid_aa64mmfr0 so that the hardware
support is properly propagated.
Cc: stable@vger.kernel.org
Fixes: 6c30bfb18d0b ("KVM: arm64: Add handlers for protected VM System Registers")
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Tested-by: Fuad Tabba <fuad.tabba@linux.dev>
Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 8758c6801776..ec469d96516a 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -121,6 +121,9 @@ static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
MAX_FEAT(ID_AA64MMFR0_EL1, SNSMEM, IMP),
MAX_FEAT(ID_AA64MMFR0_EL1, BIGENDEL0, IMP),
MAX_FEAT(ID_AA64MMFR0_EL1, EXS, IMP),
+ MAX_FEAT(ID_AA64MMFR0_EL1, TGRAN4, IMP),
+ MAX_FEAT(ID_AA64MMFR0_EL1, TGRAN16, IMP),
+ MAX_FEAT(ID_AA64MMFR0_EL1, TGRAN64, IMP),
FEAT_END
};
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping()
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 1/6] KVM: arm64: Fix MMFR0 TGRAN advertisement for pVMs Vincent Donnefort
@ 2026-09-22 13:08 ` Vincent Donnefort
2026-09-23 19:33 ` Fuad Tabba
2026-09-25 17:46 ` Wei-Lin Chang
2026-09-22 13:08 ` [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort() Vincent Donnefort
` (3 subsequent siblings)
5 siblings, 2 replies; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Vincent Donnefort
Pass the fault descriptor to fault_supports_stage2_huge_mapping() instead
of passing memslot and hva separately so it can access other s2fd
members such as vcpu.
No functional change intended.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/mmu.c | 39 +++++++++++++++++++--------------------
1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9ba86450fe4a..9dfaf4f277b5 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1353,12 +1353,21 @@ static void kvm_send_hwpoison_signal(unsigned long address, short lsb)
send_sig_mceerr(BUS_MCEERR_AR, (void __user *)address, lsb, current);
}
-static bool fault_supports_stage2_huge_mapping(struct kvm_memory_slot *memslot,
- unsigned long hva,
+struct kvm_s2_fault_desc {
+ struct kvm_vcpu *vcpu;
+ phys_addr_t fault_ipa;
+ struct kvm_s2_trans *nested;
+ struct kvm_memory_slot *memslot;
+ unsigned long hva;
+};
+
+static bool fault_supports_stage2_huge_mapping(const struct kvm_s2_fault_desc *s2fd,
unsigned long map_size)
{
- gpa_t gpa_start;
+ struct kvm_memory_slot *memslot = s2fd->memslot;
+ unsigned long hva = s2fd->hva;
hva_t uaddr_start, uaddr_end;
+ gpa_t gpa_start;
size_t size;
/* The memslot and the VMA are guaranteed to be aligned to PAGE_SIZE */
@@ -1427,9 +1436,9 @@ static bool fault_supports_stage2_huge_mapping(struct kvm_memory_slot *memslot,
* Returns the size of the mapping.
*/
static long
-transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot,
- unsigned long hva, kvm_pfn_t *pfnp, gfn_t *gfnp)
+transparent_hugepage_adjust(const struct kvm_s2_fault_desc *s2fd, kvm_pfn_t *pfnp, gfn_t *gfnp)
{
+ struct kvm *kvm = s2fd->vcpu->kvm;
kvm_pfn_t pfn = *pfnp;
gfn_t gfn = *gfnp;
@@ -1438,8 +1447,8 @@ transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot,
* sure that the HVA and IPA are sufficiently aligned and that the
* block map is contained within the memslot.
*/
- if (fault_supports_stage2_huge_mapping(memslot, hva, PMD_SIZE)) {
- int sz = get_user_mapping_size(kvm, hva);
+ if (fault_supports_stage2_huge_mapping(s2fd, PMD_SIZE)) {
+ int sz = get_user_mapping_size(kvm, s2fd->hva);
if (sz < 0)
return sz;
@@ -1597,14 +1606,6 @@ static enum kvm_pgtable_prot adjust_nested_exec_perms(struct kvm *kvm,
return prot;
}
-struct kvm_s2_fault_desc {
- struct kvm_vcpu *vcpu;
- phys_addr_t fault_ipa;
- struct kvm_s2_trans *nested;
- struct kvm_memory_slot *memslot;
- unsigned long hva;
-};
-
static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
{
bool write_fault, exec_fault;
@@ -1791,7 +1792,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
switch (vma_shift) {
#ifndef __PAGETABLE_PMD_FOLDED
case PUD_SHIFT:
- if (fault_supports_stage2_huge_mapping(s2fd->memslot, s2fd->hva, PUD_SIZE))
+ if (fault_supports_stage2_huge_mapping(s2fd, PUD_SIZE))
break;
fallthrough;
#endif
@@ -1799,7 +1800,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
vma_shift = PMD_SHIFT;
fallthrough;
case PMD_SHIFT:
- if (fault_supports_stage2_huge_mapping(s2fd->memslot, s2fd->hva, PMD_SIZE))
+ if (fault_supports_stage2_huge_mapping(s2fd, PMD_SIZE))
break;
fallthrough;
case CONT_PTE_SHIFT:
@@ -2049,9 +2050,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
if (perm_fault_granule > PAGE_SIZE) {
mapping_size = perm_fault_granule;
} else {
- mapping_size = transparent_hugepage_adjust(kvm, s2fd->memslot,
- s2fd->hva, &pfn,
- &gfn);
+ mapping_size = transparent_hugepage_adjust(s2fd, &pfn, &gfn);
if (mapping_size < 0) {
ret = mapping_size;
goto out_unlock;
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort()
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 1/6] KVM: arm64: Fix MMFR0 TGRAN advertisement for pVMs Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping() Vincent Donnefort
@ 2026-09-22 13:08 ` Vincent Donnefort
2026-09-23 19:38 ` Fuad Tabba
2026-09-27 16:31 ` Marc Zyngier
2026-09-22 13:08 ` [PATCH v1 4/6] KVM: arm64: Move gmem_abort() Vincent Donnefort
` (2 subsequent siblings)
5 siblings, 2 replies; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Vincent Donnefort
Move pkvm_mem_abort() below kvm_s2_fault_get_vma_info() so it can
resolve stage-2 fault VMA metadata without forward declarations.
No functional change intended.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/mmu.c | 138 +++++++++++++++++++++----------------------
1 file changed, 69 insertions(+), 69 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9dfaf4f277b5..242f63066252 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1706,75 +1706,6 @@ struct kvm_s2_fault_vma_info {
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;
- struct kvm_vcpu *vcpu = s2fd->vcpu;
- struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
- struct mm_struct *mm = current->mm;
- struct kvm *kvm = vcpu->kvm;
- void *hyp_memcache;
- struct page *page;
- int ret;
-
- hyp_memcache = get_mmu_memcache(vcpu);
- ret = topup_mmu_memcache(vcpu, hyp_memcache);
- 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;
- } else if (ret != 1) {
- ret = -EFAULT;
- goto dec_account;
- } else if (!folio_test_swapbacked(page_folio(page))) {
- /*
- * We really can't deal with page-cache pages returned by GUP
- * because (a) we may trigger writeback of a page for which we
- * no longer have access and (b) page_mkclean() won't find the
- * stage-2 mapping in the rmap so we can get out-of-whack with
- * the filesystem when marking the page dirty during unpinning
- * (see cc5095747edf ("ext4: don't BUG if someone dirty pages
- * without asking ext4 first")).
- *
- * Ideally we'd just restrict ourselves to anonymous pages, but
- * we also want to allow memfd (i.e. shmem) pages, so check for
- * pages backed by swap in the knowledge that the GUP pin will
- * prevent try_to_unmap() from succeeding.
- */
- ret = -EIO;
- 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,
- hyp_memcache, 0);
- write_unlock(&kvm->mmu_lock);
- if (ret) {
- if (ret == -EAGAIN)
- ret = 0;
- goto unpin;
- }
-
- return 0;
-unpin:
- unpin_user_pages(&page, 1);
-dec_account:
- account_locked_vm(mm, 1, false);
- return ret;
-}
-
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)
@@ -2235,6 +2166,75 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
return 0;
}
+static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
+{
+ unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
+ struct kvm_vcpu *vcpu = s2fd->vcpu;
+ struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
+ struct mm_struct *mm = current->mm;
+ struct kvm *kvm = vcpu->kvm;
+ void *hyp_memcache;
+ struct page *page;
+ int ret;
+
+ hyp_memcache = get_mmu_memcache(vcpu);
+ ret = topup_mmu_memcache(vcpu, hyp_memcache);
+ 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;
+ } else if (ret != 1) {
+ ret = -EFAULT;
+ goto dec_account;
+ } else if (!folio_test_swapbacked(page_folio(page))) {
+ /*
+ * We really can't deal with page-cache pages returned by GUP
+ * because (a) we may trigger writeback of a page for which we
+ * no longer have access and (b) page_mkclean() won't find the
+ * stage-2 mapping in the rmap so we can get out-of-whack with
+ * the filesystem when marking the page dirty during unpinning
+ * (see cc5095747edf ("ext4: don't BUG if someone dirty pages
+ * without asking ext4 first")).
+ *
+ * Ideally we'd just restrict ourselves to anonymous pages, but
+ * we also want to allow memfd (i.e. shmem) pages, so check for
+ * pages backed by swap in the knowledge that the GUP pin will
+ * prevent try_to_unmap() from succeeding.
+ */
+ ret = -EIO;
+ 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,
+ hyp_memcache, 0);
+ write_unlock(&kvm->mmu_lock);
+ if (ret) {
+ if (ret == -EAGAIN)
+ ret = 0;
+ goto unpin;
+ }
+
+ return 0;
+unpin:
+ unpin_user_pages(&page, 1);
+dec_account:
+ account_locked_vm(mm, 1, false);
+ return ret;
+}
+
/**
* kvm_handle_guest_abort - handles all 2nd stage aborts
* @vcpu: the VCPU pointer
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 4/6] KVM: arm64: Move gmem_abort()
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
` (2 preceding siblings ...)
2026-09-22 13:08 ` [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort() Vincent Donnefort
@ 2026-09-22 13:08 ` Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort() Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 6/6] KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort() Vincent Donnefort
5 siblings, 0 replies; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Vincent Donnefort
From: Fuad Tabba <fuad.tabba@linux.dev>
Move gmem_abort() below get_canonical_gfn() so that the next patch can
use it without a forward declaration.
No functional change intended.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/mmu.c | 170 +++++++++++++++++++++----------------------
1 file changed, 85 insertions(+), 85 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 242f63066252..5aac624b0442 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1606,91 +1606,6 @@ static enum kvm_pgtable_prot adjust_nested_exec_perms(struct kvm *kvm,
return prot;
}
-static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
-{
- bool write_fault, exec_fault;
- bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
- enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
- enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
- struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
- unsigned long mmu_seq;
- struct page *page;
- struct kvm *kvm = s2fd->vcpu->kvm;
- void *memcache = NULL;
- kvm_pfn_t pfn;
- gfn_t gfn;
- int ret;
-
- if (!perm_fault) {
- memcache = get_mmu_memcache(s2fd->vcpu);
- ret = topup_mmu_memcache(s2fd->vcpu, memcache);
- if (ret)
- return ret;
- }
-
- if (s2fd->nested)
- gfn = kvm_s2_trans_output(s2fd->nested) >> PAGE_SHIFT;
- else
- gfn = s2fd->fault_ipa >> PAGE_SHIFT;
-
- write_fault = kvm_is_write_fault(s2fd->vcpu);
- exec_fault = kvm_vcpu_trap_is_exec_fault(s2fd->vcpu);
-
- VM_WARN_ON_ONCE(write_fault && exec_fault);
-
- mmu_seq = kvm->mmu_invalidate_seq;
- /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
- smp_rmb();
-
- ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &pfn, &page, NULL);
- if (ret) {
- kvm_prepare_memory_fault_exit(s2fd->vcpu, s2fd->fault_ipa, PAGE_SIZE,
- write_fault, exec_fault, false);
- return ret;
- }
-
- if (!(s2fd->memslot->flags & KVM_MEM_READONLY))
- prot |= KVM_PGTABLE_PROT_W;
-
- if (s2fd->nested)
- prot = adjust_nested_fault_perms(s2fd->nested, prot);
-
- if (exec_fault || cpus_have_final_cap(ARM64_HAS_CACHE_DIC))
- prot |= KVM_PGTABLE_PROT_X;
-
- if (s2fd->nested)
- prot = adjust_nested_exec_perms(kvm, s2fd->nested, prot);
-
- kvm_fault_lock(kvm);
- if (mmu_invalidate_retry(kvm, mmu_seq)) {
- ret = -EAGAIN;
- goto out_unlock;
- }
-
- if (perm_fault) {
- /*
- * Drop the SW bits in favour of those stored in the
- * PTE, which will be preserved.
- */
- prot &= ~KVM_NV_GUEST_MAP_SZ;
- ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, s2fd->fault_ipa,
- prot, flags);
- } else {
- ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, s2fd->fault_ipa, PAGE_SIZE,
- __pfn_to_phys(pfn), prot,
- memcache, flags);
- }
-
-out_unlock:
- kvm_release_faultin_page(kvm, page, !!ret, prot & KVM_PGTABLE_PROT_W);
- kvm_fault_unlock(kvm);
-
- if ((prot & KVM_PGTABLE_PROT_W) && !ret)
- mark_page_dirty_in_slot(kvm, s2fd->memslot, gfn);
-
- return ret != -EAGAIN ? ret : 0;
-}
-
struct kvm_s2_fault_vma_info {
unsigned long mmu_seq;
long vma_pagesize;
@@ -2031,6 +1946,91 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
return 0;
}
+static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
+{
+ bool write_fault, exec_fault;
+ bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
+ enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
+ enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
+ struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
+ unsigned long mmu_seq;
+ struct page *page;
+ struct kvm *kvm = s2fd->vcpu->kvm;
+ void *memcache = NULL;
+ kvm_pfn_t pfn;
+ gfn_t gfn;
+ int ret;
+
+ if (!perm_fault) {
+ memcache = get_mmu_memcache(s2fd->vcpu);
+ ret = topup_mmu_memcache(s2fd->vcpu, memcache);
+ if (ret)
+ return ret;
+ }
+
+ if (s2fd->nested)
+ gfn = kvm_s2_trans_output(s2fd->nested) >> PAGE_SHIFT;
+ else
+ gfn = s2fd->fault_ipa >> PAGE_SHIFT;
+
+ write_fault = kvm_is_write_fault(s2fd->vcpu);
+ exec_fault = kvm_vcpu_trap_is_exec_fault(s2fd->vcpu);
+
+ VM_WARN_ON_ONCE(write_fault && exec_fault);
+
+ mmu_seq = kvm->mmu_invalidate_seq;
+ /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
+ smp_rmb();
+
+ ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &pfn, &page, NULL);
+ if (ret) {
+ kvm_prepare_memory_fault_exit(s2fd->vcpu, s2fd->fault_ipa, PAGE_SIZE,
+ write_fault, exec_fault, false);
+ return ret;
+ }
+
+ if (!(s2fd->memslot->flags & KVM_MEM_READONLY))
+ prot |= KVM_PGTABLE_PROT_W;
+
+ if (s2fd->nested)
+ prot = adjust_nested_fault_perms(s2fd->nested, prot);
+
+ if (exec_fault || cpus_have_final_cap(ARM64_HAS_CACHE_DIC))
+ prot |= KVM_PGTABLE_PROT_X;
+
+ if (s2fd->nested)
+ prot = adjust_nested_exec_perms(kvm, s2fd->nested, prot);
+
+ kvm_fault_lock(kvm);
+ if (mmu_invalidate_retry(kvm, mmu_seq)) {
+ ret = -EAGAIN;
+ goto out_unlock;
+ }
+
+ if (perm_fault) {
+ /*
+ * Drop the SW bits in favour of those stored in the
+ * PTE, which will be preserved.
+ */
+ prot &= ~KVM_NV_GUEST_MAP_SZ;
+ ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, s2fd->fault_ipa,
+ prot, flags);
+ } else {
+ ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, s2fd->fault_ipa, PAGE_SIZE,
+ __pfn_to_phys(pfn), prot,
+ memcache, flags);
+ }
+
+out_unlock:
+ kvm_release_faultin_page(kvm, page, !!ret, prot & KVM_PGTABLE_PROT_W);
+ kvm_fault_unlock(kvm);
+
+ if ((prot & KVM_PGTABLE_PROT_W) && !ret)
+ mark_page_dirty_in_slot(kvm, s2fd->memslot, gfn);
+
+ return ret != -EAGAIN ? ret : 0;
+}
+
static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
{
bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort()
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
` (3 preceding siblings ...)
2026-09-22 13:08 ` [PATCH v1 4/6] KVM: arm64: Move gmem_abort() Vincent Donnefort
@ 2026-09-22 13:08 ` Vincent Donnefort
2026-09-25 17:36 ` Wei-Lin Chang
2026-09-22 13:08 ` [PATCH v1 6/6] KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort() Vincent Donnefort
5 siblings, 1 reply; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Sashiko, stable,
Vincent Donnefort
From: Fuad Tabba <fuad.tabba@linux.dev>
gmem_abort() maps at s2fd->fault_ipa, which HPFAR_EL2 holds at 4K
granularity whatever the page size. The generic page-table code aligns
it, but pkvm_pgtable_stage2_map() looks up existing mappings over
[addr, addr + size), so on a pKVM host with pages larger than 4K a
guest_memfd-backed guest that faults past the first 4K of a page can
find its neighbour's mapping, get -EAGAIN and take the same fault
forever.
Take the addresses from kvm_s2_fault_vma_info instead, as
user_mem_abort() does, and report the gfn kvm_gmem_get_pfn() failed on
in the memory fault exit. kvm_s2_fault_get_vma_info() itself isn't
called: a guest_memfd memslot's userspace_addr doesn't need to be
backed by a VMA.
Fixes: a7b57e0995927 ("KVM: arm64: Handle guest_memfd-backed guest page faults")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/r/20260913175105.A57AC1F000FF@smtp.kernel.org
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/mmu.c | 28 +++++++++++++---------------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 5aac624b0442..ee0fcca838c1 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1953,11 +1953,9 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
- unsigned long mmu_seq;
- struct page *page;
+ struct kvm_s2_fault_vma_info s2vi = {};
struct kvm *kvm = s2fd->vcpu->kvm;
void *memcache = NULL;
- kvm_pfn_t pfn;
gfn_t gfn;
int ret;
@@ -1968,23 +1966,22 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
return ret;
}
- if (s2fd->nested)
- gfn = kvm_s2_trans_output(s2fd->nested) >> PAGE_SHIFT;
- else
- gfn = s2fd->fault_ipa >> PAGE_SHIFT;
+ s2vi.vma_pagesize = PAGE_SIZE;
+ s2vi.gfn = ALIGN_DOWN(s2fd->fault_ipa, s2vi.vma_pagesize) >> PAGE_SHIFT;
+ gfn = get_canonical_gfn(s2fd, &s2vi);
write_fault = kvm_is_write_fault(s2fd->vcpu);
exec_fault = kvm_vcpu_trap_is_exec_fault(s2fd->vcpu);
VM_WARN_ON_ONCE(write_fault && exec_fault);
- mmu_seq = kvm->mmu_invalidate_seq;
+ s2vi.mmu_seq = kvm->mmu_invalidate_seq;
/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
smp_rmb();
- ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &pfn, &page, NULL);
+ ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &s2vi.pfn, &s2vi.page, NULL);
if (ret) {
- kvm_prepare_memory_fault_exit(s2fd->vcpu, s2fd->fault_ipa, PAGE_SIZE,
+ kvm_prepare_memory_fault_exit(s2fd->vcpu, gfn_to_gpa(gfn), s2vi.vma_pagesize,
write_fault, exec_fault, false);
return ret;
}
@@ -2002,7 +1999,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
prot = adjust_nested_exec_perms(kvm, s2fd->nested, prot);
kvm_fault_lock(kvm);
- if (mmu_invalidate_retry(kvm, mmu_seq)) {
+ if (mmu_invalidate_retry(kvm, s2vi.mmu_seq)) {
ret = -EAGAIN;
goto out_unlock;
}
@@ -2013,16 +2010,17 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
* PTE, which will be preserved.
*/
prot &= ~KVM_NV_GUEST_MAP_SZ;
- ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, s2fd->fault_ipa,
+ ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, gfn_to_gpa(s2vi.gfn),
prot, flags);
} else {
- ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, s2fd->fault_ipa, PAGE_SIZE,
- __pfn_to_phys(pfn), prot,
+ ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, gfn_to_gpa(s2vi.gfn),
+ s2vi.vma_pagesize,
+ __pfn_to_phys(s2vi.pfn), prot,
memcache, flags);
}
out_unlock:
- kvm_release_faultin_page(kvm, page, !!ret, prot & KVM_PGTABLE_PROT_W);
+ kvm_release_faultin_page(kvm, s2vi.page, !!ret, prot & KVM_PGTABLE_PROT_W);
kvm_fault_unlock(kvm);
if ((prot & KVM_PGTABLE_PROT_W) && !ret)
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v1 6/6] KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort()
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
` (4 preceding siblings ...)
2026-09-22 13:08 ` [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort() Vincent Donnefort
@ 2026-09-22 13:08 ` Vincent Donnefort
2026-09-23 19:39 ` Fuad Tabba
5 siblings, 1 reply; 15+ messages in thread
From: Vincent Donnefort @ 2026-09-22 13:08 UTC (permalink / raw)
To: maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Vincent Donnefort, stable
To paraphrase Marc in commit 08f97454b7fa ("KVM: arm64: Fix protected
mode handling of pages larger than 4kB"), pkvm_pgtable_stage2_map()
assumes the address passed as a parameter is aligned to the size of the
intended mapping, while HPFAR_EL2 gives the IPA minus the bottom 12
bits, regardless of the system page size configuration.
To fix this alignment, bring support for kvm_s2_fault_vma_info in
pkvm_mem_abort() and use its members where possible. They do contain the
page-alignment we need to fix the fault on system with pages larger than
4K.
Also, add a check at the start of pkvm_pgtable_stage2_map(), it does not
support !PAGE_ALIGNED arguments.
Fixes: ea03466e806f ("KVM: arm64: Handle aborts from protected VMs")
Cc: stable@vger.kernel.org
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
---
arch/arm64/kvm/mmu.c | 24 +++++++++++++++---------
arch/arm64/kvm/pkvm.c | 3 +++
2 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index ee0fcca838c1..167b769107ee 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1374,9 +1374,11 @@ static bool fault_supports_stage2_huge_mapping(const struct kvm_s2_fault_desc *s
if (map_size == PAGE_SIZE)
return true;
- /* pKVM only supports PMD_SIZE huge-mappings */
- if (is_protected_kvm_enabled() && map_size != PMD_SIZE)
- return false;
+ /* pKVM only supports PMD_SIZE huge-mappings for non-protected VMs */
+ if (is_protected_kvm_enabled()) {
+ if (vcpu_is_protected(s2fd->vcpu) || map_size != PMD_SIZE)
+ return false;
+ }
size = memslot->npages * PAGE_SIZE;
@@ -2167,12 +2169,12 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
{
unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
+ struct kvm_s2_fault_vma_info s2vi = {};
struct kvm_vcpu *vcpu = s2fd->vcpu;
struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
struct mm_struct *mm = current->mm;
struct kvm *kvm = vcpu->kvm;
void *hyp_memcache;
- struct page *page;
int ret;
hyp_memcache = get_mmu_memcache(vcpu);
@@ -2180,12 +2182,16 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
if (ret)
return -ENOMEM;
+ ret = kvm_s2_fault_get_vma_info(s2fd, &s2vi);
+ if (ret)
+ return ret;
+
ret = account_locked_vm(mm, 1, true);
if (ret)
return ret;
mmap_read_lock(mm);
- ret = pin_user_pages(s2fd->hva, 1, flags, &page);
+ ret = pin_user_pages(s2fd->hva, 1, flags, &s2vi.page);
mmap_read_unlock(mm);
if (ret == -EHWPOISON) {
@@ -2195,7 +2201,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
} else if (ret != 1) {
ret = -EFAULT;
goto dec_account;
- } else if (!folio_test_swapbacked(page_folio(page))) {
+ } else if (!folio_test_swapbacked(page_folio(s2vi.page))) {
/*
* We really can't deal with page-cache pages returned by GUP
* because (a) we may trigger writeback of a page for which we
@@ -2215,8 +2221,8 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
}
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(s2vi.gfn), PAGE_SIZE,
+ page_to_phys(s2vi.page), KVM_PGTABLE_PROT_RWX,
hyp_memcache, 0);
write_unlock(&kvm->mmu_lock);
if (ret) {
@@ -2227,7 +2233,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
return 0;
unpin:
- unpin_user_pages(&page, 1);
+ unpin_user_page(s2vi.page);
dec_account:
account_locked_vm(mm, 1, false);
return ret;
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 8e4c6e4bec12..c298a5b9b12a 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -414,6 +414,9 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
u64 end = addr + size;
int ret;
+ if (WARN_ON_ONCE(!PAGE_ALIGNED(addr | size)))
+ return -EINVAL;
+
lockdep_assert_held_write(&kvm->mmu_lock);
mapping = pkvm_mapping_iter_first(&pgt->pkvm_mappings, addr, end - 1);
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping()
2026-09-22 13:08 ` [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping() Vincent Donnefort
@ 2026-09-23 19:33 ` Fuad Tabba
2026-09-25 17:46 ` Wei-Lin Chang
1 sibling, 0 replies; 15+ messages in thread
From: Fuad Tabba @ 2026-09-23 19:33 UTC (permalink / raw)
To: Vincent Donnefort
Cc: maz, oupton, kvmarm, linux-arm-kernel, joey.gouly, seiden,
suzuki.poulose, yuzenghui, catalin.marinas, will, kernel-team,
qperret
On Tue, 22 Sept 2026 at 14:08, 'Vincent Donnefort' via kernel-team
<kernel-team@android.com> wrote:
>
> Pass the fault descriptor to fault_supports_stage2_huge_mapping() instead
> of passing memslot and hva separately so it can access other s2fd
> members such as vcpu.
>
> No functional change intended.
>
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
Tested-by: Fuad Tabba < fuad.tabba@linux.dev>
Cheers,
/fuad
> ---
> arch/arm64/kvm/mmu.c | 39 +++++++++++++++++++--------------------
> 1 file changed, 19 insertions(+), 20 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 9ba86450fe4a..9dfaf4f277b5 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1353,12 +1353,21 @@ static void kvm_send_hwpoison_signal(unsigned long address, short lsb)
> send_sig_mceerr(BUS_MCEERR_AR, (void __user *)address, lsb, current);
> }
>
> -static bool fault_supports_stage2_huge_mapping(struct kvm_memory_slot *memslot,
> - unsigned long hva,
> +struct kvm_s2_fault_desc {
> + struct kvm_vcpu *vcpu;
> + phys_addr_t fault_ipa;
> + struct kvm_s2_trans *nested;
> + struct kvm_memory_slot *memslot;
> + unsigned long hva;
> +};
> +
> +static bool fault_supports_stage2_huge_mapping(const struct kvm_s2_fault_desc *s2fd,
> unsigned long map_size)
> {
> - gpa_t gpa_start;
> + struct kvm_memory_slot *memslot = s2fd->memslot;
> + unsigned long hva = s2fd->hva;
> hva_t uaddr_start, uaddr_end;
> + gpa_t gpa_start;
> size_t size;
>
> /* The memslot and the VMA are guaranteed to be aligned to PAGE_SIZE */
> @@ -1427,9 +1436,9 @@ static bool fault_supports_stage2_huge_mapping(struct kvm_memory_slot *memslot,
> * Returns the size of the mapping.
> */
> static long
> -transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot,
> - unsigned long hva, kvm_pfn_t *pfnp, gfn_t *gfnp)
> +transparent_hugepage_adjust(const struct kvm_s2_fault_desc *s2fd, kvm_pfn_t *pfnp, gfn_t *gfnp)
> {
> + struct kvm *kvm = s2fd->vcpu->kvm;
> kvm_pfn_t pfn = *pfnp;
> gfn_t gfn = *gfnp;
>
> @@ -1438,8 +1447,8 @@ transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot,
> * sure that the HVA and IPA are sufficiently aligned and that the
> * block map is contained within the memslot.
> */
> - if (fault_supports_stage2_huge_mapping(memslot, hva, PMD_SIZE)) {
> - int sz = get_user_mapping_size(kvm, hva);
> + if (fault_supports_stage2_huge_mapping(s2fd, PMD_SIZE)) {
> + int sz = get_user_mapping_size(kvm, s2fd->hva);
>
> if (sz < 0)
> return sz;
> @@ -1597,14 +1606,6 @@ static enum kvm_pgtable_prot adjust_nested_exec_perms(struct kvm *kvm,
> return prot;
> }
>
> -struct kvm_s2_fault_desc {
> - struct kvm_vcpu *vcpu;
> - phys_addr_t fault_ipa;
> - struct kvm_s2_trans *nested;
> - struct kvm_memory_slot *memslot;
> - unsigned long hva;
> -};
> -
> static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> {
> bool write_fault, exec_fault;
> @@ -1791,7 +1792,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
> switch (vma_shift) {
> #ifndef __PAGETABLE_PMD_FOLDED
> case PUD_SHIFT:
> - if (fault_supports_stage2_huge_mapping(s2fd->memslot, s2fd->hva, PUD_SIZE))
> + if (fault_supports_stage2_huge_mapping(s2fd, PUD_SIZE))
> break;
> fallthrough;
> #endif
> @@ -1799,7 +1800,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
> vma_shift = PMD_SHIFT;
> fallthrough;
> case PMD_SHIFT:
> - if (fault_supports_stage2_huge_mapping(s2fd->memslot, s2fd->hva, PMD_SIZE))
> + if (fault_supports_stage2_huge_mapping(s2fd, PMD_SIZE))
> break;
> fallthrough;
> case CONT_PTE_SHIFT:
> @@ -2049,9 +2050,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
> if (perm_fault_granule > PAGE_SIZE) {
> mapping_size = perm_fault_granule;
> } else {
> - mapping_size = transparent_hugepage_adjust(kvm, s2fd->memslot,
> - s2fd->hva, &pfn,
> - &gfn);
> + mapping_size = transparent_hugepage_adjust(s2fd, &pfn, &gfn);
> if (mapping_size < 0) {
> ret = mapping_size;
> goto out_unlock;
> --
> 2.55.0.1082.g2b9226bbc0-goog
>
> To unsubscribe from this group and stop receiving emails from it, send an email to kernel-team+unsubscribe@android.com.
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort()
2026-09-22 13:08 ` [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort() Vincent Donnefort
@ 2026-09-23 19:38 ` Fuad Tabba
2026-09-27 16:31 ` Marc Zyngier
1 sibling, 0 replies; 15+ messages in thread
From: Fuad Tabba @ 2026-09-23 19:38 UTC (permalink / raw)
To: Vincent Donnefort
Cc: maz, oupton, kvmarm, linux-arm-kernel, joey.gouly, seiden,
suzuki.poulose, yuzenghui, catalin.marinas, will, kernel-team,
qperret
On Tue, 22 Sept 2026 at 14:08, 'Vincent Donnefort' via kernel-team
<kernel-team@android.com> wrote:
>
> Move pkvm_mem_abort() below kvm_s2_fault_get_vma_info() so it can
> resolve stage-2 fault VMA metadata without forward declarations.
>
> No functional change intended.
>
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
Tested-by: Fuad Tabba < fuad.tabba@linux.dev>
Cheers,
/fuad
> ---
> arch/arm64/kvm/mmu.c | 138 +++++++++++++++++++++----------------------
> 1 file changed, 69 insertions(+), 69 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 9dfaf4f277b5..242f63066252 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1706,75 +1706,6 @@ struct kvm_s2_fault_vma_info {
> 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;
> - struct kvm_vcpu *vcpu = s2fd->vcpu;
> - struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
> - struct mm_struct *mm = current->mm;
> - struct kvm *kvm = vcpu->kvm;
> - void *hyp_memcache;
> - struct page *page;
> - int ret;
> -
> - hyp_memcache = get_mmu_memcache(vcpu);
> - ret = topup_mmu_memcache(vcpu, hyp_memcache);
> - 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;
> - } else if (ret != 1) {
> - ret = -EFAULT;
> - goto dec_account;
> - } else if (!folio_test_swapbacked(page_folio(page))) {
> - /*
> - * We really can't deal with page-cache pages returned by GUP
> - * because (a) we may trigger writeback of a page for which we
> - * no longer have access and (b) page_mkclean() won't find the
> - * stage-2 mapping in the rmap so we can get out-of-whack with
> - * the filesystem when marking the page dirty during unpinning
> - * (see cc5095747edf ("ext4: don't BUG if someone dirty pages
> - * without asking ext4 first")).
> - *
> - * Ideally we'd just restrict ourselves to anonymous pages, but
> - * we also want to allow memfd (i.e. shmem) pages, so check for
> - * pages backed by swap in the knowledge that the GUP pin will
> - * prevent try_to_unmap() from succeeding.
> - */
> - ret = -EIO;
> - 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,
> - hyp_memcache, 0);
> - write_unlock(&kvm->mmu_lock);
> - if (ret) {
> - if (ret == -EAGAIN)
> - ret = 0;
> - goto unpin;
> - }
> -
> - return 0;
> -unpin:
> - unpin_user_pages(&page, 1);
> -dec_account:
> - account_locked_vm(mm, 1, false);
> - return ret;
> -}
> -
> 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)
> @@ -2235,6 +2166,75 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
> return 0;
> }
>
> +static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
> +{
> + unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
> + struct kvm_vcpu *vcpu = s2fd->vcpu;
> + struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
> + struct mm_struct *mm = current->mm;
> + struct kvm *kvm = vcpu->kvm;
> + void *hyp_memcache;
> + struct page *page;
> + int ret;
> +
> + hyp_memcache = get_mmu_memcache(vcpu);
> + ret = topup_mmu_memcache(vcpu, hyp_memcache);
> + 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;
> + } else if (ret != 1) {
> + ret = -EFAULT;
> + goto dec_account;
> + } else if (!folio_test_swapbacked(page_folio(page))) {
> + /*
> + * We really can't deal with page-cache pages returned by GUP
> + * because (a) we may trigger writeback of a page for which we
> + * no longer have access and (b) page_mkclean() won't find the
> + * stage-2 mapping in the rmap so we can get out-of-whack with
> + * the filesystem when marking the page dirty during unpinning
> + * (see cc5095747edf ("ext4: don't BUG if someone dirty pages
> + * without asking ext4 first")).
> + *
> + * Ideally we'd just restrict ourselves to anonymous pages, but
> + * we also want to allow memfd (i.e. shmem) pages, so check for
> + * pages backed by swap in the knowledge that the GUP pin will
> + * prevent try_to_unmap() from succeeding.
> + */
> + ret = -EIO;
> + 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,
> + hyp_memcache, 0);
> + write_unlock(&kvm->mmu_lock);
> + if (ret) {
> + if (ret == -EAGAIN)
> + ret = 0;
> + goto unpin;
> + }
> +
> + return 0;
> +unpin:
> + unpin_user_pages(&page, 1);
> +dec_account:
> + account_locked_vm(mm, 1, false);
> + return ret;
> +}
> +
> /**
> * kvm_handle_guest_abort - handles all 2nd stage aborts
> * @vcpu: the VCPU pointer
> --
> 2.55.0.1082.g2b9226bbc0-goog
>
> To unsubscribe from this group and stop receiving emails from it, send an email to kernel-team+unsubscribe@android.com.
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 6/6] KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort()
2026-09-22 13:08 ` [PATCH v1 6/6] KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort() Vincent Donnefort
@ 2026-09-23 19:39 ` Fuad Tabba
0 siblings, 0 replies; 15+ messages in thread
From: Fuad Tabba @ 2026-09-23 19:39 UTC (permalink / raw)
To: Vincent Donnefort
Cc: maz, oupton, kvmarm, linux-arm-kernel, joey.gouly, seiden,
suzuki.poulose, yuzenghui, catalin.marinas, will, kernel-team,
qperret, stable
On Tue, 22 Sept 2026 at 14:08, 'Vincent Donnefort' via kernel-team
<kernel-team@android.com> wrote:
>
> To paraphrase Marc in commit 08f97454b7fa ("KVM: arm64: Fix protected
> mode handling of pages larger than 4kB"), pkvm_pgtable_stage2_map()
> assumes the address passed as a parameter is aligned to the size of the
> intended mapping, while HPFAR_EL2 gives the IPA minus the bottom 12
> bits, regardless of the system page size configuration.
>
> To fix this alignment, bring support for kvm_s2_fault_vma_info in
> pkvm_mem_abort() and use its members where possible. They do contain the
> page-alignment we need to fix the fault on system with pages larger than
> 4K.
>
> Also, add a check at the start of pkvm_pgtable_stage2_map(), it does not
> support !PAGE_ALIGNED arguments.
>
> Fixes: ea03466e806f ("KVM: arm64: Handle aborts from protected VMs")
> Cc: stable@vger.kernel.org
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev>
Tested-by: Fuad Tabba < fuad.tabba@linux.dev>
Cheers,
/fuad
> ---
> arch/arm64/kvm/mmu.c | 24 +++++++++++++++---------
> arch/arm64/kvm/pkvm.c | 3 +++
> 2 files changed, 18 insertions(+), 9 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index ee0fcca838c1..167b769107ee 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1374,9 +1374,11 @@ static bool fault_supports_stage2_huge_mapping(const struct kvm_s2_fault_desc *s
> if (map_size == PAGE_SIZE)
> return true;
>
> - /* pKVM only supports PMD_SIZE huge-mappings */
> - if (is_protected_kvm_enabled() && map_size != PMD_SIZE)
> - return false;
> + /* pKVM only supports PMD_SIZE huge-mappings for non-protected VMs */
> + if (is_protected_kvm_enabled()) {
> + if (vcpu_is_protected(s2fd->vcpu) || map_size != PMD_SIZE)
> + return false;
> + }
>
> size = memslot->npages * PAGE_SIZE;
>
> @@ -2167,12 +2169,12 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
> static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
> {
> unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
> + struct kvm_s2_fault_vma_info s2vi = {};
> struct kvm_vcpu *vcpu = s2fd->vcpu;
> struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
> struct mm_struct *mm = current->mm;
> struct kvm *kvm = vcpu->kvm;
> void *hyp_memcache;
> - struct page *page;
> int ret;
>
> hyp_memcache = get_mmu_memcache(vcpu);
> @@ -2180,12 +2182,16 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
> if (ret)
> return -ENOMEM;
>
> + ret = kvm_s2_fault_get_vma_info(s2fd, &s2vi);
> + if (ret)
> + return ret;
> +
> ret = account_locked_vm(mm, 1, true);
> if (ret)
> return ret;
>
> mmap_read_lock(mm);
> - ret = pin_user_pages(s2fd->hva, 1, flags, &page);
> + ret = pin_user_pages(s2fd->hva, 1, flags, &s2vi.page);
> mmap_read_unlock(mm);
>
> if (ret == -EHWPOISON) {
> @@ -2195,7 +2201,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
> } else if (ret != 1) {
> ret = -EFAULT;
> goto dec_account;
> - } else if (!folio_test_swapbacked(page_folio(page))) {
> + } else if (!folio_test_swapbacked(page_folio(s2vi.page))) {
> /*
> * We really can't deal with page-cache pages returned by GUP
> * because (a) we may trigger writeback of a page for which we
> @@ -2215,8 +2221,8 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
> }
>
> 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(s2vi.gfn), PAGE_SIZE,
> + page_to_phys(s2vi.page), KVM_PGTABLE_PROT_RWX,
> hyp_memcache, 0);
> write_unlock(&kvm->mmu_lock);
> if (ret) {
> @@ -2227,7 +2233,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
>
> return 0;
> unpin:
> - unpin_user_pages(&page, 1);
> + unpin_user_page(s2vi.page);
> dec_account:
> account_locked_vm(mm, 1, false);
> return ret;
> diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
> index 8e4c6e4bec12..c298a5b9b12a 100644
> --- a/arch/arm64/kvm/pkvm.c
> +++ b/arch/arm64/kvm/pkvm.c
> @@ -414,6 +414,9 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
> u64 end = addr + size;
> int ret;
>
> + if (WARN_ON_ONCE(!PAGE_ALIGNED(addr | size)))
> + return -EINVAL;
> +
> lockdep_assert_held_write(&kvm->mmu_lock);
> mapping = pkvm_mapping_iter_first(&pgt->pkvm_mappings, addr, end - 1);
>
> --
> 2.55.0.1082.g2b9226bbc0-goog
>
> To unsubscribe from this group and stop receiving emails from it, send an email to kernel-team+unsubscribe@android.com.
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort()
2026-09-22 13:08 ` [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort() Vincent Donnefort
@ 2026-09-25 17:36 ` Wei-Lin Chang
2026-09-27 16:30 ` Marc Zyngier
0 siblings, 1 reply; 15+ messages in thread
From: Wei-Lin Chang @ 2026-09-25 17:36 UTC (permalink / raw)
To: Vincent Donnefort, maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret, Sashiko, stable
On Tue, Sep 22, 2026 at 02:08:20PM +0100, Vincent Donnefort wrote:
> From: Fuad Tabba <fuad.tabba@linux.dev>
>
> gmem_abort() maps at s2fd->fault_ipa, which HPFAR_EL2 holds at 4K
> granularity whatever the page size. The generic page-table code aligns
> it, but pkvm_pgtable_stage2_map() looks up existing mappings over
> [addr, addr + size), so on a pKVM host with pages larger than 4K a
> guest_memfd-backed guest that faults past the first 4K of a page can
> find its neighbour's mapping, get -EAGAIN and take the same fault
> forever.
>
> Take the addresses from kvm_s2_fault_vma_info instead, as
> user_mem_abort() does, and report the gfn kvm_gmem_get_pfn() failed on
> in the memory fault exit. kvm_s2_fault_get_vma_info() itself isn't
> called: a guest_memfd memslot's userspace_addr doesn't need to be
> backed by a VMA.
>
> Fixes: a7b57e0995927 ("KVM: arm64: Handle guest_memfd-backed guest page faults")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Closes: https://lore.kernel.org/r/20260913175105.A57AC1F000FF@smtp.kernel.org
> Cc: stable@vger.kernel.org
> Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
> ---
> arch/arm64/kvm/mmu.c | 28 +++++++++++++---------------
> 1 file changed, 13 insertions(+), 15 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 5aac624b0442..ee0fcca838c1 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1953,11 +1953,9 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
> enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
> struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
> - unsigned long mmu_seq;
> - struct page *page;
> + struct kvm_s2_fault_vma_info s2vi = {};
I'd say it's unfortunate this struct's name contains vma :P
> struct kvm *kvm = s2fd->vcpu->kvm;
> void *memcache = NULL;
> - kvm_pfn_t pfn;
> gfn_t gfn;
> int ret;
>
> @@ -1968,23 +1966,22 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> return ret;
> }
>
> - if (s2fd->nested)
> - gfn = kvm_s2_trans_output(s2fd->nested) >> PAGE_SHIFT;
> - else
> - gfn = s2fd->fault_ipa >> PAGE_SHIFT;
> + s2vi.vma_pagesize = PAGE_SIZE;
> + s2vi.gfn = ALIGN_DOWN(s2fd->fault_ipa, s2vi.vma_pagesize) >> PAGE_SHIFT;
> + gfn = get_canonical_gfn(s2fd, &s2vi);
Perhaps it is worth reserving plain "gfn" for the value we use as the
IA of the (shadow) page table, and name this variable something like
canonical_gfn. Similar to what kvm_s2_fault_map() does.
>
> write_fault = kvm_is_write_fault(s2fd->vcpu);
> exec_fault = kvm_vcpu_trap_is_exec_fault(s2fd->vcpu);
>
> VM_WARN_ON_ONCE(write_fault && exec_fault);
>
> - mmu_seq = kvm->mmu_invalidate_seq;
> + s2vi.mmu_seq = kvm->mmu_invalidate_seq;
> /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
> smp_rmb();
>
> - ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &pfn, &page, NULL);
> + ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &s2vi.pfn, &s2vi.page, NULL);
> if (ret) {
> - kvm_prepare_memory_fault_exit(s2fd->vcpu, s2fd->fault_ipa, PAGE_SIZE,
> + kvm_prepare_memory_fault_exit(s2fd->vcpu, gfn_to_gpa(gfn), s2vi.vma_pagesize,
Huh, I guess you also fixed a bug here? s2fd->fault_ipa could be the
nested ipa instead of the canonical ipa.
Thanks,
Wei-Lin Chang
> write_fault, exec_fault, false);
> return ret;
> }
> @@ -2002,7 +1999,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> prot = adjust_nested_exec_perms(kvm, s2fd->nested, prot);
>
> kvm_fault_lock(kvm);
> - if (mmu_invalidate_retry(kvm, mmu_seq)) {
> + if (mmu_invalidate_retry(kvm, s2vi.mmu_seq)) {
> ret = -EAGAIN;
> goto out_unlock;
> }
> @@ -2013,16 +2010,17 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> * PTE, which will be preserved.
> */
> prot &= ~KVM_NV_GUEST_MAP_SZ;
> - ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, s2fd->fault_ipa,
> + ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, gfn_to_gpa(s2vi.gfn),
> prot, flags);
> } else {
> - ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, s2fd->fault_ipa, PAGE_SIZE,
> - __pfn_to_phys(pfn), prot,
> + ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, gfn_to_gpa(s2vi.gfn),
> + s2vi.vma_pagesize,
> + __pfn_to_phys(s2vi.pfn), prot,
> memcache, flags);
> }
>
> out_unlock:
> - kvm_release_faultin_page(kvm, page, !!ret, prot & KVM_PGTABLE_PROT_W);
> + kvm_release_faultin_page(kvm, s2vi.page, !!ret, prot & KVM_PGTABLE_PROT_W);
> kvm_fault_unlock(kvm);
>
> if ((prot & KVM_PGTABLE_PROT_W) && !ret)
> --
> 2.55.0.1082.g2b9226bbc0-goog
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping()
2026-09-22 13:08 ` [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping() Vincent Donnefort
2026-09-23 19:33 ` Fuad Tabba
@ 2026-09-25 17:46 ` Wei-Lin Chang
1 sibling, 0 replies; 15+ messages in thread
From: Wei-Lin Chang @ 2026-09-25 17:46 UTC (permalink / raw)
To: Vincent Donnefort, maz, oupton, kvmarm, linux-arm-kernel
Cc: joey.gouly, seiden, suzuki.poulose, yuzenghui, catalin.marinas,
will, kernel-team, fuad.tabba, qperret
On Tue, Sep 22, 2026 at 02:08:17PM +0100, Vincent Donnefort wrote:
> Pass the fault descriptor to fault_supports_stage2_huge_mapping() instead
> of passing memslot and hva separately so it can access other s2fd
> members such as vcpu.
>
> No functional change intended.
>
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Nit: Maybe also mention the transparent_hugepage_adjust() change in the
commit message?
Either way:
Reviewed-by: Wei-Lin Chang <weilin.chang@arm.com>
> ---
> arch/arm64/kvm/mmu.c | 39 +++++++++++++++++++--------------------
> 1 file changed, 19 insertions(+), 20 deletions(-)
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 9ba86450fe4a..9dfaf4f277b5 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1353,12 +1353,21 @@ static void kvm_send_hwpoison_signal(unsigned long address, short lsb)
> send_sig_mceerr(BUS_MCEERR_AR, (void __user *)address, lsb, current);
> }
>
> -static bool fault_supports_stage2_huge_mapping(struct kvm_memory_slot *memslot,
> - unsigned long hva,
> +struct kvm_s2_fault_desc {
> + struct kvm_vcpu *vcpu;
> + phys_addr_t fault_ipa;
> + struct kvm_s2_trans *nested;
> + struct kvm_memory_slot *memslot;
> + unsigned long hva;
> +};
> +
> +static bool fault_supports_stage2_huge_mapping(const struct kvm_s2_fault_desc *s2fd,
> unsigned long map_size)
> {
> - gpa_t gpa_start;
> + struct kvm_memory_slot *memslot = s2fd->memslot;
> + unsigned long hva = s2fd->hva;
> hva_t uaddr_start, uaddr_end;
> + gpa_t gpa_start;
> size_t size;
>
> /* The memslot and the VMA are guaranteed to be aligned to PAGE_SIZE */
> @@ -1427,9 +1436,9 @@ static bool fault_supports_stage2_huge_mapping(struct kvm_memory_slot *memslot,
> * Returns the size of the mapping.
> */
> static long
> -transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot,
> - unsigned long hva, kvm_pfn_t *pfnp, gfn_t *gfnp)
> +transparent_hugepage_adjust(const struct kvm_s2_fault_desc *s2fd, kvm_pfn_t *pfnp, gfn_t *gfnp)
> {
> + struct kvm *kvm = s2fd->vcpu->kvm;
> kvm_pfn_t pfn = *pfnp;
> gfn_t gfn = *gfnp;
>
> @@ -1438,8 +1447,8 @@ transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot,
> * sure that the HVA and IPA are sufficiently aligned and that the
> * block map is contained within the memslot.
> */
> - if (fault_supports_stage2_huge_mapping(memslot, hva, PMD_SIZE)) {
> - int sz = get_user_mapping_size(kvm, hva);
> + if (fault_supports_stage2_huge_mapping(s2fd, PMD_SIZE)) {
> + int sz = get_user_mapping_size(kvm, s2fd->hva);
>
> if (sz < 0)
> return sz;
> @@ -1597,14 +1606,6 @@ static enum kvm_pgtable_prot adjust_nested_exec_perms(struct kvm *kvm,
> return prot;
> }
>
> -struct kvm_s2_fault_desc {
> - struct kvm_vcpu *vcpu;
> - phys_addr_t fault_ipa;
> - struct kvm_s2_trans *nested;
> - struct kvm_memory_slot *memslot;
> - unsigned long hva;
> -};
> -
> static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> {
> bool write_fault, exec_fault;
> @@ -1791,7 +1792,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
> switch (vma_shift) {
> #ifndef __PAGETABLE_PMD_FOLDED
> case PUD_SHIFT:
> - if (fault_supports_stage2_huge_mapping(s2fd->memslot, s2fd->hva, PUD_SIZE))
> + if (fault_supports_stage2_huge_mapping(s2fd, PUD_SIZE))
> break;
> fallthrough;
> #endif
> @@ -1799,7 +1800,7 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
> vma_shift = PMD_SHIFT;
> fallthrough;
> case PMD_SHIFT:
> - if (fault_supports_stage2_huge_mapping(s2fd->memslot, s2fd->hva, PMD_SIZE))
> + if (fault_supports_stage2_huge_mapping(s2fd, PMD_SIZE))
> break;
> fallthrough;
> case CONT_PTE_SHIFT:
> @@ -2049,9 +2050,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
> if (perm_fault_granule > PAGE_SIZE) {
> mapping_size = perm_fault_granule;
> } else {
> - mapping_size = transparent_hugepage_adjust(kvm, s2fd->memslot,
> - s2fd->hva, &pfn,
> - &gfn);
> + mapping_size = transparent_hugepage_adjust(s2fd, &pfn, &gfn);
> if (mapping_size < 0) {
> ret = mapping_size;
> goto out_unlock;
> --
> 2.55.0.1082.g2b9226bbc0-goog
>
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort()
2026-09-25 17:36 ` Wei-Lin Chang
@ 2026-09-27 16:30 ` Marc Zyngier
2026-09-28 7:44 ` Fuad Tabba
0 siblings, 1 reply; 15+ messages in thread
From: Marc Zyngier @ 2026-09-27 16:30 UTC (permalink / raw)
To: Wei-Lin Chang
Cc: Vincent Donnefort, oupton, kvmarm, linux-arm-kernel, joey.gouly,
seiden, suzuki.poulose, yuzenghui, catalin.marinas, will,
kernel-team, fuad.tabba, qperret, Sashiko, stable
On Fri, 25 Sep 2026 18:36:42 +0100,
Wei-Lin Chang <weilin.chang@arm.com> wrote:
>
> On Tue, Sep 22, 2026 at 02:08:20PM +0100, Vincent Donnefort wrote:
> > From: Fuad Tabba <fuad.tabba@linux.dev>
> >
> > gmem_abort() maps at s2fd->fault_ipa, which HPFAR_EL2 holds at 4K
> > granularity whatever the page size. The generic page-table code aligns
> > it, but pkvm_pgtable_stage2_map() looks up existing mappings over
> > [addr, addr + size), so on a pKVM host with pages larger than 4K a
> > guest_memfd-backed guest that faults past the first 4K of a page can
> > find its neighbour's mapping, get -EAGAIN and take the same fault
> > forever.
> >
> > Take the addresses from kvm_s2_fault_vma_info instead, as
> > user_mem_abort() does, and report the gfn kvm_gmem_get_pfn() failed on
> > in the memory fault exit. kvm_s2_fault_get_vma_info() itself isn't
> > called: a guest_memfd memslot's userspace_addr doesn't need to be
> > backed by a VMA.
> >
> > Fixes: a7b57e0995927 ("KVM: arm64: Handle guest_memfd-backed guest page faults")
> > Reported-by: Sashiko <sashiko-bot@kernel.org>
> > Closes: https://lore.kernel.org/r/20260913175105.A57AC1F000FF@smtp.kernel.org
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
> > Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
> > ---
> > arch/arm64/kvm/mmu.c | 28 +++++++++++++---------------
> > 1 file changed, 13 insertions(+), 15 deletions(-)
> >
> > diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> > index 5aac624b0442..ee0fcca838c1 100644
> > --- a/arch/arm64/kvm/mmu.c
> > +++ b/arch/arm64/kvm/mmu.c
> > @@ -1953,11 +1953,9 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> > enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
> > enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
> > struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
> > - unsigned long mmu_seq;
> > - struct page *page;
> > + struct kvm_s2_fault_vma_info s2vi = {};
>
> I'd say it's unfortunate this struct's name contains vma :P
If that was the only thing that is wrong in the MMU code, we'd be in a
pretty good position! ;-)
>
> > struct kvm *kvm = s2fd->vcpu->kvm;
> > void *memcache = NULL;
> > - kvm_pfn_t pfn;
> > gfn_t gfn;
> > int ret;
> >
> > @@ -1968,23 +1966,22 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
> > return ret;
> > }
> >
> > - if (s2fd->nested)
> > - gfn = kvm_s2_trans_output(s2fd->nested) >> PAGE_SHIFT;
> > - else
> > - gfn = s2fd->fault_ipa >> PAGE_SHIFT;
> > + s2vi.vma_pagesize = PAGE_SIZE;
> > + s2vi.gfn = ALIGN_DOWN(s2fd->fault_ipa, s2vi.vma_pagesize) >> PAGE_SHIFT;
> > + gfn = get_canonical_gfn(s2fd, &s2vi);
>
> Perhaps it is worth reserving plain "gfn" for the value we use as the
> IA of the (shadow) page table, and name this variable something like
> canonical_gfn. Similar to what kvm_s2_fault_map() does.
Maybe. We have gfn in the vncr fault handling code as well, but that
one is the result of a S1 walk, so maybe more obvious.
>
> >
> > write_fault = kvm_is_write_fault(s2fd->vcpu);
> > exec_fault = kvm_vcpu_trap_is_exec_fault(s2fd->vcpu);
> >
> > VM_WARN_ON_ONCE(write_fault && exec_fault);
> >
> > - mmu_seq = kvm->mmu_invalidate_seq;
> > + s2vi.mmu_seq = kvm->mmu_invalidate_seq;
> > /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
> > smp_rmb();
> >
> > - ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &pfn, &page, NULL);
> > + ret = kvm_gmem_get_pfn(kvm, s2fd->memslot, gfn, &s2vi.pfn, &s2vi.page, NULL);
> > if (ret) {
> > - kvm_prepare_memory_fault_exit(s2fd->vcpu, s2fd->fault_ipa, PAGE_SIZE,
> > + kvm_prepare_memory_fault_exit(s2fd->vcpu, gfn_to_gpa(gfn), s2vi.vma_pagesize,
>
> Huh, I guess you also fixed a bug here? s2fd->fault_ipa could be the
> nested ipa instead of the canonical ipa.
Yeah, it appears so. Worth capturing in Fixes: as well.
M.
--
Jazz isn't dead. It just smells funny.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort()
2026-09-22 13:08 ` [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort() Vincent Donnefort
2026-09-23 19:38 ` Fuad Tabba
@ 2026-09-27 16:31 ` Marc Zyngier
1 sibling, 0 replies; 15+ messages in thread
From: Marc Zyngier @ 2026-09-27 16:31 UTC (permalink / raw)
To: Vincent Donnefort
Cc: oupton, kvmarm, linux-arm-kernel, joey.gouly, seiden,
suzuki.poulose, yuzenghui, catalin.marinas, will, kernel-team,
fuad.tabba, qperret
On Tue, 22 Sep 2026 14:08:18 +0100,
Vincent Donnefort <vdonnefort@google.com> wrote:
>
> Move pkvm_mem_abort() below kvm_s2_fault_get_vma_info() so it can
> resolve stage-2 fault VMA metadata without forward declarations.
>
> No functional change intended.
>
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Honestly, I vastly prefer forward declaration to code movement. It
makes backporting easier, and results in fewer patches. Same for the
following patch.
Thanks,
M.
--
Jazz isn't dead. It just smells funny.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort()
2026-09-27 16:30 ` Marc Zyngier
@ 2026-09-28 7:44 ` Fuad Tabba
0 siblings, 0 replies; 15+ messages in thread
From: Fuad Tabba @ 2026-09-28 7:44 UTC (permalink / raw)
To: Marc Zyngier
Cc: Wei-Lin Chang, Vincent Donnefort, oupton, kvmarm,
linux-arm-kernel, joey.gouly, seiden, suzuki.poulose, yuzenghui,
catalin.marinas, will, kernel-team, qperret, Sashiko, stable
Hi Marc, Wei-Lin,
On Sun, 27 Sep 2026 17:30:04 +0100, Marc Zyngier <maz@kernel.org> wrote:
[...]
> > Perhaps it is worth reserving plain "gfn" for the value we use as the
> > IA of the (shadow) page table, and name this variable something like
> > canonical_gfn. Similar to what kvm_s2_fault_map() does.
>
> Maybe. We have gfn in the vncr fault handling code as well, but that
> one is the result of a S1 walk, so maybe more obvious.
Agreed, now that s2vi.gfn sits next to it. I'll rename it to
canonical_gfn for v2.
> > Huh, I guess you also fixed a bug here? s2fd->fault_ipa could be the
> > nested ipa instead of the canonical ipa.
>
> Yeah, it appears so. Worth capturing in Fixes: as well.
That one came in with a7b57e0995927 too, so the existing Fixes: covers
it. I'll mention it in the commit message.
Cheers,
/fuad
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-09-28 9:39 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-22 13:08 [PATCH v1 0/6] Fix guest_memfd and protected VMs on systems with pages larger than 4K Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 1/6] KVM: arm64: Fix MMFR0 TGRAN advertisement for pVMs Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 2/6] KVM: arm64: Pass kvm_s2_fault_desc to fault_supports_stage2_huge_mapping() Vincent Donnefort
2026-09-23 19:33 ` Fuad Tabba
2026-09-25 17:46 ` Wei-Lin Chang
2026-09-22 13:08 ` [PATCH v1 3/6] KVM: arm64: Move pkvm_mem_abort() Vincent Donnefort
2026-09-23 19:38 ` Fuad Tabba
2026-09-27 16:31 ` Marc Zyngier
2026-09-22 13:08 ` [PATCH v1 4/6] KVM: arm64: Move gmem_abort() Vincent Donnefort
2026-09-22 13:08 ` [PATCH v1 5/6] KVM: arm64: Use kvm_s2_fault_vma_info in gmem_abort() Vincent Donnefort
2026-09-25 17:36 ` Wei-Lin Chang
2026-09-27 16:30 ` Marc Zyngier
2026-09-28 7:44 ` Fuad Tabba
2026-09-22 13:08 ` [PATCH v1 6/6] KVM: arm64: Use kvm_s2_fault_vma_info in pkvm_mem_abort() Vincent Donnefort
2026-09-23 19:39 ` Fuad Tabba
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox