[PATCH 2/8] KVM: arm64: Propagate and use mmu in s2fd when handling guest aborts

From: Lorenzo Stoakes (ARM)

Date: Tue Aug 25 2026 - 12:06:32 EST


kvm_handle_guest_abort() establishes a kvm_s2_fault_desc data structure,
s2fd, to store and propagate state to either pkvm_mem_abort(), gmem_abort()
or user_mem_abort() handlers.

Each of these, however, examines the state of the stage 2 MMU via
vcpu->arch.hw_mmu.

Introduce an s2fd->mmu field to abstract this and propagate it to callers.

Similar to adding the esr field, this allows injection of synthetic faults
with the ultimate intention of implementing stage 2 page table
pre-faulting.

No functional change intended.

Signed-off-by: Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx>
---
arch/arm64/kvm/mmu.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 30d605e87b01..80cb520e25b9 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1604,6 +1604,7 @@ struct kvm_s2_fault_desc {
struct kvm_memory_slot *memslot;
unsigned long hva;
unsigned long esr;
+ struct kvm_s2_mmu *mmu;
};

static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
@@ -1637,7 +1638,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
const bool perm_fault = kvm_s2_fault_is_perm(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;
+ struct kvm_pgtable *pgt = s2fd->mmu->pgt;
unsigned long mmu_seq;
struct page *page;
struct kvm *kvm = s2fd->vcpu->kvm;
@@ -1735,7 +1736,7 @@ 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 kvm_pgtable *pgt = s2fd->mmu->pgt;
struct mm_struct *mm = current->mm;
struct kvm *kvm = vcpu->kvm;
void *hyp_memcache;
@@ -2050,7 +2051,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
int ret;

kvm_fault_lock(kvm);
- pgt = s2fd->vcpu->arch.hw_mmu->pgt;
+ pgt = s2fd->mmu->pgt;
ret = -EAGAIN;
if (mmu_invalidate_retry(kvm, s2vi->mmu_seq))
goto out_unlock;
@@ -2271,6 +2272,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
{
struct kvm_s2_trans nested_trans, *nested = NULL;
const unsigned long esr = kvm_vcpu_get_esr(vcpu);
+ struct kvm_s2_mmu *mmu = vcpu->arch.hw_mmu;
phys_addr_t fault_ipa; /* The address we faulted on */
phys_addr_t ipa; /* Always the IPA in the L1 guest phys space */
struct kvm_memory_slot *memslot;
@@ -2300,7 +2302,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
}

/* Falls between the IPA range and the PARange? */
- if (fault_ipa >= BIT_ULL(VTCR_EL2_IPA(vcpu->arch.hw_mmu->vtcr))) {
+ if (fault_ipa >= BIT_ULL(VTCR_EL2_IPA(mmu->vtcr))) {
fault_ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(vcpu));

return kvm_inject_sea(vcpu, is_iabt, fault_ipa);
@@ -2337,8 +2339,8 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
* nothing to walk and we treat it as a 1:1 before going through the
* canonical translation.
*/
- if (kvm_is_nested_s2_mmu(vcpu->kvm,vcpu->arch.hw_mmu) &&
- vcpu->arch.hw_mmu->nested_stage2_enabled) {
+ if (kvm_is_nested_s2_mmu(vcpu->kvm, mmu) &&
+ mmu->nested_stage2_enabled) {
u32 esr;

ret = kvm_walk_nested_s2(vcpu, fault_ipa, &nested_trans);
@@ -2413,7 +2415,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
}

/* Userspace should not be able to register out-of-bounds IPAs */
- VM_BUG_ON(ipa >= kvm_phys_size(vcpu->arch.hw_mmu));
+ VM_BUG_ON(ipa >= kvm_phys_size(mmu));

if (esr_fsc_is_access_flag_fault(esr)) {
handle_access_fault(vcpu, fault_ipa);
@@ -2428,6 +2430,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
.memslot = memslot,
.hva = hva,
.esr = esr,
+ .mmu = mmu,
};

if (kvm_vm_is_protected(vcpu->kvm)) {

--
2.55.0