* [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
@ 2026-09-22 14:17 ` Lorenzo Stoakes (ARM)
2026-09-22 16:49 ` Oliver Upton
2026-09-22 14:17 ` [PATCH v3 02/14] arm64: Add ESR fault helpers Lorenzo Stoakes (ARM)
` (13 subsequent siblings)
14 siblings, 1 reply; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:17 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
Currently kvm_vcpu_pre_fault_memory() is the only place where generic code
can call vcpu_load() on a vCPU that has not yet been initialised.
This can be problematic, as being uninitialised, a vCPU might not be in a
state where it is correct to do so.
Provide kvm_arch_vcpu_allow_pre_fault_memory() to allow architectures to
override this behaviour before the load is attempted.
Implement it as a __weak symbol defaulting to the current state where it is
always permitted.
This lays the foundation for a future change which implements pre-faulting
for arm64 which will wish to disallow this for uninitialised vCPUs.
As no architecture currently overrides it, no functional change intended.
Suggested-by: Oliver Upton <oupton@kernel.org>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/kvm_host.h | 1 +
virt/kvm/kvm_main.c | 8 ++++++++
2 files changed, 9 insertions(+)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 03bfc92864b6..c11fda8704a3 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1693,6 +1693,7 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu);
bool kvm_arch_dy_runnable(struct kvm_vcpu *vcpu);
bool kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu);
bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);
+bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu);
void kvm_arch_pre_destroy_vm(struct kvm *kvm);
void kvm_arch_create_vm_debugfs(struct kvm *kvm);
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 65eb26a0520d..9662baf2bfca 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)
return false;
}
+bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
+{
+ return true;
+}
+
void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
{
int nr_vcpus, start, i, idx, yielded;
@@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
range->gpa + range->size <= range->gpa)
return -EINVAL;
+ if (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))
+ return -ENOEXEC;
+
vcpu_load(vcpu);
idx = srcu_read_lock(&vcpu->kvm->srcu);
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 14:17 ` [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault Lorenzo Stoakes (ARM)
@ 2026-09-22 16:49 ` Oliver Upton
2026-09-22 17:23 ` Sean Christopherson
2026-09-22 17:31 ` Lorenzo Stoakes (ARM)
0 siblings, 2 replies; 31+ messages in thread
From: Oliver Upton @ 2026-09-22 16:49 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
Hi Lorenzo,
On Tue, Sep 22, 2026 at 03:17:55PM +0100, Lorenzo Stoakes (ARM) wrote:
> +bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> +{
> + return true;
> +}
> +
> void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
> {
> int nr_vcpus, start, i, idx, yielded;
> @@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> range->gpa + range->size <= range->gpa)
> return -EINVAL;
>
> + if (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))
> + return -ENOEXEC;
> +
nit: it'd be better to let the arch hook return an error of its choosing
but in reality this is only going to be used by arm64.
Not worth a respin on its own though.
Thanks,
Oliver
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 16:49 ` Oliver Upton
@ 2026-09-22 17:23 ` Sean Christopherson
2026-09-22 17:30 ` Lorenzo Stoakes (ARM)
2026-09-22 17:31 ` Lorenzo Stoakes (ARM)
1 sibling, 1 reply; 31+ messages in thread
From: Sean Christopherson @ 2026-09-22 17:23 UTC (permalink / raw)
To: Oliver Upton
Cc: Lorenzo Stoakes (ARM),
Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Claudio Imbrenda,
Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026, Oliver Upton wrote:
> Hi Lorenzo,
>
> On Tue, Sep 22, 2026 at 03:17:55PM +0100, Lorenzo Stoakes (ARM) wrote:
> > +bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> > +{
> > + return true;
> > +}
> > +
> > void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
> > {
> > int nr_vcpus, start, i, idx, yielded;
> > @@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> > range->gpa + range->size <= range->gpa)
> > return -EINVAL;
> >
> > + if (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))
> > + return -ENOEXEC;
> > +
>
> nit: it'd be better to let the arch hook return an error of its choosing
> but in reality this is only going to be used by arm64.
Heh, except x86 already has something similar.
if (!vcpu->kvm->arch.pre_fault_allowed)
return -EOPNOTSUPP;
As does s390:
if (kvm_is_ucontrol(vcpu->kvm))
return -EINVAL;
I also don't like that this is subtly about avoiding vcpu_load(); it will be all
too easy to overlook that detail in the future.
Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
the return value, the connection to vcpu_load() is obvious, and we don't need to
add another pre-check if future (or cleaned-up existing?) ioctls want to do
vcpu_load() in common code.
I'd also be tempted to say it can be a macro, not a __weak function. E.g.
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27fe0cd5b2d7..99613df254cf 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1533,6 +1533,7 @@ static inline bool __vcpu_has_feature(const struct kvm_arch *ka, int feature)
#define vcpu_has_feature(v, f) __vcpu_has_feature(&(v)->kvm->arch, (f))
#define kvm_vcpu_initialized(v) vcpu_get_flag(v, VCPU_INITIALIZED)
+#define kvm_is_vcpu_loadable kvm_vcpu_initialized
int kvm_trng_call(struct kvm_vcpu *vcpu);
#ifdef CONFIG_KVM
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 3dd04605f2e5..02401b080507 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1053,6 +1053,9 @@ int kvm_trylock_all_vcpus(struct kvm *kvm);
int kvm_lock_all_vcpus(struct kvm *kvm);
void kvm_unlock_all_vcpus(struct kvm *kvm);
+#ifndef kvm_is_vcpu_loadable
+#define kvm_is_vcpu_loadable(v) true
+#endif
void vcpu_load(struct kvm_vcpu *vcpu);
void vcpu_put(struct kvm_vcpu *vcpu);
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 17:23 ` Sean Christopherson
@ 2026-09-22 17:30 ` Lorenzo Stoakes (ARM)
2026-09-22 17:36 ` Sean Christopherson
0 siblings, 1 reply; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 17:30 UTC (permalink / raw)
To: Sean Christopherson
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> On Tue, Sep 22, 2026, Oliver Upton wrote:
> > Hi Lorenzo,
> >
> > On Tue, Sep 22, 2026 at 03:17:55PM +0100, Lorenzo Stoakes (ARM) wrote:
> > > +bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> > > +{
> > > + return true;
> > > +}
> > > +
> > > void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
> > > {
> > > int nr_vcpus, start, i, idx, yielded;
> > > @@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> > > range->gpa + range->size <= range->gpa)
> > > return -EINVAL;
> > >
> > > + if (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))
> > > + return -ENOEXEC;
> > > +
> >
> > nit: it'd be better to let the arch hook return an error of its choosing
> > but in reality this is only going to be used by arm64.
>
> Heh, except x86 already has something similar.
>
> if (!vcpu->kvm->arch.pre_fault_allowed)
> return -EOPNOTSUPP;
>
> As does s390:
>
> if (kvm_is_ucontrol(vcpu->kvm))
> return -EINVAL;
Yeah but they're all for different reasons I think :)
>
> I also don't like that this is subtly about avoiding vcpu_load(); it will be all
> too easy to overlook that detail in the future.
Well you see there's a problem here...
>
> Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> the return value, the connection to vcpu_load() is obvious, and we don't need to
> add another pre-check if future (or cleaned-up existing?) ioctls want to do
> vcpu_load() in common code.
...this is exactly what I started out with.
But then you are in a pickle, because _really_ you need to do that check in
vcpu_load(). Which is a void function. Which is called by every single
architecture all over the place.
So you'd have actually no way of signalling the error back.
Of course those places are arch code and you could say 'arches should know
better and if they call it it's fine not to call the arch 'can you load'
function.
But you're still stuck with the problem of where exactly you put this check.
So then do you put that check in a wrapper around it?
Instead you can make the predicate 'don't prefault on a not-yet-initialised
vCPU' which is pretty sensible I think, have a specific place to put it and
all's well with the world.
(And adding that makes sense in the pre-fault series too...)
>
> I'd also be tempted to say it can be a macro, not a __weak function. E.g.
Yeah it can be many things but why would you want a macro if you could possibly
avoid it? :)
Macros make the already-basically-pretend C type system into something even
worse.
Also it seems the convention for 'arches might not specify this' is the __weak
route AFAICT.
>
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index 27fe0cd5b2d7..99613df254cf 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -1533,6 +1533,7 @@ static inline bool __vcpu_has_feature(const struct kvm_arch *ka, int feature)
> #define vcpu_has_feature(v, f) __vcpu_has_feature(&(v)->kvm->arch, (f))
>
> #define kvm_vcpu_initialized(v) vcpu_get_flag(v, VCPU_INITIALIZED)
> +#define kvm_is_vcpu_loadable kvm_vcpu_initialized
>
> int kvm_trng_call(struct kvm_vcpu *vcpu);
> #ifdef CONFIG_KVM
> diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> index 3dd04605f2e5..02401b080507 100644
> --- a/include/linux/kvm_host.h
> +++ b/include/linux/kvm_host.h
> @@ -1053,6 +1053,9 @@ int kvm_trylock_all_vcpus(struct kvm *kvm);
> int kvm_lock_all_vcpus(struct kvm *kvm);
> void kvm_unlock_all_vcpus(struct kvm *kvm);
>
> +#ifndef kvm_is_vcpu_loadable
> +#define kvm_is_vcpu_loadable(v) true
> +#endif
> void vcpu_load(struct kvm_vcpu *vcpu);
> void vcpu_put(struct kvm_vcpu *vcpu);
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 17:30 ` Lorenzo Stoakes (ARM)
@ 2026-09-22 17:36 ` Sean Christopherson
2026-09-22 18:01 ` Lorenzo Stoakes (ARM)
2026-09-22 18:07 ` Oliver Upton
0 siblings, 2 replies; 31+ messages in thread
From: Sean Christopherson @ 2026-09-22 17:36 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > vcpu_load() in common code.
>
> ...this is exactly what I started out with.
>
> But then you are in a pickle, because _really_ you need to do that check in
> vcpu_load(). Which is a void function. Which is called by every single
> architecture all over the place.
>
> So you'd have actually no way of signalling the error back.
>
> Of course those places are arch code and you could say 'arches should know
> better and if they call it it's fine not to call the arch 'can you load'
> function.
Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
in linux/kvm_host.h.
> But you're still stuck with the problem of where exactly you put this check.
>
> So then do you put that check in a wrapper around it?
>
> Instead you can make the predicate 'don't prefault on a not-yet-initialised
> vCPU' which is pretty sensible I think, have a specific place to put it and
> all's well with the world.
But look at it from an x86 perspective. Pretty much everyone will look at this
and expect:
bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
{
return vcpu->kvm->arch.pre_fault_allowed;
}
> > I'd also be tempted to say it can be a macro, not a __weak function. E.g.
>
> Yeah it can be many things but why would you want a macro if you could possibly
> avoid it? :)
Because it allows arch code to dererefence "struct kvm_vcpu" in kvm_host.h,
i.e. allows "inlining" the check.
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 17:36 ` Sean Christopherson
@ 2026-09-22 18:01 ` Lorenzo Stoakes (ARM)
2026-09-22 18:40 ` Sean Christopherson
2026-09-22 18:07 ` Oliver Upton
1 sibling, 1 reply; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 18:01 UTC (permalink / raw)
To: Sean Christopherson
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 10:36:49AM -0700, Sean Christopherson wrote:
> On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > > vcpu_load() in common code.
> >
> > ...this is exactly what I started out with.
> >
> > But then you are in a pickle, because _really_ you need to do that check in
> > vcpu_load(). Which is a void function. Which is called by every single
> > architecture all over the place.
> >
> > So you'd have actually no way of signalling the error back.
> >
> > Of course those places are arch code and you could say 'arches should know
> > better and if they call it it's fine not to call the arch 'can you load'
> > function.
>
> Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
> in linux/kvm_host.h.
I note you dodge the actually difficult question of what this wrapper function
would look like ;)
So maybe like:
static int kvm_vcpu_load(struct kvm_vcpu *vcpu)
{
int err;
err = kvm_arch_allow_vcpu_load(vcpu);
if (err)
return err;
vcpu_load(vcpu);
return 0;
}
?
And I do like that you'd actually gate the right thing, I feel you on that,
obviously since this kind of predicate is what I started out with.
But I'm also looking to do the smallest possible thing here that fits the series
and doesn't preface it with a 'change how core kvm does something'.
But if Oliver/Marc feel this is viable then sure can go with it.
(Also naming is hard, kvm_vcpu_load()? do_vcpu_load()? maybe_vcpu_load()?
checked_vcpu_load()? vcpu_load_checked()? :P)
>
> > But you're still stuck with the problem of where exactly you put this check.
> >
> > So then do you put that check in a wrapper around it?
> >
> > Instead you can make the predicate 'don't prefault on a not-yet-initialised
> > vCPU' which is pretty sensible I think, have a specific place to put it and
> > all's well with the world.
>
> But look at it from an x86 perspective. Pretty much everyone will look at this
> and expect:
>
> bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> {
> return vcpu->kvm->arch.pre_fault_allowed;
> }
I'm not sure I really get your point here at all? :)
Why would it matter what people who are too lazy to go check the implementation
assume about an arch hook?
>
> > > I'd also be tempted to say it can be a macro, not a __weak function. E.g.
> >
> > Yeah it can be many things but why would you want a macro if you could possibly
> > avoid it? :)
>
> Because it allows arch code to dererefence "struct kvm_vcpu" in kvm_host.h,
> i.e. allows "inlining" the check.
Yeah I mean, micro-optimising a path run on a costly startup operation seems a
little unnecessary? :)
It seems the convention is __weak but I'm not going to die on this hill.
(C type safety is something of a myth but I do prefer to try to have what
little protection it offers when possible :)
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 18:01 ` Lorenzo Stoakes (ARM)
@ 2026-09-22 18:40 ` Sean Christopherson
2026-09-22 18:52 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 31+ messages in thread
From: Sean Christopherson @ 2026-09-22 18:40 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> On Tue, Sep 22, 2026 at 10:36:49AM -0700, Sean Christopherson wrote:
> > On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > > On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > > > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > > > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > > > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > > > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > > > vcpu_load() in common code.
> > >
> > > ...this is exactly what I started out with.
> > >
> > > But then you are in a pickle, because _really_ you need to do that check in
> > > vcpu_load(). Which is a void function. Which is called by every single
> > > architecture all over the place.
> > >
> > > So you'd have actually no way of signalling the error back.
> > >
> > > Of course those places are arch code and you could say 'arches should know
> > > better and if they call it it's fine not to call the arch 'can you load'
> > > function.
> >
> > Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
> > in linux/kvm_host.h.
>
> I note you dodge the actually difficult question of what this wrapper function
> would look like ;)
Oh, I was thinking we wouldn't bother with a wrapper. Because I agree that
providing a complement to vcpu_load() would get all kinds of messy. Though we
could harden vcpu_load() with a WARN_ON_ONCE(), which in practice would prevent
the vast majority of violations from making it to a final kernel release.
> > > Instead you can make the predicate 'don't prefault on a not-yet-initialised
> > > vCPU' which is pretty sensible I think, have a specific place to put it and
> > > all's well with the world.
> >
> > But look at it from an x86 perspective. Pretty much everyone will look at this
> > and expect:
> >
> > bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> > {
> > return vcpu->kvm->arch.pre_fault_allowed;
> > }
>
> I'm not sure I really get your point here at all? :)
>
> Why would it matter what people who are too lazy to go check the implementation
> assume about an arch hook?
Because I don't hate the people that contribute to KVM, and want to make it as
easy as possible for them to not make mistakes?
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 18:40 ` Sean Christopherson
@ 2026-09-22 18:52 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 18:52 UTC (permalink / raw)
To: Sean Christopherson
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 11:40:29AM -0700, Sean Christopherson wrote:
> > > But look at it from an x86 perspective. Pretty much everyone will look at this
> > > and expect:
> > >
> > > bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> > > {
> > > return vcpu->kvm->arch.pre_fault_allowed;
> > > }
> >
> > I'm not sure I really get your point here at all? :)
> >
> > Why would it matter what people who are too lazy to go check the implementation
> > assume about an arch hook?
>
> Because I don't hate the people that contribute to KVM, and want to make it as
> easy as possible for them to not make mistakes?
Haha well I think I misunderstood you here, I see the point that you might
assume some state like this rather than an explicit conditional, and the inline
comment suggested by Oliver makes things a bit clearer.
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 17:36 ` Sean Christopherson
2026-09-22 18:01 ` Lorenzo Stoakes (ARM)
@ 2026-09-22 18:07 ` Oliver Upton
2026-09-22 18:35 ` Lorenzo Stoakes (ARM)
1 sibling, 1 reply; 31+ messages in thread
From: Oliver Upton @ 2026-09-22 18:07 UTC (permalink / raw)
To: Sean Christopherson
Cc: Lorenzo Stoakes (ARM),
Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Claudio Imbrenda,
Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 10:36:49AM -0700, Sean Christopherson wrote:
> On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > > vcpu_load() in common code.
> >
> > ...this is exactly what I started out with.
> >
> > But then you are in a pickle, because _really_ you need to do that check in
> > vcpu_load(). Which is a void function. Which is called by every single
> > architecture all over the place.
> >
> > So you'd have actually no way of signalling the error back.
> >
> > Of course those places are arch code and you could say 'arches should know
> > better and if they call it it's fine not to call the arch 'can you load'
> > function.
>
> Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
> in linux/kvm_host.h.
I feel like trying to make this generic will wind up under-documenting
the single example we have with the pre fault ioctl. Putting the comment
into a header practically guarantees that nobody will read it either.
I'd favor doing something like below and sticking the comment inline in
the ioctl handler. Unless I'm missing something blatantly obvious, I
don't see why the x86 or s390 pre-conditions can't be tested early too.
But I don't care enough to bikeshed this any further.
Thanks,
Oliver
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 8b080804bc90..396e64875fe7 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1852,6 +1852,14 @@ static int kvm_arm_vcpu_set_events(struct kvm_vcpu *vcpu,
return __kvm_arm_vcpu_set_events(vcpu, events);
}
+int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
+{
+ if (!kvm_vcpu_initialized(vcpu))
+ return -ENOEXEC;
+
+ return 0;
+}
+
long kvm_arch_vcpu_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
index 5c73f43782a7..47fe032444f4 100644
--- a/arch/s390/kvm/s390/s390.c
+++ b/arch/s390/kvm/s390/s390.c
@@ -5784,6 +5784,14 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old,
s390_kvm_mmu_commit_memory_region(kvm, old, new, change);
}
+int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
+{
+ if (kvm_is_ucontrol(vcpu->kvm))
+ return -EINVAL;
+
+ return 0;
+}
+
/**
* kvm_arch_vcpu_pre_fault_memory() -- pre-fault and link gmap dat tables
* @vcpu: the vcpu that shall appear to have generated the fault-in.
@@ -5810,9 +5818,6 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu, struct kvm_pre_fault_
gpa_t end;
int rc;
- if (kvm_is_ucontrol(vcpu->kvm))
- return -EINVAL;
-
rc = kvm_s390_faultin_gfn(vcpu, NULL, &f);
if (rc == PGM_ADDRESSING)
return -ENOENT;
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 064ecc33b926..c35fd2868c20 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5086,6 +5086,14 @@ static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa,
}
}
+int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
+{
+ if (!vcpu->kvm->arch.pre_fault_allowed)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
struct kvm_pre_fault_memory *range)
{
@@ -5095,9 +5103,6 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
u64 end;
int r;
- if (!vcpu->kvm->arch.pre_fault_allowed)
- return -EOPNOTSUPP;
-
if (kvm_is_gfn_alias(vcpu->kvm, gpa_to_gfn(range->gpa)))
return -EINVAL;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 03bfc92864b6..bff842548c04 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1693,6 +1693,7 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu);
bool kvm_arch_dy_runnable(struct kvm_vcpu *vcpu);
bool kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu);
bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);
+int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu);
void kvm_arch_pre_destroy_vm(struct kvm *kvm);
void kvm_arch_create_vm_debugfs(struct kvm *kvm);
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 65eb26a0520d..07f2ce7a3cb3 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)
return false;
}
+int __weak kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
+{
+ return 0;
+}
+
void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
{
int nr_vcpus, start, i, idx, yielded;
@@ -4353,7 +4358,7 @@ static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu)
static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
struct kvm_pre_fault_memory *range)
{
- int idx;
+ int idx, ret;
long r;
u64 full_size;
@@ -4365,6 +4370,14 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
range->gpa + range->size <= range->gpa)
return -EINVAL;
+ /*
+ * Certain architectures (e.g. arm64) need to reject the ioctl 'early'
+ * before vcpu_load().
+ */
+ ret = kvm_arch_pre_fault_allowed(vcpu);
+ if (ret)
+ return ret;
+
vcpu_load(vcpu);
idx = srcu_read_lock(&vcpu->kvm->srcu);
Thanks,
Oliver
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 18:07 ` Oliver Upton
@ 2026-09-22 18:35 ` Lorenzo Stoakes (ARM)
2026-09-22 18:46 ` Sean Christopherson
0 siblings, 1 reply; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 18:35 UTC (permalink / raw)
To: Oliver Upton
Cc: Sean Christopherson, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 11:07:53AM -0700, Oliver Upton wrote:
> On Tue, Sep 22, 2026 at 10:36:49AM -0700, Sean Christopherson wrote:
> > On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > > On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > > > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > > > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > > > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > > > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > > > vcpu_load() in common code.
> > >
> > > ...this is exactly what I started out with.
> > >
> > > But then you are in a pickle, because _really_ you need to do that check in
> > > vcpu_load(). Which is a void function. Which is called by every single
> > > architecture all over the place.
> > >
> > > So you'd have actually no way of signalling the error back.
> > >
> > > Of course those places are arch code and you could say 'arches should know
> > > better and if they call it it's fine not to call the arch 'can you load'
> > > function.
> >
> > Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
> > in linux/kvm_host.h.
>
> I feel like trying to make this generic will wind up under-documenting
> the single example we have with the pre fault ioctl. Putting the comment
> into a header practically guarantees that nobody will read it either.
>
> I'd favor doing something like below and sticking the comment inline in
> the ioctl handler. Unless I'm missing something blatantly obvious, I
> don't see why the x86 or s390 pre-conditions can't be tested early too.
>
> But I don't care enough to bikeshed this any further.
Haha yup :) this is eminately bikesheddable territory.
>
> Thanks,
> Oliver
I'm fine with the below if x86/s390 people are.
The inline comment is a cheeky trick that should help clarify intent (I think
perhaps Sean that's what you meant re: people assuming it would check some local
state?)
Anyway if people think that's sane I can do on respin and we can settle on the
lovely shade of purple or whatever the shed looks like now ;)
>
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 8b080804bc90..396e64875fe7 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -1852,6 +1852,14 @@ static int kvm_arm_vcpu_set_events(struct kvm_vcpu *vcpu,
> return __kvm_arm_vcpu_set_events(vcpu, events);
> }
>
> +int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
> +{
> + if (!kvm_vcpu_initialized(vcpu))
> + return -ENOEXEC;
> +
> + return 0;
> +}
> +
> long kvm_arch_vcpu_ioctl(struct file *filp,
> unsigned int ioctl, unsigned long arg)
> {
> diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
> index 5c73f43782a7..47fe032444f4 100644
> --- a/arch/s390/kvm/s390/s390.c
> +++ b/arch/s390/kvm/s390/s390.c
> @@ -5784,6 +5784,14 @@ void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old,
> s390_kvm_mmu_commit_memory_region(kvm, old, new, change);
> }
>
> +int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
> +{
> + if (kvm_is_ucontrol(vcpu->kvm))
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> /**
> * kvm_arch_vcpu_pre_fault_memory() -- pre-fault and link gmap dat tables
> * @vcpu: the vcpu that shall appear to have generated the fault-in.
> @@ -5810,9 +5818,6 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu, struct kvm_pre_fault_
> gpa_t end;
> int rc;
>
> - if (kvm_is_ucontrol(vcpu->kvm))
> - return -EINVAL;
> -
> rc = kvm_s390_faultin_gfn(vcpu, NULL, &f);
> if (rc == PGM_ADDRESSING)
> return -ENOENT;
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 064ecc33b926..c35fd2868c20 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -5086,6 +5086,14 @@ static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa,
> }
> }
>
> +int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
> +{
> + if (!vcpu->kvm->arch.pre_fault_allowed)
> + return -EOPNOTSUPP;
> +
> + return 0;
> +}
> +
> long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> struct kvm_pre_fault_memory *range)
> {
> @@ -5095,9 +5103,6 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> u64 end;
> int r;
>
> - if (!vcpu->kvm->arch.pre_fault_allowed)
> - return -EOPNOTSUPP;
> -
> if (kvm_is_gfn_alias(vcpu->kvm, gpa_to_gfn(range->gpa)))
> return -EINVAL;
>
> diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> index 03bfc92864b6..bff842548c04 100644
> --- a/include/linux/kvm_host.h
> +++ b/include/linux/kvm_host.h
> @@ -1693,6 +1693,7 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu);
> bool kvm_arch_dy_runnable(struct kvm_vcpu *vcpu);
> bool kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu);
> bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);
> +int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu);
> void kvm_arch_pre_destroy_vm(struct kvm *kvm);
> void kvm_arch_create_vm_debugfs(struct kvm *kvm);
>
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 65eb26a0520d..07f2ce7a3cb3 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)
> return false;
> }
>
> +int __weak kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
> +{
> + return 0;
> +}
> +
> void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
> {
> int nr_vcpus, start, i, idx, yielded;
> @@ -4353,7 +4358,7 @@ static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu)
> static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> struct kvm_pre_fault_memory *range)
> {
> - int idx;
> + int idx, ret;
> long r;
> u64 full_size;
>
> @@ -4365,6 +4370,14 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> range->gpa + range->size <= range->gpa)
> return -EINVAL;
>
> + /*
> + * Certain architectures (e.g. arm64) need to reject the ioctl 'early'
> + * before vcpu_load().
> + */
> + ret = kvm_arch_pre_fault_allowed(vcpu);
> + if (ret)
> + return ret;
> +
> vcpu_load(vcpu);
> idx = srcu_read_lock(&vcpu->kvm->srcu);
>
> Thanks,
> Oliver
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 18:35 ` Lorenzo Stoakes (ARM)
@ 2026-09-22 18:46 ` Sean Christopherson
2026-09-22 18:54 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 31+ messages in thread
From: Sean Christopherson @ 2026-09-22 18:46 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> On Tue, Sep 22, 2026 at 11:07:53AM -0700, Oliver Upton wrote:
> > On Tue, Sep 22, 2026 at 10:36:49AM -0700, Sean Christopherson wrote:
> > > On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > > > On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > > > > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > > > > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > > > > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > > > > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > > > > vcpu_load() in common code.
> > > >
> > > > ...this is exactly what I started out with.
> > > >
> > > > But then you are in a pickle, because _really_ you need to do that check in
> > > > vcpu_load(). Which is a void function. Which is called by every single
> > > > architecture all over the place.
> > > >
> > > > So you'd have actually no way of signalling the error back.
> > > >
> > > > Of course those places are arch code and you could say 'arches should know
> > > > better and if they call it it's fine not to call the arch 'can you load'
> > > > function.
> > >
> > > Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
> > > in linux/kvm_host.h.
> >
> > I feel like trying to make this generic will wind up under-documenting
> > the single example we have with the pre fault ioctl. Putting the comment
> > into a header practically guarantees that nobody will read it either.
> >
> > I'd favor doing something like below and sticking the comment inline in
> > the ioctl handler. Unless I'm missing something blatantly obvious, I
> > don't see why the x86 or s390 pre-conditions can't be tested early too.
Oh, they definitely can. I'm a-ok with using kvm_arch_pre_fault_allowed() on
s390 and x86, the only option I am against is adding kvm_arch_pre_fault_allowed()
but then not using it on architectures that obviously perform that exact check.
> > --- a/virt/kvm/kvm_main.c
> > +++ b/virt/kvm/kvm_main.c
> > @@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)
> > return false;
> > }
> >
> > +int __weak kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
> > +{
> > + return 0;
> > +}
There should be no need for a __weak placeholder since this code is guarded by
CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY=y. I.e. force architectures to define the
API. I don't think it's a coincidence that all of arm64, s390, and x86 ended up
with restrictions; pre-faulting is far from a simple operation.
Actually, that's an argument for a dedicated kvm_arch_pre_fault_allowed() versus
a generic kvm_is_vcpu_loadable(): it helps force future architectures to actually
think about when exactly pre-faulting is safe.
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 18:46 ` Sean Christopherson
@ 2026-09-22 18:54 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 18:54 UTC (permalink / raw)
To: Sean Christopherson
Cc: Oliver Upton, Catalin Marinas, Will Deacon, Marc Zyngier,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, linux-arm-kernel, linux-kernel, kvmarm, kvm,
linux-doc, linux-kselftest, Jack Thomson, Jack Thomson,
Alexandru Elisei, Vincent Donnefort, Aneesh Kumar K.V,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 11:46:18AM -0700, Sean Christopherson wrote:
> On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > On Tue, Sep 22, 2026 at 11:07:53AM -0700, Oliver Upton wrote:
> > > On Tue, Sep 22, 2026 at 10:36:49AM -0700, Sean Christopherson wrote:
> > > > On Tue, Sep 22, 2026, Lorenzo Stoakes (ARM) wrote:
> > > > > On Tue, Sep 22, 2026 at 10:23:43AM -0700, Sean Christopherson wrote:
> > > > > > Rather than have kvm_arch_vcpu_allow_pre_fault_memory(), what if we add a more
> > > > > > generic kvm_is_vcpu_loadable()? That way we don't need to worry as much about
> > > > > > the return value, the connection to vcpu_load() is obvious, and we don't need to
> > > > > > add another pre-check if future (or cleaned-up existing?) ioctls want to do
> > > > > > vcpu_load() in common code.
> > > > >
> > > > > ...this is exactly what I started out with.
> > > > >
> > > > > But then you are in a pickle, because _really_ you need to do that check in
> > > > > vcpu_load(). Which is a void function. Which is called by every single
> > > > > architecture all over the place.
> > > > >
> > > > > So you'd have actually no way of signalling the error back.
> > > > >
> > > > > Of course those places are arch code and you could say 'arches should know
> > > > > better and if they call it it's fine not to call the arch 'can you load'
> > > > > function.
> > > >
> > > > Yes, that's my vote. It'd be easy enough to clarify that "rule" with a comment
> > > > in linux/kvm_host.h.
> > >
> > > I feel like trying to make this generic will wind up under-documenting
> > > the single example we have with the pre fault ioctl. Putting the comment
> > > into a header practically guarantees that nobody will read it either.
> > >
> > > I'd favor doing something like below and sticking the comment inline in
> > > the ioctl handler. Unless I'm missing something blatantly obvious, I
> > > don't see why the x86 or s390 pre-conditions can't be tested early too.
>
> Oh, they definitely can. I'm a-ok with using kvm_arch_pre_fault_allowed() on
> s390 and x86, the only option I am against is adding kvm_arch_pre_fault_allowed()
> but then not using it on architectures that obviously perform that exact check.
>
> > > --- a/virt/kvm/kvm_main.c
> > > +++ b/virt/kvm/kvm_main.c
> > > @@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)
> > > return false;
> > > }
> > >
> > > +int __weak kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
> > > +{
> > > + return 0;
> > > +}
>
> There should be no need for a __weak placeholder since this code is guarded by
> CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY=y. I.e. force architectures to define the
> API. I don't think it's a coincidence that all of arm64, s390, and x86 ended up
> with restrictions; pre-faulting is far from a simple operation.
>
> Actually, that's an argument for a dedicated kvm_arch_pre_fault_allowed() versus
> a generic kvm_is_vcpu_loadable(): it helps force future architectures to actually
> think about when exactly pre-faulting is safe.
Ack on all and I agree __weak is not necessary in this case and probably quite
useful to make arches define it.
Will take this approach on respin thanks all!
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault
2026-09-22 16:49 ` Oliver Upton
2026-09-22 17:23 ` Sean Christopherson
@ 2026-09-22 17:31 ` Lorenzo Stoakes (ARM)
1 sibling, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 17:31 UTC (permalink / raw)
To: Oliver Upton
Cc: Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 09:49:18AM -0700, Oliver Upton wrote:
> Hi Lorenzo,
>
> On Tue, Sep 22, 2026 at 03:17:55PM +0100, Lorenzo Stoakes (ARM) wrote:
> > +bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
> > +{
> > + return true;
> > +}
> > +
> > void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
> > {
> > int nr_vcpus, start, i, idx, yielded;
> > @@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
> > range->gpa + range->size <= range->gpa)
> > return -EINVAL;
> >
> > + if (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))
> > + return -ENOEXEC;
> > +
>
> nit: it'd be better to let the arch hook return an error of its choosing
> but in reality this is only going to be used by arm64.
>
> Not worth a respin on its own though.
Ack will fix up if there's a respin for something else!
>
> Thanks,
> Oliver
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v3 02/14] arm64: Add ESR fault helpers
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
2026-09-22 14:17 ` [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault Lorenzo Stoakes (ARM)
@ 2026-09-22 14:17 ` Lorenzo Stoakes (ARM)
2026-09-22 17:00 ` Oliver Upton
2026-09-22 14:17 ` [PATCH v3 03/14] KVM: arm64: Use ESR helpers in guest abort handling Lorenzo Stoakes (ARM)
` (12 subsequent siblings)
14 siblings, 1 reply; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:17 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
Add helper functions which operate directly on an ESR value rather than
trying to access the register itself.
These mirror equivalent KVM vCPU wrappers in kvm_emulate.h and allow those
wrappers and KVM's stage-2 fault handling to operate on a plain ESR value.
This is needed to later generate a synthetic fault for the stage-2 page
table pre-faulting mechanism.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/include/asm/esr.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
index f816f5d77f1a..9c0205983d9a 100644
--- a/arch/arm64/include/asm/esr.h
+++ b/arch/arm64/include/asm/esr.h
@@ -437,6 +437,50 @@
#ifndef __ASSEMBLER__
#include <asm/types.h>
+static __always_inline u8 esr_get_ec(unsigned long esr)
+{
+ return ESR_ELx_EC(esr);
+}
+
+static __always_inline bool esr_trap_is_iabt(unsigned long esr)
+{
+ return esr_get_ec(esr) == ESR_ELx_EC_IABT_LOW;
+}
+
+static __always_inline bool esr_abt_is_s1ptw(unsigned long esr)
+{
+ return esr & ESR_ELx_S1PTW;
+}
+
+/* Always check for S1PTW *before* using this. */
+static __always_inline bool esr_dabt_is_write(unsigned long esr)
+{
+ return esr & ESR_ELx_WNR;
+}
+
+static __always_inline bool esr_dabt_is_cm(unsigned long esr)
+{
+ return esr & ESR_ELx_CM;
+}
+
+static __always_inline bool esr_abt_is_exec_fault(unsigned long esr)
+{
+ return esr_trap_is_iabt(esr) && !esr_abt_is_s1ptw(esr);
+}
+
+static __always_inline bool esr_abt_is_sea(unsigned long esr)
+{
+ switch (esr & ESR_ELx_FSC) {
+ case ESR_ELx_FSC_EXTABT:
+ case ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3):
+ case ESR_ELx_FSC_SECC:
+ case ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3):
+ return true;
+ default:
+ return false;
+ }
+}
+
static inline unsigned long esr_brk_comment(unsigned long esr)
{
return esr & ESR_ELx_BRK64_ISS_COMMENT_MASK;
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 02/14] arm64: Add ESR fault helpers
2026-09-22 14:17 ` [PATCH v3 02/14] arm64: Add ESR fault helpers Lorenzo Stoakes (ARM)
@ 2026-09-22 17:00 ` Oliver Upton
2026-09-22 17:45 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 31+ messages in thread
From: Oliver Upton @ 2026-09-22 17:00 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 03:17:56PM +0100, Lorenzo Stoakes (ARM) wrote:
> Add helper functions which operate directly on an ESR value rather than
> trying to access the register itself.
>
> These mirror equivalent KVM vCPU wrappers in kvm_emulate.h and allow those
> wrappers and KVM's stage-2 fault handling to operate on a plain ESR value.
>
> This is needed to later generate a synthetic fault for the stage-2 page
> table pre-faulting mechanism.
>
> No functional change intended.
>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> arch/arm64/include/asm/esr.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 44 insertions(+)
>
> diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
> index f816f5d77f1a..9c0205983d9a 100644
> --- a/arch/arm64/include/asm/esr.h
> +++ b/arch/arm64/include/asm/esr.h
> @@ -437,6 +437,50 @@
> #ifndef __ASSEMBLER__
> #include <asm/types.h>
>
> +static __always_inline u8 esr_get_ec(unsigned long esr)
> +{
> + return ESR_ELx_EC(esr);
> +}
Why do we need to wrap the macro in an inline function?
> +static __always_inline bool esr_trap_is_iabt(unsigned long esr)
> +{
> + return esr_get_ec(esr) == ESR_ELx_EC_IABT_LOW;
> +}
> +
> +static __always_inline bool esr_abt_is_s1ptw(unsigned long esr)
> +{
> + return esr & ESR_ELx_S1PTW;
> +}
This helper doesn't make a ton of sense outside of KVM, since
ESR_ELx.S1PTW is RES0 (not RAZ) outside of a stage-2 abort.
Thanks,
Oliver
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 02/14] arm64: Add ESR fault helpers
2026-09-22 17:00 ` Oliver Upton
@ 2026-09-22 17:45 ` Lorenzo Stoakes (ARM)
2026-09-22 18:13 ` Oliver Upton
0 siblings, 1 reply; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 17:45 UTC (permalink / raw)
To: Oliver Upton
Cc: Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 10:00:39AM -0700, Oliver Upton wrote:
> On Tue, Sep 22, 2026 at 03:17:56PM +0100, Lorenzo Stoakes (ARM) wrote:
> > Add helper functions which operate directly on an ESR value rather than
> > trying to access the register itself.
> >
> > These mirror equivalent KVM vCPU wrappers in kvm_emulate.h and allow those
> > wrappers and KVM's stage-2 fault handling to operate on a plain ESR value.
> >
> > This is needed to later generate a synthetic fault for the stage-2 page
> > table pre-faulting mechanism.
> >
> > No functional change intended.
> >
> > Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> > ---
> > arch/arm64/include/asm/esr.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 44 insertions(+)
> >
> > diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
> > index f816f5d77f1a..9c0205983d9a 100644
> > --- a/arch/arm64/include/asm/esr.h
> > +++ b/arch/arm64/include/asm/esr.h
> > @@ -437,6 +437,50 @@
> > #ifndef __ASSEMBLER__
> > #include <asm/types.h>
> >
> > +static __always_inline u8 esr_get_ec(unsigned long esr)
> > +{
> > + return ESR_ELx_EC(esr);
> > +}
>
> Why do we need to wrap the macro in an inline function?
It seemed like a more consistent way of doing this but looking through the code
ESR_ELx_EC() is referenced directly in quite a few places so will swap out.
>
> > +static __always_inline bool esr_trap_is_iabt(unsigned long esr)
> > +{
> > + return esr_get_ec(esr) == ESR_ELx_EC_IABT_LOW;
> > +}
> > +
> > +static __always_inline bool esr_abt_is_s1ptw(unsigned long esr)
> > +{
> > + return esr & ESR_ELx_S1PTW;
> > +}
>
> This helper doesn't make a ton of sense outside of KVM, since
> ESR_ELx.S1PTW is RES0 (not RAZ) outside of a stage-2 abort.
OK, kvm_emulate.h better?
>
> Thanks,
> Oliver
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 02/14] arm64: Add ESR fault helpers
2026-09-22 17:45 ` Lorenzo Stoakes (ARM)
@ 2026-09-22 18:13 ` Oliver Upton
0 siblings, 0 replies; 31+ messages in thread
From: Oliver Upton @ 2026-09-22 18:13 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 06:45:43PM +0100, Lorenzo Stoakes (ARM) wrote:
> On Tue, Sep 22, 2026 at 10:00:39AM -0700, Oliver Upton wrote:
> > On Tue, Sep 22, 2026 at 03:17:56PM +0100, Lorenzo Stoakes (ARM) wrote:
> > > Add helper functions which operate directly on an ESR value rather than
> > > trying to access the register itself.
> > >
> > > These mirror equivalent KVM vCPU wrappers in kvm_emulate.h and allow those
> > > wrappers and KVM's stage-2 fault handling to operate on a plain ESR value.
> > >
> > > This is needed to later generate a synthetic fault for the stage-2 page
> > > table pre-faulting mechanism.
> > >
> > > No functional change intended.
> > >
> > > Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> > > ---
> > > arch/arm64/include/asm/esr.h | 44 ++++++++++++++++++++++++++++++++++++++++++++
> > > 1 file changed, 44 insertions(+)
> > >
> > > diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
> > > index f816f5d77f1a..9c0205983d9a 100644
> > > --- a/arch/arm64/include/asm/esr.h
> > > +++ b/arch/arm64/include/asm/esr.h
> > > @@ -437,6 +437,50 @@
> > > #ifndef __ASSEMBLER__
> > > #include <asm/types.h>
> > >
> > > +static __always_inline u8 esr_get_ec(unsigned long esr)
> > > +{
> > > + return ESR_ELx_EC(esr);
> > > +}
> >
> > Why do we need to wrap the macro in an inline function?
>
> It seemed like a more consistent way of doing this but looking through the code
> ESR_ELx_EC() is referenced directly in quite a few places so will swap out.
>
> >
> > > +static __always_inline bool esr_trap_is_iabt(unsigned long esr)
> > > +{
> > > + return esr_get_ec(esr) == ESR_ELx_EC_IABT_LOW;
> > > +}
> > > +
> > > +static __always_inline bool esr_abt_is_s1ptw(unsigned long esr)
> > > +{
> > > + return esr & ESR_ELx_S1PTW;
> > > +}
> >
> > This helper doesn't make a ton of sense outside of KVM, since
> > ESR_ELx.S1PTW is RES0 (not RAZ) outside of a stage-2 abort.
>
> OK, kvm_emulate.h better?
Works for me, thanks!
Best,
Oliver
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v3 03/14] KVM: arm64: Use ESR helpers in guest abort handling
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
2026-09-22 14:17 ` [PATCH v3 01/14] KVM: Allow architectures to disallow pre-fault Lorenzo Stoakes (ARM)
2026-09-22 14:17 ` [PATCH v3 02/14] arm64: Add ESR fault helpers Lorenzo Stoakes (ARM)
@ 2026-09-22 14:17 ` Lorenzo Stoakes (ARM)
2026-09-22 14:17 ` [PATCH v3 04/14] KVM: arm64: Propagate and use esr in s2fd when handling guest aborts Lorenzo Stoakes (ARM)
` (11 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:17 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
Convert kvm_vcpu_* ESR wrappers in kvm_emulate.h and the stage-2 abort
handling logic in mmu.c to use the newly introduced ESR helpers.
kvm_is_write_fault() is split in two, with esr_abt_is_write_fault()
handling the esr parts of the operation and kvm_is_write_fault() wraps it.
Introduce/modify kvm_s2_fault_is_{perm,exec,write}(),
kvm_s2_perm_fault_granule() so the abort path reads the ESR from a single
place.
This is to allow a later change to permit stage 2 pre-faulting via a
synthetic ESR value.
Remove now-unused kvm_vcpu_dabt_is_cm(), kvm_vcpu_trap_is_exec_fault() and
kvm_vcpu_trap_get_perm_fault_granule().
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/include/asm/kvm_emulate.h | 52 +++++++-----------------
arch/arm64/kvm/mmu.c | 76 ++++++++++++++++++++++--------------
2 files changed, 61 insertions(+), 67 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index a3c1928bdf74..a3b272265554 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -411,18 +411,13 @@ static __always_inline int kvm_vcpu_dabt_get_rd(const struct kvm_vcpu *vcpu)
static __always_inline bool kvm_vcpu_abt_iss1tw(const struct kvm_vcpu *vcpu)
{
- return !!(kvm_vcpu_get_esr(vcpu) & ESR_ELx_S1PTW);
+ return esr_abt_is_s1ptw(kvm_vcpu_get_esr(vcpu));
}
/* Always check for S1PTW *before* using this. */
static __always_inline bool kvm_vcpu_dabt_iswrite(const struct kvm_vcpu *vcpu)
{
- return kvm_vcpu_get_esr(vcpu) & ESR_ELx_WNR;
-}
-
-static inline bool kvm_vcpu_dabt_is_cm(const struct kvm_vcpu *vcpu)
-{
- return !!(kvm_vcpu_get_esr(vcpu) & ESR_ELx_CM);
+ return esr_dabt_is_write(kvm_vcpu_get_esr(vcpu));
}
static __always_inline unsigned int kvm_vcpu_dabt_get_as(const struct kvm_vcpu *vcpu)
@@ -438,17 +433,12 @@ static __always_inline bool kvm_vcpu_trap_il_is32bit(const struct kvm_vcpu *vcpu
static __always_inline u8 kvm_vcpu_trap_get_class(const struct kvm_vcpu *vcpu)
{
- return ESR_ELx_EC(kvm_vcpu_get_esr(vcpu));
+ return esr_get_ec(kvm_vcpu_get_esr(vcpu));
}
static inline bool kvm_vcpu_trap_is_iabt(const struct kvm_vcpu *vcpu)
{
- return kvm_vcpu_trap_get_class(vcpu) == ESR_ELx_EC_IABT_LOW;
-}
-
-static inline bool kvm_vcpu_trap_is_exec_fault(const struct kvm_vcpu *vcpu)
-{
- return kvm_vcpu_trap_is_iabt(vcpu) && !kvm_vcpu_abt_iss1tw(vcpu);
+ return esr_trap_is_iabt(kvm_vcpu_get_esr(vcpu));
}
static __always_inline u8 kvm_vcpu_trap_get_fault(const struct kvm_vcpu *vcpu)
@@ -468,26 +458,9 @@ bool kvm_vcpu_trap_is_translation_fault(const struct kvm_vcpu *vcpu)
return esr_fsc_is_translation_fault(kvm_vcpu_get_esr(vcpu));
}
-static inline
-u64 kvm_vcpu_trap_get_perm_fault_granule(const struct kvm_vcpu *vcpu)
-{
- unsigned long esr = kvm_vcpu_get_esr(vcpu);
-
- BUG_ON(!esr_fsc_is_permission_fault(esr));
- return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(esr & ESR_ELx_FSC_LEVEL));
-}
-
static __always_inline bool kvm_vcpu_abt_issea(const struct kvm_vcpu *vcpu)
{
- switch (kvm_vcpu_trap_get_fault(vcpu)) {
- case ESR_ELx_FSC_EXTABT:
- case ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3):
- case ESR_ELx_FSC_SECC:
- case ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3):
- return true;
- default:
- return false;
- }
+ return esr_abt_is_sea(kvm_vcpu_get_esr(vcpu));
}
static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu)
@@ -496,9 +469,9 @@ static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu)
return ESR_ELx_SYS64_ISS_RT(esr);
}
-static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)
+static inline bool esr_abt_is_write_fault(unsigned long esr)
{
- if (kvm_vcpu_abt_iss1tw(vcpu)) {
+ if (esr_abt_is_s1ptw(esr)) {
/*
* Only a permission fault on a S1PTW should be
* considered as a write. Otherwise, page tables baked
@@ -511,13 +484,18 @@ static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)
* first), then a permission fault to allow the flags
* to be set.
*/
- return kvm_vcpu_trap_is_permission_fault(vcpu);
+ return esr_fsc_is_permission_fault(esr);
}
- if (kvm_vcpu_trap_is_iabt(vcpu))
+ if (esr_trap_is_iabt(esr))
return false;
- return kvm_vcpu_dabt_iswrite(vcpu);
+ return esr_dabt_is_write(esr);
+}
+
+static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)
+{
+ return esr_abt_is_write_fault(kvm_vcpu_get_esr(vcpu));
}
static inline unsigned long kvm_vcpu_get_mpidr_aff(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index d6187295c373..d8b11817a45f 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1657,10 +1657,35 @@ struct kvm_s2_fault_desc {
unsigned long hva;
};
+static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
+{
+ return esr_fsc_is_permission_fault(kvm_vcpu_get_esr(s2fd->vcpu));
+}
+
+static bool kvm_s2_fault_is_exec(const struct kvm_s2_fault_desc *s2fd)
+{
+ return esr_abt_is_exec_fault(kvm_vcpu_get_esr(s2fd->vcpu));
+}
+
+static bool kvm_s2_fault_is_write(const struct kvm_s2_fault_desc *s2fd)
+{
+ return esr_abt_is_write_fault(kvm_vcpu_get_esr(s2fd->vcpu));
+}
+
+static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd)
+{
+ u64 level;
+
+ if (!kvm_s2_fault_is_perm(s2fd))
+ return 0;
+ level = kvm_vcpu_get_esr(s2fd->vcpu) & ESR_ELx_FSC_LEVEL;
+ return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
+}
+
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);
+ 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;
@@ -1690,8 +1715,8 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
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);
+ write_fault = kvm_s2_fault_is_write(s2fd);
+ exec_fault = kvm_s2_fault_is_exec(s2fd);
VM_WARN_ON_ONCE(write_fault && exec_fault);
@@ -1910,11 +1935,6 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
return vma_shift;
}
-static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
-{
- return kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
-}
-
static int kvm_s2_fault_get_vma_info(const struct kvm_s2_fault_desc *s2fd,
struct kvm_s2_fault_vma_info *s2vi)
{
@@ -1980,7 +2000,7 @@ static int kvm_s2_fault_pin_pfn(const struct kvm_s2_fault_desc *s2fd,
return ret;
s2vi->pfn = __kvm_faultin_pfn(s2fd->memslot, get_canonical_gfn(s2fd, s2vi),
- kvm_is_write_fault(s2fd->vcpu) ? FOLL_WRITE : 0,
+ kvm_s2_fault_is_write(s2fd) ? FOLL_WRITE : 0,
&s2vi->map_writable, &s2vi->page);
if (unlikely(is_error_noslot_pfn(s2vi->pfn))) {
if (s2vi->pfn == KVM_PFN_ERR_HWPOISON) {
@@ -2038,7 +2058,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
{
struct kvm *kvm = s2fd->vcpu->kvm;
- if (kvm_vcpu_trap_is_exec_fault(s2fd->vcpu) && s2vi->map_non_cacheable)
+ if (kvm_s2_fault_is_exec(s2fd) && s2vi->map_non_cacheable)
return -ENOEXEC;
/*
@@ -2056,13 +2076,13 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
if (s2vi->map_writable && (s2vi->device ||
!memslot_is_logging(s2fd->memslot) ||
- kvm_is_write_fault(s2fd->vcpu)))
+ kvm_s2_fault_is_write(s2fd)))
*prot |= KVM_PGTABLE_PROT_W;
if (s2fd->nested)
*prot = adjust_nested_fault_perms(s2fd->nested, *prot);
- if (kvm_vcpu_trap_is_exec_fault(s2fd->vcpu))
+ if (kvm_s2_fault_is_exec(s2fd))
*prot |= KVM_PGTABLE_PROT_X;
if (s2vi->map_non_cacheable)
@@ -2115,8 +2135,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
if (mmu_invalidate_retry(kvm, s2vi->mmu_seq))
goto out_unlock;
- perm_fault_granule = (kvm_s2_fault_is_perm(s2fd) ?
- kvm_vcpu_trap_get_perm_fault_granule(s2fd->vcpu) : 0);
+ perm_fault_granule = kvm_s2_perm_fault_granule(s2fd);
mapping_size = s2vi->vma_pagesize;
pfn = s2vi->pfn;
gfn = s2vi->gfn;
@@ -2195,7 +2214,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
{
- bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
+ bool perm_fault = kvm_s2_fault_is_perm(s2fd);
struct kvm_s2_fault_vma_info s2vi = {};
enum kvm_pgtable_prot prot;
void *memcache;
@@ -2342,7 +2361,7 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
{
struct kvm_s2_trans nested_trans, *nested = NULL;
- unsigned long esr;
+ unsigned long esr = kvm_vcpu_get_esr(vcpu);
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;
@@ -2351,11 +2370,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
gfn_t gfn;
int ret, idx;
- if (kvm_vcpu_abt_issea(vcpu))
+ if (esr_abt_is_sea(esr))
return kvm_handle_guest_sea(vcpu);
- esr = kvm_vcpu_get_esr(vcpu);
-
/*
* The fault IPA should be reliable at this point as we're not dealing
* with an SEA.
@@ -2364,7 +2381,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
if (KVM_BUG_ON(ipa == INVALID_GPA, vcpu->kvm))
return -EFAULT;
- is_iabt = kvm_vcpu_trap_is_iabt(vcpu);
+ is_iabt = esr_trap_is_iabt(esr);
if (esr_fsc_is_translation_fault(esr)) {
/* Beyond sanitised PARange (which is the IPA limit) */
@@ -2381,7 +2398,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
}
}
- trace_kvm_guest_fault(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu),
+ trace_kvm_guest_fault(*vcpu_pc(vcpu), esr,
kvm_vcpu_get_hfar(vcpu), fault_ipa);
/* Check the stage-2 fault is trans. fault or write fault */
@@ -2390,9 +2407,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
!esr_fsc_is_access_flag_fault(esr) &&
!esr_fsc_is_excl_atomic_fault(esr)) {
kvm_err("Unsupported FSC: EC=%#x xFSC=%#lx ESR_EL2=%#lx\n",
- kvm_vcpu_trap_get_class(vcpu),
- (unsigned long)kvm_vcpu_trap_get_fault(vcpu),
- (unsigned long)kvm_vcpu_get_esr(vcpu));
+ esr_get_ec(esr),
+ (unsigned long)(esr & ESR_ELx_FSC),
+ (unsigned long)esr);
return -EFAULT;
}
@@ -2441,7 +2458,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
gfn = ipa >> PAGE_SHIFT;
memslot = gfn_to_memslot(vcpu->kvm, gfn);
hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
- write_fault = kvm_is_write_fault(vcpu);
+ write_fault = esr_abt_is_write_fault(esr);
if (kvm_is_error_hva(hva) || (write_fault && !writable)) {
/*
* The guest has put either its instructions or its page-tables
@@ -2454,7 +2471,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
goto out;
}
- if (kvm_vcpu_abt_iss1tw(vcpu)) {
+ if (esr_abt_is_s1ptw(esr)) {
ret = kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
goto out_unlock;
}
@@ -2469,7 +2486,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
* So let's assume that the guest is just being
* cautious, and skip the instruction.
*/
- if (kvm_is_error_hva(hva) && kvm_vcpu_dabt_is_cm(vcpu)) {
+ if (kvm_is_error_hva(hva) && esr_dabt_is_cm(esr)) {
kvm_incr_pc(vcpu);
ret = 1;
goto out_unlock;
@@ -2506,9 +2523,8 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
if (kvm_vm_is_protected(vcpu->kvm)) {
ret = pkvm_mem_abort(&s2fd);
} else {
- VM_WARN_ON_ONCE(kvm_vcpu_trap_is_permission_fault(vcpu) &&
- !write_fault &&
- !kvm_vcpu_trap_is_exec_fault(vcpu));
+ VM_WARN_ON_ONCE(kvm_s2_fault_is_perm(&s2fd) && !write_fault &&
+ !kvm_s2_fault_is_exec(&s2fd));
if (kvm_slot_has_gmem(memslot))
ret = gmem_abort(&s2fd);
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 04/14] KVM: arm64: Propagate and use esr in s2fd when handling guest aborts
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (2 preceding siblings ...)
2026-09-22 14:17 ` [PATCH v3 03/14] KVM: arm64: Use ESR helpers in guest abort handling Lorenzo Stoakes (ARM)
@ 2026-09-22 14:17 ` Lorenzo Stoakes (ARM)
2026-09-22 14:17 ` [PATCH v3 05/14] KVM: arm64: Propagate and use mmu " Lorenzo Stoakes (ARM)
` (10 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:17 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
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 Exception Syndrome Register (ESR) via
s2fd->vcpu.
Introduce an s2fd->esr field to abstract this and propagate it to callers.
The value of this (beyond refactoring) is to be able to later generate
faults with a synthetic esr, specifically to implement stage 2 page table
pre-faulting.
Abort handlers which use kvm_s2_fault_desc - gmem_abort() and
user_mem_abort() - now only reference s2fd->esr and do not look it up in
any other way, which makes it safe to pass a synthetic s2fd->esr value to
these functions.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/kvm/mmu.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index d8b11817a45f..3cd4dd6478db 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1655,21 +1655,22 @@ struct kvm_s2_fault_desc {
struct kvm_s2_trans *nested;
struct kvm_memory_slot *memslot;
unsigned long hva;
+ unsigned long esr;
};
static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
{
- return esr_fsc_is_permission_fault(kvm_vcpu_get_esr(s2fd->vcpu));
+ return esr_fsc_is_permission_fault(s2fd->esr);
}
static bool kvm_s2_fault_is_exec(const struct kvm_s2_fault_desc *s2fd)
{
- return esr_abt_is_exec_fault(kvm_vcpu_get_esr(s2fd->vcpu));
+ return esr_abt_is_exec_fault(s2fd->esr);
}
static bool kvm_s2_fault_is_write(const struct kvm_s2_fault_desc *s2fd)
{
- return esr_abt_is_write_fault(kvm_vcpu_get_esr(s2fd->vcpu));
+ return esr_abt_is_write_fault(s2fd->esr);
}
static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd)
@@ -1678,7 +1679,7 @@ static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd)
if (!kvm_s2_fault_is_perm(s2fd))
return 0;
- level = kvm_vcpu_get_esr(s2fd->vcpu) & ESR_ELx_FSC_LEVEL;
+ level = s2fd->esr & ESR_ELx_FSC_LEVEL;
return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
}
@@ -2067,7 +2068,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
* and trigger the exception here. Since the memslot is valid, inject
* the fault back to the guest.
*/
- if (esr_fsc_is_excl_atomic_fault(kvm_vcpu_get_esr(s2fd->vcpu))) {
+ if (esr_fsc_is_excl_atomic_fault(s2fd->esr)) {
kvm_inject_dabt_excl_atomic(s2fd->vcpu, kvm_vcpu_get_hfar(s2fd->vcpu));
return 1;
}
@@ -2518,6 +2519,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
.nested = nested,
.memslot = memslot,
.hva = hva,
+ .esr = esr,
};
if (kvm_vm_is_protected(vcpu->kvm)) {
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 05/14] KVM: arm64: Propagate and use mmu in s2fd when handling guest aborts
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (3 preceding siblings ...)
2026-09-22 14:17 ` [PATCH v3 04/14] KVM: arm64: Propagate and use esr in s2fd when handling guest aborts Lorenzo Stoakes (ARM)
@ 2026-09-22 14:17 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 06/14] KVM: arm64: Propagate and use kvm_s2_fault_result on S2 fault Lorenzo Stoakes (ARM)
` (9 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:17 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
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@kernel.org>
---
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 3cd4dd6478db..ea0d4be0298b 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1656,6 +1656,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)
@@ -1689,7 +1690,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
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;
struct kvm_guest_s2_mapping *mapping = NULL;
unsigned long mmu_seq;
struct page *page;
@@ -1805,7 +1806,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;
@@ -2131,7 +2132,7 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
}
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;
@@ -2363,6 +2364,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
{
struct kvm_s2_trans nested_trans, *nested = NULL;
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;
@@ -2392,7 +2394,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);
@@ -2429,8 +2431,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);
@@ -2505,7 +2507,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);
@@ -2520,6 +2522,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
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 06/14] KVM: arm64: Propagate and use kvm_s2_fault_result on S2 fault
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (4 preceding siblings ...)
2026-09-22 14:17 ` [PATCH v3 05/14] KVM: arm64: Propagate and use mmu " Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 07/14] KVM: arm64: Size the stage-2 memcache from the fault MMU Lorenzo Stoakes (ARM)
` (8 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
When stage 2 page tables fault the net result may either be that a page is
mapped, an error occurred or the fault should be retried (-EAGAIN).
When a fault succeeds it may be upgraded to a PMD size via
transparent_hugepage_adjust().
In order to support KVM pre-faulting the outcome of the fault and the
mapping size must be recorded.
Track the mapping size via new kvm_s2_fault_result struct, which is
threaded through gmem_abort(), user_mem_abort() and kvm_s2_fault_map().
PKVM and SEA aren't relevant to synthetic pre-faulting so neither
kvm_inject_sea() nor pkvm_mem_abort() are altered.
Actual hardware faulting doesn't require this information, so
kvm_handle_guest_abort() simply passes NULL kvm_s2_fault_result to
gmem_abort() and user_mem_abort().
A non-NULL result also tells the abort handlers that the caller is
pre-faulting rather than a vcpu, in which case -EAGAIN is propagated to the
caller to allow the pre-fault to be retried.
No functional change intended.
Suggested-by: Vincent Donnefort <vdonnefort@google.com>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/kvm/mmu.c | 37 +++++++++++++++++++++++++++----------
1 file changed, 27 insertions(+), 10 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index ea0d4be0298b..5c429065a4fe 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1659,6 +1659,10 @@ struct kvm_s2_fault_desc {
struct kvm_s2_mmu *mmu;
};
+struct kvm_s2_fault_result {
+ unsigned long mapping_size;
+};
+
static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
{
return esr_fsc_is_permission_fault(s2fd->esr);
@@ -1684,7 +1688,8 @@ static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd)
return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
}
-static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
+static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
+ struct kvm_s2_fault_result *result)
{
bool write_fault, exec_fault;
bool perm_fault = kvm_s2_fault_is_perm(s2fd);
@@ -1784,7 +1789,13 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
if ((prot & KVM_PGTABLE_PROT_W) && !ret)
mark_page_dirty_in_slot(kvm, s2fd->memslot, gfn);
- return ret != -EAGAIN ? ret : 0;
+ if (ret == -EAGAIN)
+ return result ? ret : 0;
+
+ if (result && !ret)
+ result->mapping_size = PAGE_SIZE;
+
+ return ret;
}
struct kvm_s2_fault_vma_info {
@@ -2108,7 +2119,8 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
const struct kvm_s2_fault_vma_info *s2vi,
enum kvm_pgtable_prot prot,
- void *memcache)
+ void *memcache,
+ struct kvm_s2_fault_result *result)
{
enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
struct kvm_guest_s2_mapping *mapping = NULL;
@@ -2209,12 +2221,17 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
mark_page_dirty_in_slot(kvm, s2fd->memslot,
gpa_to_gfn(canonical_ipa));
- if (ret != -EAGAIN)
- return ret;
- return 0;
+ if (ret == -EAGAIN)
+ return result ? ret : 0;
+
+ if (result && !ret)
+ result->mapping_size = mapping_size;
+
+ return ret;
}
-static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
+static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,
+ struct kvm_s2_fault_result *result)
{
bool perm_fault = kvm_s2_fault_is_perm(s2fd);
struct kvm_s2_fault_vma_info s2vi = {};
@@ -2253,7 +2270,7 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
return ret;
}
- return kvm_s2_fault_map(s2fd, &s2vi, prot, memcache);
+ return kvm_s2_fault_map(s2fd, &s2vi, prot, memcache, result);
}
/* Resolve the access fault by making the page young again. */
@@ -2532,9 +2549,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
!kvm_s2_fault_is_exec(&s2fd));
if (kvm_slot_has_gmem(memslot))
- ret = gmem_abort(&s2fd);
+ ret = gmem_abort(&s2fd, NULL);
else
- ret = user_mem_abort(&s2fd);
+ ret = user_mem_abort(&s2fd, NULL);
}
if (ret == 0)
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 07/14] KVM: arm64: Size the stage-2 memcache from the fault MMU
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (5 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 06/14] KVM: arm64: Propagate and use kvm_s2_fault_result on S2 fault Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 08/14] KVM: arm64: Propagate EHWPOISON in kvm_s2_fault_pin_pfn() Lorenzo Stoakes (ARM)
` (7 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
topup_mmu_memcache() sizes the page-table cache from
vcpu->arch.hw_mmu, which is the MMU that faulted.
Pass the target s2fd->mmu instead, so the mmu used can be controlled by the
caller.
For a guest abort this is vcpu->arch.hw_mmu, so this commit introduces no
functional change in practice.
However, it's necessary for a future change which introduces stage 2
pre-faulting which must always target the canonical MMU regardless of the
vCPU's last-run context.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/kvm/mmu.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 5c429065a4fe..221ea069f9bb 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1606,9 +1606,9 @@ static void *get_mmu_memcache(struct kvm_vcpu *vcpu)
return &vcpu->arch.pkvm_memcache;
}
-static int topup_mmu_memcache(struct kvm_vcpu *vcpu, void *memcache)
+static int topup_mmu_memcache(struct kvm_s2_mmu *mmu, void *memcache)
{
- int min_pages = kvm_mmu_cache_min_pages(vcpu->arch.hw_mmu);
+ int min_pages = kvm_mmu_cache_min_pages(mmu);
if (!is_protected_kvm_enabled())
return kvm_mmu_topup_memory_cache(memcache, min_pages);
@@ -1707,7 +1707,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
if (!perm_fault) {
memcache = get_mmu_memcache(s2fd->vcpu);
- ret = topup_mmu_memcache(s2fd->vcpu, memcache);
+ ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
if (kvm_is_nested_s2_mmu(kvm, pgt->mmu)) {
@@ -1825,7 +1825,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
int ret;
hyp_memcache = get_mmu_memcache(vcpu);
- ret = topup_mmu_memcache(vcpu, hyp_memcache);
+ ret = topup_mmu_memcache(s2fd->mmu, hyp_memcache);
if (ret)
return -ENOMEM;
@@ -2251,7 +2251,7 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,
memcache = get_mmu_memcache(s2fd->vcpu);
if (!perm_fault || memslot_is_logging(s2fd->memslot) ||
is_protected_kvm_enabled()) {
- ret = topup_mmu_memcache(s2fd->vcpu, memcache);
+ ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
}
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 08/14] KVM: arm64: Propagate EHWPOISON in kvm_s2_fault_pin_pfn()
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (6 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 07/14] KVM: arm64: Size the stage-2 memcache from the fault MMU Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 09/14] KVM: arm64: Pass walk flags to kvm_pgtable_get_leaf() Lorenzo Stoakes (ARM)
` (6 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
Currently kvm_s2_fault_pin_pfn() handles a poisoned page directly by
sending a SIGBUS signal itself.
This is an odd place to do it, the caller should decide what to do with
errors, so move the handling to the sole caller, user_mem_abort().
This lays the foundation for stage 2 pre-faulting which, arising from a
synthetic fault, should not send a signal.
In order to do so, check to see if user_mem_abort()'s caller has set result
- i.e. whether it wants to be informed about the outcome of the fault
handling.
If it does, then it is implied that it should handle the -EHWPOISON error
itself. This is the case for pre-faulting.
Otherwise this is real hardware, so send the signal.
No functional change intended.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/kvm/mmu.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 221ea069f9bb..2576b967d20e 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2016,10 +2016,8 @@ static int kvm_s2_fault_pin_pfn(const struct kvm_s2_fault_desc *s2fd,
kvm_s2_fault_is_write(s2fd) ? FOLL_WRITE : 0,
&s2vi->map_writable, &s2vi->page);
if (unlikely(is_error_noslot_pfn(s2vi->pfn))) {
- if (s2vi->pfn == KVM_PFN_ERR_HWPOISON) {
- kvm_send_hwpoison_signal(s2fd->hva, __ffs(s2vi->vma_pagesize));
- return 0;
- }
+ if (s2vi->pfn == KVM_PFN_ERR_HWPOISON)
+ return -EHWPOISON;
return -EFAULT;
}
@@ -2261,6 +2259,13 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,
* get block mapping for device MMIO region.
*/
ret = kvm_s2_fault_pin_pfn(s2fd, &s2vi);
+ if (ret == -EHWPOISON) {
+ /* If result is specified, let the caller handle this. */
+ if (result)
+ return -EHWPOISON;
+ kvm_send_hwpoison_signal(s2fd->hva, __ffs(s2vi.vma_pagesize));
+ return 0;
+ }
if (ret != 1)
return ret;
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 09/14] KVM: arm64: Pass walk flags to kvm_pgtable_get_leaf()
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (7 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 08/14] KVM: arm64: Propagate EHWPOISON in kvm_s2_fault_pin_pfn() Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 10/14] KVM: arm64: Implement KVM_PRE_FAULT_MEMORY Lorenzo Stoakes (ARM)
` (5 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
From: Jack Thomson <jackabt@amazon.com>
Allow callers of kvm_pgtable_get_leaf() to specify the page-table walk
flags, in preparation for performing walks under the MMU read lock.
Reading a stage-2 leaf while only holding the read lock requires
KVM_PGTABLE_WALK_SHARED: parallel faults (which also only hold the read
lock) can unlink table pages and free them via RCU, so the walker must
be inside an RCU read-side critical section, which the shared walk flag
provides via kvm_pgtable_walk_begin().
All existing callers either hold the write lock, walk with interrupts
disabled, or run at hyp where shared walks are rejected; they keep the
current behaviour by passing no flags.
No functional change intended.
Signed-off-by: Jack Thomson <jackabt@amazon.com>
[ljs: fixup new kvm_pgtable_get_leaf() invocation]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
arch/arm64/include/asm/kvm_pgtable.h | 5 ++++-
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 10 +++++-----
arch/arm64/kvm/hyp/nvhe/mm.c | 2 +-
arch/arm64/kvm/hyp/pgtable.c | 5 +++--
arch/arm64/kvm/mmu.c | 2 +-
arch/arm64/kvm/nested.c | 2 +-
6 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h
index 20d12da3d28e..589db1d6405f 100644
--- a/arch/arm64/include/asm/kvm_pgtable.h
+++ b/arch/arm64/include/asm/kvm_pgtable.h
@@ -859,6 +859,8 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,
* @addr: Input address for the start of the walk.
* @ptep: Pointer to storage for the retrieved PTE.
* @level: Pointer to storage for the level of the retrieved PTE.
+ * @flags: Flags to control the page-table walk
+ * (see struct kvm_pgtable_visit_ctx).
*
* The offset of @addr within a page is ignored.
*
@@ -869,7 +871,8 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,
* Return: 0 on success, negative error code on failure.
*/
int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,
- kvm_pte_t *ptep, s8 *level);
+ kvm_pte_t *ptep, s8 *level,
+ enum kvm_pgtable_walk_flags flags);
/**
* kvm_pgtable_stage2_pte_prot() - Retrieve the protection attributes of a
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index a6a47c1e058b..443ee433119e 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -540,7 +540,7 @@ static int host_stage2_adjust_range(u64 addr, struct kvm_mem_range *range)
int ret;
hyp_assert_lock_held(&host_mmu.lock);
- ret = kvm_pgtable_get_leaf(&host_mmu.pgt, addr, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, addr, &pte, &level, 0);
if (ret)
return ret;
@@ -930,7 +930,7 @@ static int get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa, kvm_pte_t *ptep,
s8 level;
int ret;
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level, 0);
if (ret)
return ret;
if (guest_pte_is_poisoned(pte))
@@ -979,7 +979,7 @@ int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu)
ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(&hyp_vcpu->vcpu));
guest_lock_component(vm);
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level, 0);
if (ret)
goto unlock;
@@ -1335,7 +1335,7 @@ static int host_stage2_get_guest_info(phys_addr_t phys, struct pkvm_hyp_vm **vm,
return -EPERM;
}
- ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, &level, 0);
if (ret)
return ret;
@@ -1564,7 +1564,7 @@ static int __check_host_shared_guest(struct pkvm_hyp_vm *vm, u64 *__phys, u64 ip
s8 level;
int ret;
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level, 0);
if (ret)
return ret;
if (!kvm_pte_valid(pte))
diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c
index 29ab5ee9d57f..bcf2fd1ddd75 100644
--- a/arch/arm64/kvm/hyp/nvhe/mm.c
+++ b/arch/arm64/kvm/hyp/nvhe/mm.c
@@ -486,7 +486,7 @@ static int check_page_ownership(phys_addr_t phys)
return -EPERM;
}
- ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL);
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL, 0);
if (ret)
return ret;
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index b74dd5ce1efd..347eec3957d6 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -298,12 +298,13 @@ static int leaf_walker(const struct kvm_pgtable_visit_ctx *ctx,
}
int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,
- kvm_pte_t *ptep, s8 *level)
+ kvm_pte_t *ptep, s8 *level,
+ enum kvm_pgtable_walk_flags flags)
{
struct leaf_walk_data data;
struct kvm_pgtable_walker walker = {
.cb = leaf_walker,
- .flags = KVM_PGTABLE_WALK_LEAF,
+ .flags = flags | KVM_PGTABLE_WALK_LEAF,
.arg = &data,
};
int ret;
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 2576b967d20e..6a85024eab69 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -895,7 +895,7 @@ static int get_user_mapping_size(struct kvm *kvm, u64 addr)
* IPI-ing threads).
*/
local_irq_save(flags);
- ret = kvm_pgtable_get_leaf(&pgt, addr, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&pgt, addr, &pte, &level, 0);
local_irq_restore(flags);
if (ret)
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index ec754a865a00..cd868af63b1b 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -662,7 +662,7 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)
return 0;
tmp &= ~(sz - 1);
- if (kvm_pgtable_get_leaf(mmu->pgt, tmp, &pte, NULL))
+ if (kvm_pgtable_get_leaf(mmu->pgt, tmp, &pte, NULL, 0))
goto again;
if (!(pte & PTE_VALID))
goto again;
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 10/14] KVM: arm64: Implement KVM_PRE_FAULT_MEMORY
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (8 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 09/14] KVM: arm64: Pass walk flags to kvm_pgtable_get_leaf() Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 11/14] Documentation: KVM: document arm64 KVM_PRE_FAULT_MEMORY Lorenzo Stoakes (ARM)
` (4 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
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.
Additionally, in pKVM mode, the stage 2 page tables are owned by the
hypervisor rather than the host - the host can neither walk them nor
populate them directly, so it's not clear that the pre-fault mechanism
correctly maps onto pKVM.
Disallow pre-faulting of uninitialised vCPUs as they are not in a
safe state to do so, using the newly introduced
kvm_arch_vcpu_allow_pre_fault_memory() hook.
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 | 147 ++++++++++++++++++++++++++++++++++++++
4 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index cad60569f061..c1831c8e421d 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 31f803010c57..367638e1e020 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -410,6 +410,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 6a85024eab69..98c8ca0faf79 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/interval_tree.h>
@@ -2933,3 +2934,149 @@ 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
+
+bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
+{
+ /* Only initialised vCPUs can pre-fault. */
+ return kvm_vcpu_initialized(vcpu);
+}
+
+/**
+ * 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
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 11/14] Documentation: KVM: document arm64 KVM_PRE_FAULT_MEMORY
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (9 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 10/14] KVM: arm64: Implement KVM_PRE_FAULT_MEMORY Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 12/14] KVM: selftests: Enable pre_fault_memory_test for arm64 Lorenzo Stoakes (ARM)
` (3 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
Update the KVM API documentation to reflect the fact that arm64 now
supports this functionality.
Also document error values and arm64-specific behaviour.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
Documentation/virt/kvm/api.rst | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 1a90598901c5..71b642148b7f 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6489,7 +6489,7 @@ See KVM_SET_USER_MEMORY_REGION2 for additional details.
---------------------------
:Capability: KVM_CAP_PRE_FAULT_MEMORY
-:Architectures: none
+:Architectures: x86, s390, arm64
:Type: vcpu ioctl
:Parameters: struct kvm_pre_fault_memory (in/out)
:Returns: 0 if at least one page is processed, < 0 on error
@@ -6497,12 +6497,15 @@ See KVM_SET_USER_MEMORY_REGION2 for additional details.
Errors:
========== ===============================================================
+ EAGAIN A race occurred before progress was made, but a retry may succeed.
EINVAL The specified `gpa` and `size` were invalid (e.g. not
page aligned, causes an overflow, or size is zero), or the VM
is UCONTROL (s390).
ENOENT The specified `gpa` is outside defined memslots.
+ ENOEXEC The vCPU has not been initialised (arm64).
EINTR An unmasked signal is pending and no page was processed.
EFAULT The parameter address was invalid.
+ EHWPOISON A poisoned host page was encountered.
EOPNOTSUPP Mapping memory for a GPA is unsupported by the
hypervisor, and/or for the current vCPU state/mode.
EIO unexpected error conditions (also causes a WARN)
@@ -6522,7 +6525,17 @@ Errors:
KVM_PRE_FAULT_MEMORY populates KVM's stage-2 page tables used to map memory
for the current vCPU state. KVM maps memory as if the vCPU generated a
stage-2 read page fault, e.g. faults in memory as needed, but doesn't break
-CoW. On x86, KVM does not mark any newly created stage-2 PTE as Accessed.
+CoW. On arm64, KVM marks newly created stage-2 PTEs as Accessed, as it
+does for any stage-2 fault, but leaves the Accessed state of existing PTEs
+unchanged. On x86, KVM does not mark any newly created stage-2 PTE as
+Accessed, and for s390 it is not applicable.
+
+On arm64, a GPA is interpreted as an IPA, and never interpreted as the IPA
+of a nested guest. Pre-faulting only populates canonical stage-2 page
+tables.
+
+The feature is not supported on arm64 if the protected KVM (pKVM) feature
+is enabled.
In the case of confidential VM types where there is an initial set up of
private guest memory before the guest is 'finalized'/measured, this ioctl
@@ -6537,7 +6550,7 @@ When the ioctl returns, the input values are updated to point to the
remaining range. If `size` > 0 on return, the caller can just issue
the ioctl again with the same `struct kvm_map_memory` argument.
-Shadow page tables cannot support this ioctl because they
+On x86, shadow page tables cannot support this ioctl because they
are indexed by virtual address or nested guest physical address.
Calling this ioctl when the guest is using shadow page tables (for
example because it is running a nested guest with nested page tables)
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 12/14] KVM: selftests: Enable pre_fault_memory_test for arm64
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (10 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 11/14] Documentation: KVM: document arm64 KVM_PRE_FAULT_MEMORY Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 13/14] KVM: selftests: Add option for different backing in pre-fault tests Lorenzo Stoakes (ARM)
` (2 subsequent siblings)
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
From: Jack Thomson <jackabt@amazon.com>
Enable the pre_fault_memory_test to run on arm64 by making it work with
different guest page sizes and testing multiple guest configurations.
Add an assert on the exit reason, comparing against UCALL_EXIT_REASON so
the check is portable, as arm64 exits with KVM_EXIT_MMIO while x86 uses
KVM_EXIT_IO.
Signed-off-by: Jack Thomson <jackabt@amazon.com>
[ljs: merge conflict resolution with commit c847704619da, commit message
accuracy fixup]
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../testing/selftests/kvm/pre_fault_memory_test.c | 117 +++++++++++++++++----
2 files changed, 96 insertions(+), 22 deletions(-)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 6a1482e3a286..874ff9097384 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -205,6 +205,7 @@ TEST_GEN_PROGS_arm64 += guest_memfd_test
TEST_GEN_PROGS_arm64 += mmu_stress_test
TEST_GEN_PROGS_arm64 += rseq_test
TEST_GEN_PROGS_arm64 += steal_time
+TEST_GEN_PROGS_arm64 += pre_fault_memory_test
TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_s390 += s390/memop
diff --git a/tools/testing/selftests/kvm/pre_fault_memory_test.c b/tools/testing/selftests/kvm/pre_fault_memory_test.c
index c57631aab3d3..a5993f8e1feb 100644
--- a/tools/testing/selftests/kvm/pre_fault_memory_test.c
+++ b/tools/testing/selftests/kvm/pre_fault_memory_test.c
@@ -12,19 +12,29 @@
#include <processor.h>
#include <pthread.h>
#include <ucall_common.h>
+#include <guest_modes.h>
/* Arbitrarily chosen values */
-#define TEST_SIZE (SZ_2M + PAGE_SIZE)
-#define TEST_NPAGES (TEST_SIZE / PAGE_SIZE)
+#define TEST_BASE_SIZE SZ_2M
#define TEST_SLOT 10
+/* Storage of test info to share with guest code */
+struct test_config {
+ u64 page_size;
+ u64 test_size;
+ u64 test_num_pages;
+};
+
+static struct test_config test_config;
+
static void guest_code(u64 base_gva)
{
volatile u64 val __used;
+ struct test_config *config = &test_config;
int i;
- for (i = 0; i < TEST_NPAGES; i++) {
- u64 *src = (u64 *)(base_gva + i * PAGE_SIZE);
+ for (i = 0; i < config->test_num_pages; i++) {
+ u64 *src = (u64 *)(base_gva + i * config->page_size);
val = *src;
}
@@ -57,7 +67,7 @@ static void *delete_slot_worker(void *__data)
cpu_relax();
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, data->gpa,
- TEST_SLOT, TEST_NPAGES, data->flags);
+ TEST_SLOT, test_config.test_num_pages, data->flags);
return NULL;
}
@@ -150,8 +160,8 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
/*
* Assert success if prefaulting the entire range should succeed, i.e.
* complete with no bytes remaining. Otherwise prefaulting should have
- * failed due to ENOENT (due to RET_PF_EMULATE for emulated MMIO when
- * no memslot exists).
+ * failed due to ENOENT (no memslot exists for the GPA; on x86 this
+ * surfaces via RET_PF_EMULATE).
*/
if (!expected_left)
TEST_ASSERT_VM_VCPU_IOCTL(!ret, KVM_PRE_FAULT_MEMORY, ret, vcpu->vm);
@@ -160,39 +170,72 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
KVM_PRE_FAULT_MEMORY, ret, vcpu->vm);
}
-static void __test_pre_fault_memory(unsigned long vm_type, bool private)
+struct test_params {
+ unsigned long vm_type;
+ bool private;
+};
+
+static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
{
- gpa_t gpa, gva, alignment, guest_page_size;
+ gpa_t gpa, gva, alignment, guest_page_size, host_page_size;
+ struct test_params *p = arg;
const struct vm_shape shape = {
- .mode = VM_MODE_DEFAULT,
- .type = vm_type,
+ .mode = guest_mode,
+ .type = p->vm_type,
};
struct kvm_vcpu *vcpu;
+ struct kvm_run *run;
struct kvm_vm *vm;
struct ucall uc;
+ pr_info("Testing guest mode: %s\n", vm_guest_mode_string(guest_mode));
+
vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code);
- alignment = guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
- gpa = (vm->max_gfn - TEST_NPAGES) * guest_page_size;
+ guest_page_size = vm_guest_mode_params[guest_mode].page_size;
+ host_page_size = getpagesize();
+
+ test_config.page_size = guest_page_size;
+ test_config.test_size = align_up(TEST_BASE_SIZE + test_config.page_size,
+ host_page_size);
+ test_config.test_num_pages = vm_calc_num_guest_pages(vm->mode, test_config.test_size);
+
+ gpa = (vm->max_gfn - test_config.test_num_pages) * test_config.page_size;
alignment = SZ_2M;
+ alignment = max(alignment, host_page_size);
gpa = align_down(gpa, alignment);
gva = gpa & ((1ULL << (vm->va_bits - 1)) - 1);
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa, TEST_SLOT,
- TEST_NPAGES, private ? KVM_MEM_GUEST_MEMFD : 0);
- virt_map(vm, gva, gpa, TEST_NPAGES);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ gpa, TEST_SLOT, test_config.test_num_pages,
+ p->private ? KVM_MEM_GUEST_MEMFD : 0);
+ virt_map(vm, gva, gpa, test_config.test_num_pages);
- if (private)
- vm_mem_set_private(vm, gpa, TEST_SIZE);
+ if (p->private)
+ vm_mem_set_private(vm, gpa, test_config.test_size);
- pre_fault_memory(vcpu, gpa, 0, SZ_2M, 0, private);
- pre_fault_memory(vcpu, gpa, SZ_2M, PAGE_SIZE * 2, PAGE_SIZE, private);
- pre_fault_memory(vcpu, gpa, TEST_SIZE, PAGE_SIZE, PAGE_SIZE, private);
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0, p->private);
+ /* Retry the same range after the first prefault attempt. */
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0, p->private);
+ pre_fault_memory(vcpu, gpa,
+ test_config.test_size - host_page_size,
+ host_page_size * 2, host_page_size, p->private);
+ pre_fault_memory(vcpu, gpa, test_config.test_size,
+ host_page_size, host_page_size, p->private);
vcpu_args_set(vcpu, 1, gva);
+
+ /* Export the shared variables to the guest. */
+ sync_global_to_guest(vm, test_config);
+
vcpu_run(vcpu);
+ run = vcpu->run;
+ TEST_ASSERT(run->exit_reason == UCALL_EXIT_REASON,
+ "Wanted %s, got exit reason: %u (%s)",
+ exit_reason_str(UCALL_EXIT_REASON),
+ run->exit_reason, exit_reason_str(run->exit_reason));
+
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
@@ -209,16 +252,46 @@ static void __test_pre_fault_memory(unsigned long vm_type, bool private)
static void test_pre_fault_memory(unsigned long vm_type, bool private)
{
+ struct test_params p = {
+ .vm_type = vm_type,
+ .private = private,
+ };
+
if (vm_type && !(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(vm_type))) {
pr_info("Skipping tests for vm_type 0x%lx\n", vm_type);
return;
}
- __test_pre_fault_memory(vm_type, private);
+ for_each_guest_mode(__test_pre_fault_memory, &p);
+}
+
+static void help(char *name)
+{
+ puts("");
+ printf("usage: %s [-h] [-m mode]\n", name);
+ puts("");
+ guest_modes_help();
+ puts("");
}
int main(int argc, char *argv[])
{
+ int opt;
+
+ guest_modes_append_default();
+
+ while ((opt = getopt(argc, argv, "hm:")) != -1) {
+ switch (opt) {
+ case 'm':
+ guest_modes_cmdline(optarg);
+ break;
+ case 'h':
+ default:
+ help(argv[0]);
+ exit(0);
+ }
+ }
+
TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
test_pre_fault_memory(0, false);
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 13/14] KVM: selftests: Add option for different backing in pre-fault tests
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (11 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 12/14] KVM: selftests: Enable pre_fault_memory_test for arm64 Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 14:18 ` [PATCH v3 14/14] KVM: selftests: Add nested pre-fault test for arm64 Lorenzo Stoakes (ARM)
2026-09-22 20:42 ` [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Oliver Upton
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
From: Jack Thomson <jackabt@amazon.com>
Add a -s option to specify different memory backing types for the
pre-fault tests (e.g. anonymous, hugetlb), allowing testing of the
pre-fault functionality across different memory configurations.
Signed-off-by: Jack Thomson <jackabt@amazon.com>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
.../testing/selftests/kvm/pre_fault_memory_test.c | 51 +++++++++++++++-------
1 file changed, 36 insertions(+), 15 deletions(-)
diff --git a/tools/testing/selftests/kvm/pre_fault_memory_test.c b/tools/testing/selftests/kvm/pre_fault_memory_test.c
index a5993f8e1feb..3082fe09b95f 100644
--- a/tools/testing/selftests/kvm/pre_fault_memory_test.c
+++ b/tools/testing/selftests/kvm/pre_fault_memory_test.c
@@ -46,6 +46,7 @@ struct slot_worker_data {
struct kvm_vm *vm;
gpa_t gpa;
u32 flags;
+ enum vm_mem_backing_src_type mem_backing_src;
bool worker_ready;
bool prefault_ready;
bool recreate_slot;
@@ -66,14 +67,16 @@ static void *delete_slot_worker(void *__data)
while (!READ_ONCE(data->recreate_slot))
cpu_relax();
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, data->gpa,
+ vm_userspace_mem_region_add(vm, data->mem_backing_src, data->gpa,
TEST_SLOT, test_config.test_num_pages, data->flags);
return NULL;
}
static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
- u64 size, u64 expected_left, bool private)
+ u64 size, u64 expected_left,
+ enum vm_mem_backing_src_type mem_backing_src,
+ bool private)
{
struct kvm_pre_fault_memory range = {
.gpa = base_gpa + offset,
@@ -84,6 +87,7 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
.vm = vcpu->vm,
.gpa = base_gpa,
.flags = private ? KVM_MEM_GUEST_MEMFD : 0,
+ .mem_backing_src = mem_backing_src,
};
bool slot_recreated = false;
pthread_t slot_worker;
@@ -173,11 +177,13 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
struct test_params {
unsigned long vm_type;
bool private;
+ enum vm_mem_backing_src_type mem_backing_src;
};
static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
{
gpa_t gpa, gva, alignment, guest_page_size, host_page_size;
+ gpa_t backing_src_pagesz, mem_page_size;
struct test_params *p = arg;
const struct vm_shape shape = {
.mode = guest_mode,
@@ -189,24 +195,28 @@ static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
struct ucall uc;
pr_info("Testing guest mode: %s\n", vm_guest_mode_string(guest_mode));
+ pr_info("Testing memory backing src type: %s\n",
+ vm_mem_backing_src_alias(p->mem_backing_src)->name);
vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code);
guest_page_size = vm_guest_mode_params[guest_mode].page_size;
host_page_size = getpagesize();
+ backing_src_pagesz = get_backing_src_pagesz(p->mem_backing_src);
+ mem_page_size = max(host_page_size, backing_src_pagesz);
test_config.page_size = guest_page_size;
test_config.test_size = align_up(TEST_BASE_SIZE + test_config.page_size,
- host_page_size);
+ mem_page_size);
test_config.test_num_pages = vm_calc_num_guest_pages(vm->mode, test_config.test_size);
gpa = (vm->max_gfn - test_config.test_num_pages) * test_config.page_size;
alignment = SZ_2M;
- alignment = max(alignment, host_page_size);
+ alignment = max(alignment, mem_page_size);
gpa = align_down(gpa, alignment);
gva = gpa & ((1ULL << (vm->va_bits - 1)) - 1);
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ vm_userspace_mem_region_add(vm, p->mem_backing_src,
gpa, TEST_SLOT, test_config.test_num_pages,
p->private ? KVM_MEM_GUEST_MEMFD : 0);
virt_map(vm, gva, gpa, test_config.test_num_pages);
@@ -214,14 +224,18 @@ static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
if (p->private)
vm_mem_set_private(vm, gpa, test_config.test_size);
- pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0, p->private);
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,
+ p->mem_backing_src, p->private);
/* Retry the same range after the first prefault attempt. */
- pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0, p->private);
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,
+ p->mem_backing_src, p->private);
pre_fault_memory(vcpu, gpa,
test_config.test_size - host_page_size,
- host_page_size * 2, host_page_size, p->private);
+ host_page_size * 2, host_page_size,
+ p->mem_backing_src, p->private);
pre_fault_memory(vcpu, gpa, test_config.test_size,
- host_page_size, host_page_size, p->private);
+ host_page_size, host_page_size,
+ p->mem_backing_src, p->private);
vcpu_args_set(vcpu, 1, gva);
@@ -250,11 +264,13 @@ static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
kvm_vm_free(vm);
}
-static void test_pre_fault_memory(unsigned long vm_type, bool private)
+static void test_pre_fault_memory(unsigned long vm_type, enum vm_mem_backing_src_type backing_src,
+ bool private)
{
struct test_params p = {
.vm_type = vm_type,
.private = private,
+ .mem_backing_src = backing_src,
};
if (vm_type && !(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(vm_type))) {
@@ -268,23 +284,28 @@ static void test_pre_fault_memory(unsigned long vm_type, bool private)
static void help(char *name)
{
puts("");
- printf("usage: %s [-h] [-m mode]\n", name);
+ printf("usage: %s [-h] [-m mode] [-s mem-type]\n", name);
puts("");
guest_modes_help();
+ backing_src_help("-s");
puts("");
}
int main(int argc, char *argv[])
{
+ enum vm_mem_backing_src_type backing = DEFAULT_VM_MEM_SRC;
int opt;
guest_modes_append_default();
- while ((opt = getopt(argc, argv, "hm:")) != -1) {
+ while ((opt = getopt(argc, argv, "hm:s:")) != -1) {
switch (opt) {
case 'm':
guest_modes_cmdline(optarg);
break;
+ case 's':
+ backing = parse_backing_src_type(optarg);
+ break;
case 'h':
default:
help(argv[0]);
@@ -294,10 +315,10 @@ int main(int argc, char *argv[])
TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
- test_pre_fault_memory(0, false);
+ test_pre_fault_memory(0, backing, false);
#ifdef __x86_64__
- test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, false);
- test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, true);
+ test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, false);
+ test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, true);
#endif
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v3 14/14] KVM: selftests: Add nested pre-fault test for arm64
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (12 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 13/14] KVM: selftests: Add option for different backing in pre-fault tests Lorenzo Stoakes (ARM)
@ 2026-09-22 14:18 ` Lorenzo Stoakes (ARM)
2026-09-22 20:42 ` [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Oliver Upton
14 siblings, 0 replies; 31+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-09-22 14:18 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Marc Zyngier, Oliver Upton,
Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Paolo Bonzini, Jonathan Corbet, Mark Rutland, Fuad Tabba,
Randy Dunlap, Fuad Tabba
Cc: linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang,
Lorenzo Stoakes (ARM)
Add an arm64 nested-virtualisation selftest for KVM_PRE_FAULT_MEMORY.
The guest enters vEL2, sets up its EL2 translation configuration into the
real EL1 registers then ERETs to vEL1 and exits to userspace so its vCPU
last-run context is nested backed by a shadow stage 2 MMU.
The test then asserts that pre-faulting ignores that and instead targets
the canonical stage 2 page tables only.
Userspace cannot observe a mistargeted pre-fault, so simply assert that the
pre-faulting works correctly when the last-run context was nested.
Based on a patch by Jack Thomson with gratitude. The test here is
simplified and focused on what can be asserted.
Link: https://lore.kernel.org/kvmarm/20260612162354.73378-6-jackabt.amazon@gmail.com/
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/arm64/nv_pre_fault_memory_test.c | 158 +++++++++++++++++++++
2 files changed, 159 insertions(+)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 874ff9097384..908bdc7cf4f5 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -178,6 +178,7 @@ TEST_GEN_PROGS_arm64 += arm64/debug-exceptions
TEST_GEN_PROGS_arm64 += arm64/hello_el2
TEST_GEN_PROGS_arm64 += arm64/host_sve
TEST_GEN_PROGS_arm64 += arm64/hypercalls
+TEST_GEN_PROGS_arm64 += arm64/nv_pre_fault_memory_test
TEST_GEN_PROGS_arm64 += arm64/external_aborts
TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
diff --git a/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c
new file mode 100644
index 000000000000..09c1db303856
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nv_pre_fault_memory_test - Test KVM_PRE_FAULT_MEMORY on a vCPU whose
+ * last-run context is nested.
+ *
+ * The guest enters vEL2, sets up its EL2 translation configuration into the
+ * real EL1 registers then ERETs to vEL1 and exits to userspace so its vCPU
+ * last-run context is nested backed by a shadow stage 2 MMU.
+ *
+ * Assert that pre-faulting ignores that and targets the canonical stage-2
+ * page tables only.
+ */
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#include <asm/sysreg.h>
+#include <linux/sizes.h>
+
+#define TEST_MEM_SLOT 10
+#define TEST_MEM_SIZE SZ_2M
+#define TEST_MEM_GPA SZ_1G
+
+static void guest_el1_code(void)
+{
+ u64 offset;
+
+ GUEST_ASSERT_EQ(get_current_el(), 1);
+
+ /* Exit to userspace with the vEL1 (nested) context live. */
+ GUEST_SYNC(1);
+
+ /*
+ * Touch the prefaulted range. vstage-2 is disabled, so the shadow
+ * stage-2 is a 1:1 view of the canonical IPA space.
+ */
+ for (offset = 0; offset < TEST_MEM_SIZE; offset += SZ_4K)
+ READ_ONCE(*(u64 *)(TEST_MEM_GPA + offset));
+
+ GUEST_DONE();
+}
+
+static void guest_code(void)
+{
+ u64 sp;
+
+ GUEST_ASSERT_EQ(get_current_el(), 2);
+
+ /*
+ * Mirror the EL2 translation regime into the real EL1 registers so
+ * that vEL1 runs on the test's stage-1 page tables. With E2H=1, the
+ * _EL1 accessors read the EL2 registers, and the _EL12 accessors
+ * write the real EL1 registers.
+ */
+ write_sysreg_s(read_sysreg(sctlr_el1), SYS_SCTLR_EL12);
+ write_sysreg_s(read_sysreg(tcr_el1), SYS_TCR_EL12);
+ write_sysreg_s(read_sysreg(ttbr0_el1), SYS_TTBR0_EL12);
+ write_sysreg_s(read_sysreg(mair_el1), SYS_MAIR_EL12);
+ write_sysreg_s(read_sysreg(cpacr_el1), SYS_CPACR_EL12);
+
+ /* Run vEL1 on the same stack. */
+ asm volatile("mov %0, sp" : "=r"(sp));
+ write_sysreg(sp, sp_el1);
+
+ /*
+ * Drop TGE so that vEL1 is a nested context rather than host EL0.
+ * KVM backs it with a shadow stage-2 MMU even though vstage-2 is
+ * disabled (HCR_EL2.VM=0).
+ */
+ write_sysreg(read_sysreg(hcr_el2) & ~HCR_EL2_TGE, hcr_el2);
+ isb();
+
+ write_sysreg(PSR_MODE_EL1h | PSR_F_BIT | PSR_I_BIT | PSR_A_BIT |
+ PSR_D_BIT, spsr_el2);
+ write_sysreg((u64)guest_el1_code, elr_el2);
+ asm volatile("eret");
+
+ GUEST_ASSERT(false);
+}
+
+static void pre_fault(struct kvm_vcpu *vcpu, u64 gpa, u64 size)
+{
+ struct kvm_pre_fault_memory range = {
+ .gpa = gpa,
+ .size = size,
+ };
+ int ret;
+
+ do {
+ ret = __vcpu_ioctl(vcpu, KVM_PRE_FAULT_MEMORY, &range);
+ } while ((!ret && range.size) ||
+ (ret < 0 && (errno == EINTR || errno == EAGAIN)));
+
+ TEST_ASSERT(!ret, "KVM_PRE_FAULT_MEMORY failed, ret: %d errno: %d",
+ ret, errno);
+ TEST_ASSERT_EQ(range.size, 0);
+}
+
+int main(void)
+{
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ u64 npages;
+
+ TEST_REQUIRE(test_supports_el2());
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
+
+ vm = vm_create(1);
+
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ npages = TEST_MEM_SIZE / vm->page_size;
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, TEST_MEM_GPA,
+ TEST_MEM_SLOT, npages, 0);
+ virt_map(vm, TEST_MEM_GPA, TEST_MEM_GPA, npages);
+
+ /* Run the guest until it has ERET'd from vEL2 to vEL1. */
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ TEST_ASSERT_EQ(uc.args[1], 1);
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+ }
+
+ /*
+ * The vCPU's last-run context is vEL1, so its hw_mmu is a shadow
+ * stage-2 MMU.
+ *
+ * Pre-faulting must ignore that and populate the canonical stage-2.
+ */
+ pre_fault(vcpu, TEST_MEM_GPA, TEST_MEM_SIZE);
+
+ /* Resume at vEL1 and touch the prefaulted range. */
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+ }
+
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.55.0
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support
2026-09-22 14:17 [PATCH v3 00/14] KVM: arm64: Add KVM_PRE_FAULT_MEMORY support Lorenzo Stoakes (ARM)
` (13 preceding siblings ...)
2026-09-22 14:18 ` [PATCH v3 14/14] KVM: selftests: Add nested pre-fault test for arm64 Lorenzo Stoakes (ARM)
@ 2026-09-22 20:42 ` Oliver Upton
14 siblings, 0 replies; 31+ messages in thread
From: Oliver Upton @ 2026-09-22 20:42 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Catalin Marinas, Will Deacon, Marc Zyngier, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Jonathan Corbet, Mark Rutland, Fuad Tabba, Randy Dunlap,
linux-arm-kernel, linux-kernel, kvmarm, kvm, linux-doc,
linux-kselftest, Jack Thomson, Jack Thomson, Alexandru Elisei,
Vincent Donnefort, Aneesh Kumar K.V, Sean Christopherson,
Claudio Imbrenda, Leo Soares Passos, Wei-Lin Chang
On Tue, Sep 22, 2026 at 03:17:54PM +0100, Lorenzo Stoakes (ARM) wrote:
> ---
> Jack Thomson (3):
> KVM: arm64: Pass walk flags to kvm_pgtable_get_leaf()
> KVM: selftests: Enable pre_fault_memory_test for arm64
> KVM: selftests: Add option for different backing in pre-fault tests
>
> Lorenzo Stoakes (ARM) (11):
> KVM: Allow architectures to disallow pre-fault
> arm64: Add ESR fault helpers
> KVM: arm64: Use ESR helpers in guest abort handling
> KVM: arm64: Propagate and use esr in s2fd when handling guest aborts
> KVM: arm64: Propagate and use mmu in s2fd when handling guest aborts
> KVM: arm64: Propagate and use kvm_s2_fault_result on S2 fault
> KVM: arm64: Size the stage-2 memcache from the fault MMU
> KVM: arm64: Propagate EHWPOISON in kvm_s2_fault_pin_pfn()
> KVM: arm64: Implement KVM_PRE_FAULT_MEMORY
> Documentation: KVM: document arm64 KVM_PRE_FAULT_MEMORY
> KVM: selftests: Add nested pre-fault test for arm64
Besides the usual nitpicking, this series is looking good to me. Thank
you for picking this up Lorenzo!
Reviewed-by: Oliver Upton <oupton@kernel.org>
Thanks,
Oliver
^ permalink raw reply [flat|nested] 31+ messages in thread