From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
To: Catalin Marinas <catalin.marinas@arm.com>,
Will Deacon <will@kernel.org>, Marc Zyngier <maz@kernel.org>,
Oliver Upton <oupton@kernel.org>, Fuad Tabba <tabba@google.com>,
Joey Gouly <joey.gouly@arm.com>,
Steffen Eiden <seiden@linux.ibm.com>,
Suzuki K Poulose <suzuki.poulose@arm.com>,
Zenghui Yu <yuzenghui@huawei.com>,
Paolo Bonzini <pbonzini@redhat.com>,
Jonathan Corbet <corbet@lwn.net>
Cc: linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kselftest@vger.kernel.org,
Jack Thomson <jackabt@amazon.com>,
Jack Thomson <jackabt.amazon@gmail.com>,
Alexandru Elisei <alexandru.elisei@arm.com>,
Vincent Donnefort <vdonnefort@google.com>,
"Aneesh Kumar K.V" <aneesh.kumar@kernel.org>,
Sean Christopherson <seanjc@google.com>,
Claudio Imbrenda <imbrenda@linux.ibm.com>,
Leo Soares Passos <Leo.Bras@arm.com>,
"Lorenzo Stoakes (ARM)" <ljs@kernel.org>
Subject: [PATCH v2 09/13] KVM: arm64: Implement KVM_PRE_FAULT_MEMORY
Date: Mon, 14 Sep 2026 13:26:20 +0100 [thread overview]
Message-ID: <20260914-kvm-arm-prefault-v2-9-26fb47f74b73@kernel.org> (raw)
In-Reply-To: <20260914-kvm-arm-prefault-v2-0-26fb47f74b73@kernel.org>
Implement KVM stage 2 page table pre-faulting for the arm64 architecture,
with the core of the implementation in kvm_arch_vcpu_pre_fault_memory().
Pre-fault by first trying a page table walk under the MMU read lock then,
if it fails, injecting a synthetic data abort at the page table level
at which the page table walk failed.
This is necessarily racey as reclaim might happen at any time. Successfully
pre-faulting can therefore only guarantee that each GPA was observed to be
mapped at least once.
Protected KVM (pKVM) is not supported at all because pKVM creates VMs and
vCPUs when first run, meaning any attempt to pre-fault prior to this cannot
succeed.
A retry mechanic is implemented when user_mem_abort() or gmem_abort() fail
to map memory due to a benign failure where a hardware abort would not
result in an error.
An invalid memslot (i.e. a memslot with the KVM_MEMSLOT_INVALID flag set)
results in the operation returning -EAGAIN without a retry mechanic,
because an invalid memslot means the pre-fault operation raced with memslot
reclaim, and since the SRCU lock is held, progress cannot be made, and the
user must retry.
This work is based with gratitude on Jack Thomson's original series, its
previous revisions and the feedback they received.
Link: https://patch.msgid.link/20260612162354.73378-1-jackabt.amazon@gmail.com/
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/include/asm/kvm_pkvm.h | 2 +-
arch/arm64/kvm/Kconfig | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/mmu.c | 141 ++++++++++++++++++++++++++++++++++++++
4 files changed, 144 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index beea00e693a0..4d6e5765e9e5 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -44,9 +44,9 @@ static inline bool kvm_pkvm_ext_allowed(struct kvm *kvm, long ext)
case KVM_CAP_ARM_PTRAUTH_GENERIC:
return true;
case KVM_CAP_ARM_MTE:
- return false;
case KVM_CAP_ARM_EAGER_SPLIT_CHUNK_SIZE:
case KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES:
+ case KVM_CAP_PRE_FAULT_MEMORY:
return false;
default:
return !kvm || !kvm_vm_is_protected(kvm);
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449154f9a485..71233068b7cb 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -37,6 +37,7 @@ menuconfig KVM
select SCHED_INFO
select GUEST_PERF_EVENTS if PERF_EVENTS
select KVM_GUEST_MEMFD
+ select KVM_GENERIC_PRE_FAULT_MEMORY
help
Support hosting virtualized guest machines.
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index ccae82c1242b..58ac70f31d84 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -393,6 +393,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_COUNTER_OFFSET:
case KVM_CAP_ARM_WRITABLE_IMP_ID_REGS:
case KVM_CAP_ARM_SEA_TO_USER:
+ case KVM_CAP_PRE_FAULT_MEMORY:
r = 1;
break;
case KVM_CAP_SET_GUEST_DEBUG2:
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9c922b498adb..17f1f6d36f2b 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -5,6 +5,7 @@
*/
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/mman.h>
#include <linux/kvm_host.h>
#include <linux/io.h>
@@ -2835,3 +2836,143 @@ void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled)
trace_kvm_toggle_cache(*vcpu_pc(vcpu), was_enabled, now_enabled);
}
+
+/*
+ * Try to walk to the specified GPA in canonical mmu - if unmapped returns 0, if
+ * mapped returns the granule size, otherwise returns an error.
+ */
+static long kvm_walk_s2(struct kvm_pgtable *pgt,
+ gpa_t gpa, s8 *level)
+{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
+ kvm_pte_t pte;
+ long ret;
+
+ guard(read_lock)(&kvm->mmu_lock);
+
+ ret = kvm_pgtable_get_leaf(pgt, gpa, &pte, level,
+ KVM_PGTABLE_WALK_SHARED);
+ if (ret)
+ return ret;
+ /* Unpopulated, must fault. */
+ if (!kvm_pte_valid(pte))
+ return 0;
+ return kvm_granule_size(*level);
+}
+
+/* Synthesised data abort at specified page table level. */
+#define PRE_FAULT_ESR(level) \
+ ((ESR_ELx_EC_DABT_LOW << ESR_ELx_EC_SHIFT) | \
+ ESR_ELx_IL | ESR_ELx_FSC_FAULT_L(level))
+
+/* Retrieve either a read-only or a read/write hva. */
+static hva_t gfn_to_hva_memslot_read(struct kvm_memory_slot *slot, gfn_t gfn)
+{
+ return gfn_to_hva_memslot_prot(slot, gfn, /*writable=*/NULL);
+}
+
+static long __pre_fault_s2(struct kvm_s2_mmu *mmu, struct kvm_vcpu *vcpu,
+ gpa_t gpa, struct kvm_memory_slot *memslot, s8 level)
+{
+ const bool is_gmem = kvm_slot_has_gmem(memslot);
+ const gfn_t gfn = gpa_to_gfn(gpa);
+ const hva_t hva = is_gmem ? 0 : gfn_to_hva_memslot_read(memslot, gfn);
+ const struct kvm_s2_fault_desc s2fd = {
+ .vcpu = vcpu,
+ .fault_ipa = gpa,
+ .nested = NULL,
+ .memslot = memslot,
+ .hva = hva,
+ .esr = PRE_FAULT_ESR(level),
+ .mmu = mmu,
+ };
+ struct kvm_s2_fault_result result = {};
+ long ret;
+
+ if (kvm_is_error_hva(hva))
+ return -EFAULT;
+
+ if (is_gmem)
+ ret = gmem_abort(&s2fd, &result);
+ else
+ ret = user_mem_abort(&s2fd, &result);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+ return result.mapping_size;
+}
+
+static long pre_fault_s2(struct kvm_s2_mmu *mmu, struct kvm_vcpu *vcpu,
+ gpa_t gpa, struct kvm_memory_slot *memslot)
+{
+ s8 level;
+ long ret;
+
+ /* Try a walk first. */
+ ret = kvm_walk_s2(mmu->pgt, gpa, &level);
+ if (ret)
+ return ret;
+ /* OK, have to fault page in. */
+ return __pre_fault_s2(mmu, vcpu, gpa, memslot, level);
+}
+
+static unsigned long
+pre_fault_bytes_consumed(gpa_t gpa, unsigned long granule_size,
+ unsigned long bytes_remaining)
+{
+ /* Granules are always a power-of-2. */
+ const unsigned long granule_bytes_remaining =
+ granule_size - (gpa % granule_size);
+
+ return min(granule_bytes_remaining, bytes_remaining);
+}
+
+/* If you lose the race this many times, time to give up. */
+#define MAX_PRE_FAULT_RETRIES 3
+
+/**
+ * kvm_arch_vcpu_pre_fault_memory - pre-fault stage-2 page tables for the
+ * specified GPA.
+ * @vcpu: The VCPU pointer
+ * @range: {gpa, size, flags} tuple
+ *
+ * The mapping performed is always best-effort - faulting in is necessarily
+ * racey. The ranges faulted in are canonical, nested page tables are ignored.
+ *
+ * @range->gpa specifies the GPA to pre-fault, @range->size specifies how many
+ * bytes remain to be pre-faulted and @range->flags is reserved and must be 0.
+ *
+ * Returns: the number of bytes the pre-fault consumed, or an error.
+ */
+long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
+ struct kvm_pre_fault_memory *range)
+{
+ struct kvm *kvm = vcpu->kvm;
+ const u64 bytes_remaining = range->size;
+ struct kvm_s2_mmu *mmu = &kvm->arch.mmu; /* Canonical. */
+ struct kvm_memory_slot *memslot;
+ const gpa_t gpa = range->gpa;
+ int num_retries = 0;
+ long ret;
+
+ /*
+ * pKVM is unsupported as their vCPUs are instantiated on first run and
+ * pre-faulting only running vCPUs would be inconsistent and confusing.
+ */
+ if (is_protected_kvm_enabled())
+ return -EOPNOTSUPP;
+
+ memslot = gfn_to_memslot(kvm, gpa_to_gfn(gpa));
+ if (!memslot)
+ return -ENOENT;
+ /* SRCU must be released for progress and only userland can do that. */
+ if (memslot->flags & KVM_MEMSLOT_INVALID)
+ return -EAGAIN;
+
+ do {
+ ret = pre_fault_s2(mmu, vcpu, gpa, memslot);
+ } while (ret == -EAGAIN && num_retries++ < MAX_PRE_FAULT_RETRIES);
+
+ if (IS_ERR_VALUE(ret))
+ return ret;
+ return pre_fault_bytes_consumed(gpa, ret, bytes_remaining);
+}
--
2.55.0
next prev parent reply other threads:[~2026-09-14 12:27 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 12:26 [PATCH v2 00/13] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 01/13] arm64: Add ESR fault helpers Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 02/13] KVM: arm64: Use ESR helpers in guest abort handling Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 03/13] KVM: arm64: Propagate and use esr in s2fd when handling guest aborts Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 04/13] KVM: arm64: Propagate and use mmu " Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 05/13] KVM: arm64: Propagate and use kvm_s2_fault_result on S2 fault Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 06/13] KVM: arm64: Size the stage-2 memcache from the fault MMU Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 07/13] KVM: arm64: Propagate EHWPOISON in kvm_s2_fault_pin_pfn() Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 08/13] KVM: arm64: Pass walk flags to kvm_pgtable_get_leaf() Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` Lorenzo Stoakes (ARM) [this message]
2026-09-14 12:26 ` [PATCH v2 10/13] Documentation: KVM: document arm64 KVM_PRE_FAULT_MEMORY Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 11/13] KVM: selftests: Enable pre_fault_memory_test for arm64 Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 12/13] KVM: selftests: Add option for different backing in pre-fault tests Lorenzo Stoakes (ARM)
2026-09-14 12:26 ` [PATCH v2 13/13] KVM: selftests: Add nested pre-fault test for arm64 Lorenzo Stoakes (ARM)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260914-kvm-arm-prefault-v2-9-26fb47f74b73@kernel.org \
--to=ljs@kernel.org \
--cc=Leo.Bras@arm.com \
--cc=alexandru.elisei@arm.com \
--cc=aneesh.kumar@kernel.org \
--cc=catalin.marinas@arm.com \
--cc=corbet@lwn.net \
--cc=imbrenda@linux.ibm.com \
--cc=jackabt.amazon@gmail.com \
--cc=jackabt@amazon.com \
--cc=joey.gouly@arm.com \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=pbonzini@redhat.com \
--cc=seanjc@google.com \
--cc=seiden@linux.ibm.com \
--cc=suzuki.poulose@arm.com \
--cc=tabba@google.com \
--cc=vdonnefort@google.com \
--cc=will@kernel.org \
--cc=yuzenghui@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®