* [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu
@ 2026-06-03 10:57 Paolo Bonzini
2026-06-03 10:57 ` [PATCH 01/24] KVM: x86: remove nested_mmu from mmu_is_nested() Paolo Bonzini
` (23 more replies)
0 siblings, 24 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
The RFC for this series only had minor comments on the naming;
probably, everybody liked the idea of doing it and nobody liked
the idea of reviewing it front to back. So here it is without the
RFC tag.
The kvm_mmu is a "god data structure" that includes three different
tasks: describing the guest page table's format, walking the guest
page tables and building the page tables. This means that the
(already poorly named) nested_mmu is only used in part, since it
has no page tables to construct.
Furthermore, some parts are reused across guest and host page
tables (such as the reserved bits detector) but others are not;
for example permission_fault is replaced by simplified code such as
is_executable_pte().
This series cleans this up by splitting kvm_mmu in three parts:
- kvm_pagewalk is the page table walker. There are two of them
per vCPU, gva_walk and ngpa_walk. walk_mmu is *always* replaced
by a single gva_walk no matter if running an L1 or L2 guest,
unlike in the current code that moves it between root_mmu and
nested_mmu.
- kvm_mmu retains the page table building functionality. It uses
a page table walker to build shadow pages; that is always gva_walk
for root_mmu or ngpa_walk for guest_mmu.
- kvm_page_format allows KVM to operate on PTEs that already exist,
and merges the code around permission_mask() with the pre-existing
struct rsvd_bits_validate. Both kvm_pagewalk and kvm_mmu have their
own kvm_page_format, just like struct kvm_mmu had two instances of
struct rsvd_bits_validate for gPTE and SPTE reserved bit checks.
The cleanup alone already does something useful, which is to reduce
the confusion between guest_mmu and nested_mmu. nested_mmu came to
exist long before the introduction of guest_mmu and stole the obvious
name, resulting in comments like "Exempt nested MMUs" where the code
actually exempts guest_mmu. Renaming guest_mmu could be the next
step, though the RFC had multiple opinions about how to do this.
However, the last patch also shows the code reuse benefits can be used
for new features too. By adapting the permission_fault() machinery and
using it to test SPTEs against struct kvm_page_fault, it makes it possible
to support SPTEs that have XS!=XU; these were not supported yet by KVM,
but could now be added via memory attributes.
The first five patches are a repost of
https://lore.kernel.org/kvm/20260530165545.25599-1-pbonzini@redhat.com/T/
because sashiko does not yet support Based-on tags or similar.
Paolo
RFC->v1:
- replaced first four patches with "KVM: x86: small MMU-adjacent cleanups"
- renamed cpu_walk and tdp_walk to gva_walk and ngpa_walk (as well as
the temporary nested_cpu_walk to ngva_walk)
Paolo Bonzini (24):
KVM: x86: remove nested_mmu from mmu_is_nested()
KVM: nVMX: remove unnecessary code in prepare_vmcs02_rare
KVM: nSVM: invalidate cached PDPTRs across nested NPT transitions
KVM: x86: check that kvm_handle_invpcid is only invoked with shadow paging
KVM: x86/mmu: move pdptrs out of the MMU
KVM: x86/hyperv: remove unnecessary mmu_is_nested() check
KVM: x86/mmu: introduce struct kvm_pagewalk
KVM: x86/mmu: move get_guest_pgd to struct kvm_pagewalk
KVM: x86/mmu: move gva_to_gpa to struct kvm_pagewalk
KVM: x86/mmu: move get_pdptr to struct kvm_pagewalk
KVM: x86/mmu: move inject_page_fault to struct kvm_pagewalk
KVM: x86/mmu: move CPU-related fields to struct kvm_pagewalk
KVM: x86/mmu: change CPU-role accessor fields to take struct kvm_pagewalk
KVM: x86/mmu: move remaining permission fields to struct kvm_pagewalk
KVM: x86/mmu: pass struct kvm_pagewalk to kvm_mmu_invalidate_addr
KVM: x86/mmu: change walk_mmu to struct kvm_pagewalk
KVM: x86/mmu: change nested_mmu.w to ngva_walk
KVM: x86/mmu: make gva_walk a value
KVM: x86/mmu: pull struct kvm_pagewalk out of struct kvm_mmu
KVM: x86/mmu: cleanup functions that initialize shadow MMU
KVM: x86/mmu: pull page format to a new struct
KVM: x86/mmu: merge struct rsvd_bits_validate into struct
kvm_page_format
KVM: x86/mmu: parameterize update_permission_bitmask()
KVM: x86/mmu: use kvm_page_format to test SPTEs
arch/x86/include/asm/kvm_host.h | 75 +++---
arch/x86/kvm/hyperv.c | 7 +-
arch/x86/kvm/kvm_cache_regs.h | 12 +-
arch/x86/kvm/mmu.h | 31 +--
arch/x86/kvm/mmu/mmu.c | 411 +++++++++++++++-----------------
arch/x86/kvm/mmu/paging_tmpl.h | 88 +++----
arch/x86/kvm/mmu/spte.c | 4 +-
arch/x86/kvm/mmu/spte.h | 64 ++---
arch/x86/kvm/mmu/tdp_mmu.c | 3 +-
arch/x86/kvm/svm/nested.c | 40 ++--
arch/x86/kvm/svm/svm.c | 2 +-
arch/x86/kvm/vmx/nested.c | 26 +-
arch/x86/kvm/vmx/vmx.c | 22 +-
arch/x86/kvm/x86.c | 67 +++---
arch/x86/kvm/x86.h | 2 +-
15 files changed, 422 insertions(+), 432 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH 01/24] KVM: x86: remove nested_mmu from mmu_is_nested()
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 02/24] KVM: nVMX: remove unnecessary code in prepare_vmcs02_rare Paolo Bonzini
` (22 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
nested_mmu is always stored into vcpu->arch.walk_mmu at the same time as
guest_mmu is stored into vcpu->arch.mmu. But nested_mmu is not even
a proper MMU, it is only used for page walking; plus the fact that
walk_mmu has to be switched at all is just an implementation detail.
In the end what matters here is whether the guest is using nested
page tables; vmx/nested.c and svm/nested.c check it to see if they
are in nEPT or nNPT context respectively. So switch to checking
root_mmu vs. guest_mmu, which is a more cogent test.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Message-ID: <20260511150648.685374-2-pbonzini@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/x86.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 38a905fa86de..60ff064de12f 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -290,7 +290,7 @@ static inline bool x86_exception_has_error_code(unsigned int vector)
static inline bool mmu_is_nested(struct kvm_vcpu *vcpu)
{
- return vcpu->arch.walk_mmu == &vcpu->arch.nested_mmu;
+ return vcpu->arch.mmu == &vcpu->arch.guest_mmu;
}
static inline bool is_pae(struct kvm_vcpu *vcpu)
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 02/24] KVM: nVMX: remove unnecessary code in prepare_vmcs02_rare
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
2026-06-03 10:57 ` [PATCH 01/24] KVM: x86: remove nested_mmu from mmu_is_nested() Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 03/24] KVM: nSVM: invalidate cached PDPTRs across nested NPT transitions Paolo Bonzini
` (21 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm; +Cc: Sean Christopherson
The early vmwrite of the PDPTRs in prepare_vmcs02_rare() is redundant, because
every write it does will be performed by prepare_vmcs02() if it is actually
needed.
In any case where the emulator or the processor need the PDPTR, either
is_pae_paging() is true on vmentry, or a write of CR0, CR4 or EFER will
cause a vmexit to L0. The next vmentry will refresh the PDPTRs in the
vmcs02 from vmcs12.
In fact, the original version[1] of what ended up being commit
c7554efc8335 ("KVM: nVMX: Copy PDPTRs to/from vmcs12 only when
necessary"), the writes in what is now prepare_vmcs02_rare() were removed.
When the mega-collection of optimizations was posted[2], the removal of
that code got dropped as a rebase good, so reinstate it.
[1] https://lore.kernel.org/all/20190507160640.4812-16-sean.j.christopherson@intel.com
[2] https://lore.kernel.org/all/1560445409-17363-31-git-send-email-pbonzini@redhat.com
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/vmx/nested.c | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 4690a4d23709..1bd0839146fd 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -2623,17 +2623,6 @@ static void prepare_vmcs02_rare(struct vcpu_vmx *vmx, struct vmcs12 *vmcs12)
vmcs_writel(GUEST_SYSENTER_ESP, vmcs12->guest_sysenter_esp);
vmcs_writel(GUEST_SYSENTER_EIP, vmcs12->guest_sysenter_eip);
- /*
- * L1 may access the L2's PDPTR, so save them to construct
- * vmcs12
- */
- if (enable_ept) {
- vmcs_write64(GUEST_PDPTR0, vmcs12->guest_pdptr0);
- vmcs_write64(GUEST_PDPTR1, vmcs12->guest_pdptr1);
- vmcs_write64(GUEST_PDPTR2, vmcs12->guest_pdptr2);
- vmcs_write64(GUEST_PDPTR3, vmcs12->guest_pdptr3);
- }
-
if (kvm_mpx_supported() && vmx->vcpu.arch.nested_run_pending &&
(vmcs12->vm_entry_controls & VM_ENTRY_LOAD_BNDCFGS))
vmcs_write64(GUEST_BNDCFGS, vmcs12->guest_bndcfgs);
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 03/24] KVM: nSVM: invalidate cached PDPTRs across nested NPT transitions
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
2026-06-03 10:57 ` [PATCH 01/24] KVM: x86: remove nested_mmu from mmu_is_nested() Paolo Bonzini
2026-06-03 10:57 ` [PATCH 02/24] KVM: nVMX: remove unnecessary code in prepare_vmcs02_rare Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 04/24] KVM: x86: check that kvm_handle_invpcid is only invoked with shadow paging Paolo Bonzini
` (20 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
When L2 runs under nested NPT and uses PAE paging, KVM's cached PDPTRs
in mmu->pdptrs[] can hold stale or wrong values after nested
transitions and across migration restore, because both
nested_svm_load_cr3() and svm_get_nested_state_pages() only refresh
PDPTRs on the !nested_npt path.
The user-visible bug is on migration restore of an L2 running with nested
NPT and 32-bit PAE paging, if userspace uses KVM_SET_SREGS rather than
KVM_SET_SREGS2. In that case, load_pdptrs() leaves VCPU_EXREG_PDPTR
marked as available, and kvm_pdptr_read() will use a stale translation
that used L1 GPAs instead of L2 nGPAs. svm_get_nested_state_pages()
runs on first KVM_RUN but skips the refresh because nested_npt_enabled()
is true. The CPU itself reads L2's PDPTRs correctly from memory via
L1's NPT, but KVM-side walking of guest PAE page tables uses the bogus
cached values.
Unlike Intel's GUEST_PDPTR0..3 fields in the VMCS, SVM has no
VMCB-cached PDPTR state: the in-memory PDPTEs at the current CR3 are
the only source of truth, and svm_cache_reg(VCPU_EXREG_PDPTR) simply
reloads them from memory via load_pdptrs(). Clearing the avail
bit (and the dirty bit because !avail/dirty is invalid) to force
a reload when PDPTRs as needed fixes the bug.
Do the same for nested_svm_load_cr3()'s nested_npt branch, so that
the invariant "PDPTRs need reloading" is handled similarly for both
immediate and deferred loading.
Note that SVM's usage of pdptrs is overall doubtful, because load_pdptrs()
will return 0 without updating mmu->pdptrs in case of failures but
marks the register as available before the load attempt. Probably,
nSVM shouldn't be using kvm_get_pdptr() at all.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/kvm_cache_regs.h | 8 ++++++++
arch/x86/kvm/svm/nested.c | 27 ++++++++++++++++++---------
2 files changed, 26 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/kvm_cache_regs.h b/arch/x86/kvm/kvm_cache_regs.h
index 2ae492ad6412..6bae5db5a54e 100644
--- a/arch/x86/kvm/kvm_cache_regs.h
+++ b/arch/x86/kvm/kvm_cache_regs.h
@@ -77,6 +77,14 @@ static inline bool kvm_register_is_dirty(struct kvm_vcpu *vcpu,
return test_bit(reg, vcpu->arch.regs_dirty);
}
+static inline void kvm_register_mark_for_reload(struct kvm_vcpu *vcpu,
+ enum kvm_reg reg)
+{
+ kvm_assert_register_caching_allowed(vcpu);
+ __clear_bit(reg, vcpu->arch.regs_avail);
+ __clear_bit(reg, vcpu->arch.regs_dirty);
+}
+
static inline void kvm_register_mark_available(struct kvm_vcpu *vcpu,
enum kvm_reg reg)
{
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 3d1fd1776e19..aa5a1d8ea136 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -680,9 +680,12 @@ static int nested_svm_load_cr3(struct kvm_vcpu *vcpu, unsigned long cr3,
if (CC(!kvm_vcpu_is_legal_cr3(vcpu, cr3)))
return -EINVAL;
- if (reload_pdptrs && !nested_npt && is_pae_paging(vcpu) &&
- CC(!load_pdptrs(vcpu, cr3)))
- return -EINVAL;
+ if (reload_pdptrs && is_pae_paging(vcpu)) {
+ if (nested_npt)
+ kvm_register_mark_for_reload(vcpu, VCPU_REG_PDPTR);
+ else if (CC(!load_pdptrs(vcpu, cr3)))
+ return -EINVAL;
+ }
vcpu->arch.cr3 = cr3;
@@ -2055,15 +2058,21 @@ static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu)
if (WARN_ON(!is_guest_mode(vcpu)))
return true;
- if (!vcpu->arch.pdptrs_from_userspace &&
- !nested_npt_enabled(to_svm(vcpu)) && is_pae_paging(vcpu))
+ if (is_pae_paging(vcpu)) {
/*
- * Reload the guest's PDPTRs since after a migration
- * the guest CR3 might be restored prior to setting the nested
- * state which can lead to a load of wrong PDPTRs.
+ * After migration, CR3 may have been restored before
+ * KVM_SET_NESTED_STATE, so the PDPTR load into mmu->pdptrs[]
+ * may have treated CR3 as an L1 GPA. For nNPT, drop the
+ * cache so the next access reloads them with the proper
+ * nGPA translation. For !nNPT, reload eagerly unless userspace
+ * already supplied authoritative PDPTRs via KVM_SET_SREGS2.
*/
- if (CC(!load_pdptrs(vcpu, vcpu->arch.cr3)))
+ if (nested_npt_enabled(to_svm(vcpu)))
+ kvm_register_mark_for_reload(vcpu, VCPU_REG_PDPTR);
+ else if (!vcpu->arch.pdptrs_from_userspace &&
+ CC(!load_pdptrs(vcpu, vcpu->arch.cr3)))
return false;
+ }
if (!nested_svm_merge_msrpm(vcpu)) {
vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 04/24] KVM: x86: check that kvm_handle_invpcid is only invoked with shadow paging
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (2 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 03/24] KVM: nSVM: invalidate cached PDPTRs across nested NPT transitions Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 05/24] KVM: x86/mmu: move pdptrs out of the MMU Paolo Bonzini
` (19 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
This is true for both Intel and AMD. On Intel, "enable INVPCID" is
set unconditionally if supported, but the vmexit is triggered by the
"INVLPG exiting" control which is disabled by enable_ept. On AMD, KVM
can intercept INVPCID if NPT is enabled but only in order to inject #UD
in the guest.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/x86.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 48f259015ce4..6897b9f4ce7f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -14282,6 +14282,9 @@ int kvm_handle_invpcid(struct kvm_vcpu *vcpu, unsigned long type, gva_t gva)
return 1;
}
+ if (WARN_ON_ONCE(tdp_enabled))
+ return 0;
+
pcid_enabled = kvm_is_cr4_bit_set(vcpu, X86_CR4_PCIDE);
switch (type) {
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 05/24] KVM: x86/mmu: move pdptrs out of the MMU
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (3 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 04/24] KVM: x86: check that kvm_handle_invpcid is only invoked with shadow paging Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 06/24] KVM: x86/hyperv: remove unnecessary mmu_is_nested() check Paolo Bonzini
` (18 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
PDPTRs are part of the CPU state. A bit unconventionally, they are
reached via vcpu->arch.walk_mmu instead of being stored in vcpu->arch
directly. That is nice in principle---it would allow TDP shadow paging
to have its own PDPTRs---but it is not necessary, because EPT has no
PDPTRs and NPT does not cache them.
Since kvm_pdptr_read does not otherwise need the MMU, drop the pdptrs
from the MMU altogether. There is however something to be careful
about, in that PDPTRs are now not stored separately in root_mmu and
nested_mmu for L1 and L2 guests. In practice this was already not
an issue:
- for EPT the VMCS0x has to keep them up to date; and for the purpose
of emulation they are always loaded from the VMCS on vmentry/vmexit,
thanks to the clearing of dirty and available register bitmaps in
vmx_switch_vmcs()
- for NPT, VCPU_EXREG_PDPTR is similarly cleared for nNPT, which does
not cache the PDPTRs; while for non-nNPT the PDPTRs are loaded
together with the load of CR3.
Note that page table PDPTRs are not affected, since they are stored
in pae_root.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 5 ++---
arch/x86/kvm/kvm_cache_regs.h | 4 ++--
arch/x86/kvm/svm/svm.c | 2 +-
arch/x86/kvm/vmx/vmx.c | 20 ++++++++------------
arch/x86/kvm/x86.c | 6 +++---
5 files changed, 16 insertions(+), 21 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 53527b0550c7..c7c1c2e2a7c2 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -522,10 +522,7 @@ struct kvm_mmu {
* the bits spte never used.
*/
struct rsvd_bits_validate shadow_zero_check;
-
struct rsvd_bits_validate guest_rsvd_check;
-
- u64 pdptrs[4]; /* pae */
};
enum pmc_type {
@@ -883,6 +880,8 @@ struct kvm_vcpu_arch {
*/
struct kvm_mmu *walk_mmu;
+ u64 pdptrs[4]; /* pae */
+
struct kvm_mmu_memory_cache mmu_pte_list_desc_cache;
struct kvm_mmu_memory_cache mmu_shadow_page_cache;
struct kvm_mmu_memory_cache mmu_shadowed_info_cache;
diff --git a/arch/x86/kvm/kvm_cache_regs.h b/arch/x86/kvm/kvm_cache_regs.h
index 6bae5db5a54e..2a93e8c45c1a 100644
--- a/arch/x86/kvm/kvm_cache_regs.h
+++ b/arch/x86/kvm/kvm_cache_regs.h
@@ -192,12 +192,12 @@ static inline u64 kvm_pdptr_read(struct kvm_vcpu *vcpu, int index)
if (!kvm_register_is_available(vcpu, VCPU_REG_PDPTR))
kvm_x86_call(cache_reg)(vcpu, VCPU_REG_PDPTR);
- return vcpu->arch.walk_mmu->pdptrs[index];
+ return vcpu->arch.pdptrs[index];
}
static inline void kvm_pdptr_write(struct kvm_vcpu *vcpu, int index, u64 value)
{
- vcpu->arch.walk_mmu->pdptrs[index] = value;
+ vcpu->arch.pdptrs[index] = value;
}
static inline ulong kvm_read_cr0_bits(struct kvm_vcpu *vcpu, ulong mask)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index b78dd8805ebb..d190a81e030f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1526,7 +1526,7 @@ static void svm_cache_reg(struct kvm_vcpu *vcpu, enum kvm_reg reg)
switch (reg) {
case VCPU_REG_PDPTR:
/*
- * When !npt_enabled, mmu->pdptrs[] is already available since
+ * When !npt_enabled, vcpu->pdptrs[] is already available since
* it is always updated per SDM when moving to CRs.
*/
if (npt_enabled)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 1701db1b2e18..5b74315f7e95 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -3363,30 +3363,26 @@ void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu)
void vmx_ept_load_pdptrs(struct kvm_vcpu *vcpu)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
-
if (!kvm_register_is_dirty(vcpu, VCPU_REG_PDPTR))
return;
if (is_pae_paging(vcpu)) {
- vmcs_write64(GUEST_PDPTR0, mmu->pdptrs[0]);
- vmcs_write64(GUEST_PDPTR1, mmu->pdptrs[1]);
- vmcs_write64(GUEST_PDPTR2, mmu->pdptrs[2]);
- vmcs_write64(GUEST_PDPTR3, mmu->pdptrs[3]);
+ vmcs_write64(GUEST_PDPTR0, vcpu->arch.pdptrs[0]);
+ vmcs_write64(GUEST_PDPTR1, vcpu->arch.pdptrs[1]);
+ vmcs_write64(GUEST_PDPTR2, vcpu->arch.pdptrs[2]);
+ vmcs_write64(GUEST_PDPTR3, vcpu->arch.pdptrs[3]);
}
}
void ept_save_pdptrs(struct kvm_vcpu *vcpu)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
-
if (WARN_ON_ONCE(!is_pae_paging(vcpu)))
return;
- mmu->pdptrs[0] = vmcs_read64(GUEST_PDPTR0);
- mmu->pdptrs[1] = vmcs_read64(GUEST_PDPTR1);
- mmu->pdptrs[2] = vmcs_read64(GUEST_PDPTR2);
- mmu->pdptrs[3] = vmcs_read64(GUEST_PDPTR3);
+ vcpu->arch.pdptrs[0] = vmcs_read64(GUEST_PDPTR0);
+ vcpu->arch.pdptrs[1] = vmcs_read64(GUEST_PDPTR1);
+ vcpu->arch.pdptrs[2] = vmcs_read64(GUEST_PDPTR2);
+ vcpu->arch.pdptrs[3] = vmcs_read64(GUEST_PDPTR3);
kvm_register_mark_available(vcpu, VCPU_REG_PDPTR);
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 6897b9f4ce7f..c5e55597533b 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1065,7 +1065,7 @@ int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
gpa_t real_gpa;
int i;
int ret;
- u64 pdpte[ARRAY_SIZE(mmu->pdptrs)];
+ u64 pdpte[ARRAY_SIZE(vcpu->arch.pdptrs)];
/*
* If the MMU is nested, CR3 holds an L2 GPA and needs to be translated
@@ -1094,10 +1094,10 @@ int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
* Marking VCPU_REG_PDPTR dirty doesn't work for !tdp_enabled.
* Shadow page roots need to be reconstructed instead.
*/
- if (!tdp_enabled && memcmp(mmu->pdptrs, pdpte, sizeof(mmu->pdptrs)))
+ if (!tdp_enabled && memcmp(vcpu->arch.pdptrs, pdpte, sizeof(vcpu->arch.pdptrs)))
kvm_mmu_free_roots(vcpu->kvm, mmu, KVM_MMU_ROOT_CURRENT);
- memcpy(mmu->pdptrs, pdpte, sizeof(mmu->pdptrs));
+ memcpy(vcpu->arch.pdptrs, pdpte, sizeof(vcpu->arch.pdptrs));
kvm_register_mark_dirty(vcpu, VCPU_REG_PDPTR);
kvm_make_request(KVM_REQ_LOAD_MMU_PGD, vcpu);
vcpu->arch.pdptrs_from_userspace = false;
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 06/24] KVM: x86/hyperv: remove unnecessary mmu_is_nested() check
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (4 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 05/24] KVM: x86/mmu: move pdptrs out of the MMU Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 07/24] KVM: x86/mmu: introduce struct kvm_pagewalk Paolo Bonzini
` (17 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
Just always go through kvm_translate_gpa(), which will either invoke
the vendor check or just return hc->ingpa back.
This is a better way to fix the issue of commit 464af6fc2b1d ("KVM:
x86: check for nEPT/nNPT in slow flush hypercalls", 2026-05-03).
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/hyperv.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 015c6947b462..a374fd64a76a 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -2040,10 +2040,9 @@ static u64 kvm_hv_flush_tlb(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc)
* flush). Translate the address here so the memory can be uniformly
* read with kvm_read_guest().
*/
- if (!hc->fast && mmu_is_nested(vcpu)) {
- hc->ingpa = kvm_x86_ops.nested_ops->translate_nested_gpa(
- vcpu, hc->ingpa,
- PFERR_GUEST_FINAL_MASK, NULL, 0);
+ if (!hc->fast) {
+ hc->ingpa = kvm_translate_gpa(vcpu, vcpu->arch.walk_mmu, hc->ingpa,
+ PFERR_GUEST_FINAL_MASK, NULL, 0);
if (unlikely(hc->ingpa == INVALID_GPA))
return HV_STATUS_INVALID_HYPERCALL_INPUT;
}
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 07/24] KVM: x86/mmu: introduce struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (5 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 06/24] KVM: x86/hyperv: remove unnecessary mmu_is_nested() check Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 08/24] KVM: x86/mmu: move get_guest_pgd to " Paolo Bonzini
` (16 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
In preparation for separating walking and building of page tables,
introduce a dummy struct kvm_pagewalk and pass it around instead of
its containing kvm_mmu to functions that do not build the page tables.
Outermost functions retrieve the mmu via container_of, while internal
functions can pass around the struct kvm_pagewalk pointer. x86.c is
still (mostly) oblivious to the existence of struct kvm_pagewalk. There
are only a couple exceptions for now, which were done already here
for simplicity, but the plan is for the KVM code to use struct
kvm_pagewalk whenever dealing with guest page tables.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 7 +++++-
arch/x86/kvm/hyperv.c | 2 +-
arch/x86/kvm/mmu.h | 19 +++++++++------
arch/x86/kvm/mmu/mmu.c | 2 +-
arch/x86/kvm/mmu/paging_tmpl.h | 43 +++++++++++++++++++--------------
arch/x86/kvm/x86.c | 4 +--
6 files changed, 46 insertions(+), 31 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index c7c1c2e2a7c2..f72af337330b 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -476,10 +476,15 @@ struct kvm_page_fault;
/*
* x86 supports 4 paging modes (5-level 64-bit, 4-level 64-bit, 3-level 32-bit,
- * and 2-level 32-bit). The kvm_mmu structure abstracts the details of the
+ * and 2-level 32-bit). The kvm_pagewalk structure abstracts the details of the
* current mmu mode.
*/
+struct kvm_pagewalk {
+};
+
struct kvm_mmu {
+ struct kvm_pagewalk w;
+
unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index a374fd64a76a..a6e7d6f85409 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -2041,7 +2041,7 @@ static u64 kvm_hv_flush_tlb(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc)
* read with kvm_read_guest().
*/
if (!hc->fast) {
- hc->ingpa = kvm_translate_gpa(vcpu, vcpu->arch.walk_mmu, hc->ingpa,
+ hc->ingpa = kvm_translate_gpa(vcpu, &vcpu->arch.walk_mmu->w, hc->ingpa,
PFERR_GUEST_FINAL_MASK, NULL, 0);
if (unlikely(hc->ingpa == INVALID_GPA))
return HV_STATUS_INVALID_HYPERCALL_INPUT;
diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
index ddf4e467c071..3f8ac193a1e6 100644
--- a/arch/x86/kvm/mmu.h
+++ b/arch/x86/kvm/mmu.h
@@ -169,21 +169,22 @@ static inline void kvm_mmu_load_pgd(struct kvm_vcpu *vcpu)
}
static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu)
+ struct kvm_pagewalk *w)
{
/*
* When EPT is enabled, KVM may passthrough CR0.WP to the guest, i.e.
- * @mmu's snapshot of CR0.WP and thus all related paging metadata may
+ * @w's snapshot of CR0.WP and thus all related paging metadata may
* be stale. Refresh CR0.WP and the metadata on-demand when checking
* for permission faults. Exempt nested MMUs, i.e. MMUs for shadowing
* nEPT and nNPT, as CR0.WP is ignored in both cases. Note, KVM does
* need to refresh nested_mmu, a.k.a. the walker used to translate L2
* GVAs to GPAs, as that "MMU" needs to honor L2's CR0.WP.
*/
- if (!tdp_enabled || mmu == &vcpu->arch.guest_mmu)
+ if (!tdp_enabled || w == &vcpu->arch.guest_mmu.w)
return;
- __kvm_mmu_refresh_passthrough_bits(vcpu, mmu);
+ __kvm_mmu_refresh_passthrough_bits(vcpu,
+ container_of(w, struct kvm_mmu, w));
}
/*
@@ -194,10 +195,12 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
* Return zero if the access does not fault; return the page fault error code
* if the access faults.
*/
-static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
+static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
unsigned pte_access, unsigned pte_pkey,
u64 access)
{
+ struct kvm_mmu *mmu = container_of(w, struct kvm_mmu, w);
+
/* strip nested paging fault error codes */
unsigned int pfec = access;
unsigned long rflags = kvm_x86_call(get_rflags)(vcpu);
@@ -220,7 +223,7 @@ static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
u32 errcode = PFERR_PRESENT_MASK;
bool fault;
- kvm_mmu_refresh_passthrough_bits(vcpu, mmu);
+ kvm_mmu_refresh_passthrough_bits(vcpu, w);
fault = (mmu->permissions[index] >> pte_access) & 1;
@@ -301,12 +304,12 @@ static inline void kvm_update_page_stats(struct kvm *kvm, int level, int count)
}
static inline gpa_t kvm_translate_gpa(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu,
+ struct kvm_pagewalk *w,
gpa_t gpa, u64 access,
struct x86_exception *exception,
u64 pte_access)
{
- if (mmu != &vcpu->arch.nested_mmu)
+ if (w != &vcpu->arch.nested_mmu.w)
return gpa;
return kvm_x86_ops.nested_ops->translate_nested_gpa(vcpu, gpa, access,
exception,
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index f8aa7eda661e..42b7397a1845 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4354,7 +4354,7 @@ static gpa_t nonpaging_gva_to_gpa(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
* user-mode address if CR0.PG=0. Therefore *include* ACC_USER_MASK in
* the last argument to kvm_translate_gpa (which NPT does not use).
*/
- return kvm_translate_gpa(vcpu, mmu, vaddr, access | PFERR_GUEST_FINAL_MASK,
+ return kvm_translate_gpa(vcpu, &mmu->w, vaddr, access | PFERR_GUEST_FINAL_MASK,
exception, ACC_ALL);
}
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 07100bbfc270..ab1aebf2f73c 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -106,9 +106,10 @@ static gfn_t gpte_to_gfn_lvl(pt_element_t gpte, int lvl)
return (gpte & PT_LVL_ADDR_MASK(lvl)) >> PAGE_SHIFT;
}
-static inline void FNAME(protect_clean_gpte)(struct kvm_mmu *mmu, unsigned *access,
+static inline void FNAME(protect_clean_gpte)(struct kvm_pagewalk *w, unsigned *access,
unsigned gpte)
{
+ struct kvm_mmu __maybe_unused *mmu = container_of(w, struct kvm_mmu, w);
unsigned mask;
/* dirty bit is not supported, so no need to track it */
@@ -147,8 +148,10 @@ static bool FNAME(is_bad_mt_xwr)(struct rsvd_bits_validate *rsvd_check, u64 gpte
#endif
}
-static bool FNAME(is_rsvd_bits_set)(struct kvm_mmu *mmu, u64 gpte, int level)
+static bool FNAME(is_rsvd_bits_set)(struct kvm_pagewalk *w, u64 gpte, int level)
{
+ struct kvm_mmu *mmu = container_of(w, struct kvm_mmu, w);
+
return __is_rsvd_bits_set(&mmu->guest_rsvd_check, gpte, level) ||
FNAME(is_bad_mt_xwr)(&mmu->guest_rsvd_check, gpte);
}
@@ -165,7 +168,7 @@ static bool FNAME(prefetch_invalid_gpte)(struct kvm_vcpu *vcpu,
!(gpte & PT_GUEST_ACCESSED_MASK))
goto no_present;
- if (FNAME(is_rsvd_bits_set)(vcpu->arch.mmu, gpte, PG_LEVEL_4K))
+ if (FNAME(is_rsvd_bits_set)(&vcpu->arch.mmu->w, gpte, PG_LEVEL_4K))
goto no_present;
return false;
@@ -206,10 +209,11 @@ static inline unsigned FNAME(gpte_access)(u64 gpte)
}
static int FNAME(update_accessed_dirty_bits)(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu,
+ struct kvm_pagewalk *w,
struct guest_walker *walker,
gpa_t addr, int write_fault)
{
+ struct kvm_mmu __maybe_unused *mmu = container_of(w, struct kvm_mmu, w);
unsigned level, index;
pt_element_t pte, orig_pte;
pt_element_t __user *ptep_user;
@@ -278,9 +282,11 @@ static inline unsigned FNAME(gpte_pkeys)(struct kvm_vcpu *vcpu, u64 gpte)
return pkeys;
}
-static inline bool FNAME(is_last_gpte)(struct kvm_mmu *mmu,
+static inline bool FNAME(is_last_gpte)(struct kvm_pagewalk *w,
unsigned int level, unsigned int gpte)
{
+ struct kvm_mmu __maybe_unused *mmu = container_of(w, struct kvm_mmu, w);
+
/*
* For EPT and PAE paging (both variants), bit 7 is either reserved at
* all level or indicates a huge page (ignoring CR3/EPTP). In either
@@ -311,9 +317,10 @@ static inline bool FNAME(is_last_gpte)(struct kvm_mmu *mmu,
* Fetch a guest pte for a guest virtual address, or for an L2's GPA.
*/
static int FNAME(walk_addr_generic)(struct guest_walker *walker,
- struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
+ struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t addr, u64 access)
{
+ struct kvm_mmu *mmu = container_of(w, struct kvm_mmu, w);
int ret;
pt_element_t pte;
pt_element_t __user *ptep_user;
@@ -387,7 +394,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
walker->table_gfn[walker->level - 1] = table_gfn;
walker->pte_gpa[walker->level - 1] = pte_gpa;
- real_gpa = kvm_translate_gpa(vcpu, mmu, gfn_to_gpa(table_gfn),
+ real_gpa = kvm_translate_gpa(vcpu, w, gfn_to_gpa(table_gfn),
nested_access | PFERR_GUEST_PAGE_MASK,
&walker->fault, 0);
@@ -429,7 +436,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
if (unlikely(!FNAME(is_present_gpte)(mmu, pte)))
goto error;
- if (unlikely(FNAME(is_rsvd_bits_set)(mmu, pte, walker->level))) {
+ if (unlikely(FNAME(is_rsvd_bits_set)(w, pte, walker->level))) {
errcode = PFERR_RSVD_MASK | PFERR_PRESENT_MASK;
goto error;
}
@@ -438,14 +445,14 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
/* Convert to ACC_*_MASK flags for struct guest_walker. */
walker->pt_access[walker->level - 1] = FNAME(gpte_access)(pt_access ^ walk_nx_mask);
- } while (!FNAME(is_last_gpte)(mmu, walker->level, pte));
+ } while (!FNAME(is_last_gpte)(w, walker->level, pte));
pte_pkey = FNAME(gpte_pkeys)(vcpu, pte);
accessed_dirty = have_ad ? pte_access & PT_GUEST_ACCESSED_MASK : 0;
/* Convert to ACC_*_MASK flags for struct guest_walker. */
walker->pte_access = FNAME(gpte_access)(pte_access ^ walk_nx_mask);
- errcode = permission_fault(vcpu, mmu, walker->pte_access, pte_pkey, access);
+ errcode = permission_fault(vcpu, w, walker->pte_access, pte_pkey, access);
if (unlikely(errcode))
goto error;
@@ -457,7 +464,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
gfn += pse36_gfn_delta(pte);
#endif
- real_gpa = kvm_translate_gpa(vcpu, mmu, gfn_to_gpa(gfn),
+ real_gpa = kvm_translate_gpa(vcpu, w, gfn_to_gpa(gfn),
access | PFERR_GUEST_FINAL_MASK,
&walker->fault, walker->pte_access);
if (real_gpa == INVALID_GPA)
@@ -466,7 +473,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
walker->gfn = real_gpa >> PAGE_SHIFT;
if (!write_fault)
- FNAME(protect_clean_gpte)(mmu, &walker->pte_access, pte);
+ FNAME(protect_clean_gpte)(w, &walker->pte_access, pte);
else
/*
* On a write fault, fold the dirty bit into accessed_dirty.
@@ -477,7 +484,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
(PT_GUEST_DIRTY_SHIFT - PT_GUEST_ACCESSED_SHIFT);
if (unlikely(!accessed_dirty)) {
- ret = FNAME(update_accessed_dirty_bits)(vcpu, mmu, walker,
+ ret = FNAME(update_accessed_dirty_bits)(vcpu, w, walker,
addr, write_fault);
if (unlikely(ret < 0))
goto error;
@@ -539,7 +546,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
}
#endif
walker->fault.address = addr;
- walker->fault.nested_page_fault = mmu != vcpu->arch.walk_mmu;
+ walker->fault.nested_page_fault = w != &vcpu->arch.walk_mmu->w;
walker->fault.async_page_fault = false;
trace_kvm_mmu_walker_error(walker->fault.error_code);
@@ -549,7 +556,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
static int FNAME(walk_addr)(struct guest_walker *walker,
struct kvm_vcpu *vcpu, gpa_t addr, u64 access)
{
- return FNAME(walk_addr_generic)(walker, vcpu, vcpu->arch.mmu, addr,
+ return FNAME(walk_addr_generic)(walker, vcpu, &vcpu->arch.mmu->w, addr,
access);
}
@@ -565,7 +572,7 @@ FNAME(prefetch_gpte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
gfn = gpte_to_gfn(gpte);
pte_access = sp->role.access & FNAME(gpte_access)(gpte);
- FNAME(protect_clean_gpte)(vcpu->arch.mmu, &pte_access, gpte);
+ FNAME(protect_clean_gpte)(&vcpu->arch.mmu->w, &pte_access, gpte);
return kvm_mmu_prefetch_sptes(vcpu, gfn, spte, 1, pte_access);
}
@@ -895,7 +902,7 @@ static gpa_t FNAME(gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
WARN_ON_ONCE((addr >> 32) && mmu == vcpu->arch.walk_mmu);
#endif
- r = FNAME(walk_addr_generic)(&walker, vcpu, mmu, addr, access);
+ r = FNAME(walk_addr_generic)(&walker, vcpu, &mmu->w, addr, access);
if (r) {
gpa = gfn_to_gpa(walker.gfn);
@@ -945,7 +952,7 @@ static int FNAME(sync_spte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp, int
gfn = gpte_to_gfn(gpte);
pte_access = sp->role.access;
pte_access &= FNAME(gpte_access)(gpte);
- FNAME(protect_clean_gpte)(vcpu->arch.mmu, &pte_access, gpte);
+ FNAME(protect_clean_gpte)(&vcpu->arch.mmu->w, &pte_access, gpte);
if (sync_mmio_spte(vcpu, &sp->spt[i], gfn, pte_access))
return 0;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c5e55597533b..0f44482d4be0 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1071,7 +1071,7 @@ int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
* If the MMU is nested, CR3 holds an L2 GPA and needs to be translated
* to an L1 GPA.
*/
- real_gpa = kvm_translate_gpa(vcpu, mmu, gfn_to_gpa(pdpt_gfn),
+ real_gpa = kvm_translate_gpa(vcpu, &mmu->w, gfn_to_gpa(pdpt_gfn),
PFERR_USER_MASK | PFERR_WRITE_MASK |
PFERR_GUEST_PAGE_MASK, NULL, 0);
if (real_gpa == INVALID_GPA)
@@ -8090,7 +8090,7 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
* shadow page table for L2 guest.
*/
if (vcpu_match_mmio_gva(vcpu, gva) && (!is_paging(vcpu) ||
- !permission_fault(vcpu, vcpu->arch.walk_mmu,
+ !permission_fault(vcpu, &vcpu->arch.walk_mmu->w,
vcpu->arch.mmio_access, 0, access))) {
*gpa = vcpu->arch.mmio_gfn << PAGE_SHIFT |
(gva & (PAGE_SIZE - 1));
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 08/24] KVM: x86/mmu: move get_guest_pgd to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (6 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 07/24] KVM: x86/mmu: introduce struct kvm_pagewalk Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:57 ` [PATCH 09/24] KVM: x86/mmu: move gva_to_gpa " Paolo Bonzini
` (15 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
Start moving page walking functionality out of kvm_mmu. The easiest
target is the callbacks; change the kvm_mmu_get_guest_pgd() wrapper
to take a struct kvm_pagewalk too, and avoid the MMU indirection
whenever the caller has one.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 2 +-
arch/x86/kvm/mmu/mmu.c | 21 ++++++++++++---------
arch/x86/kvm/mmu/paging_tmpl.h | 2 +-
arch/x86/kvm/svm/nested.c | 4 +++-
arch/x86/kvm/vmx/nested.c | 3 ++-
5 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index f72af337330b..81c0ae3fc3f3 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -480,12 +480,12 @@ struct kvm_page_fault;
* current mmu mode.
*/
struct kvm_pagewalk {
+ unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
};
struct kvm_mmu {
struct kvm_pagewalk w;
- unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
void (*inject_page_fault)(struct kvm_vcpu *vcpu,
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 42b7397a1845..8981e5526ba1 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -269,12 +269,12 @@ static unsigned long get_guest_cr3(struct kvm_vcpu *vcpu)
}
static inline unsigned long kvm_mmu_get_guest_pgd(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu)
+ struct kvm_pagewalk *w)
{
- if (IS_ENABLED(CONFIG_MITIGATION_RETPOLINE) && mmu->get_guest_pgd == get_guest_cr3)
+ if (IS_ENABLED(CONFIG_MITIGATION_RETPOLINE) && w->get_guest_pgd == get_guest_cr3)
return kvm_read_cr3(vcpu);
- return mmu->get_guest_pgd(vcpu);
+ return w->get_guest_pgd(vcpu);
}
static inline bool kvm_available_flush_remote_tlbs_range(void)
@@ -4071,7 +4071,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
int quadrant, i, r;
hpa_t root;
- root_pgd = kvm_mmu_get_guest_pgd(vcpu, mmu);
+ root_pgd = kvm_mmu_get_guest_pgd(vcpu, &mmu->w);
root_gfn = (root_pgd & __PT_BASE_ADDR_MASK) >> PAGE_SHIFT;
if (!kvm_vcpu_is_visible_gfn(vcpu, root_gfn)) {
@@ -4543,7 +4543,7 @@ static bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu,
if (arch.direct_map)
arch.cr3 = (unsigned long)INVALID_GPA;
else
- arch.cr3 = kvm_mmu_get_guest_pgd(vcpu, vcpu->arch.mmu);
+ arch.cr3 = kvm_mmu_get_guest_pgd(vcpu, &vcpu->arch.mmu->w);
return kvm_setup_async_pf(vcpu, fault->addr,
kvm_vcpu_gfn_to_hva(vcpu, fault->gfn), &arch);
@@ -4565,7 +4565,7 @@ void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu, struct kvm_async_pf *work)
return;
if (!vcpu->arch.mmu->root_role.direct &&
- work->arch.cr3 != kvm_mmu_get_guest_pgd(vcpu, vcpu->arch.mmu))
+ work->arch.cr3 != kvm_mmu_get_guest_pgd(vcpu, &vcpu->arch.mmu->w))
return;
r = kvm_mmu_do_page_fault(vcpu, work->cr2_or_gpa, work->arch.error_code,
@@ -5880,10 +5880,11 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->root_role.word = root_role.word;
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
- context->get_guest_pgd = get_guest_cr3;
context->get_pdptr = kvm_pdptr_read;
context->inject_page_fault = kvm_inject_page_fault;
+ context->w.get_guest_pgd = get_guest_cr3;
+
if (!is_cr0_pg(context))
context->gva_to_gpa = nonpaging_gva_to_gpa;
else if (is_cr4_pae(context))
@@ -6031,7 +6032,8 @@ static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
kvm_init_shadow_mmu(vcpu, cpu_role);
- context->get_guest_pgd = get_guest_cr3;
+ context->w.get_guest_pgd = get_guest_cr3;
+
context->get_pdptr = kvm_pdptr_read;
context->inject_page_fault = kvm_inject_page_fault;
}
@@ -6045,10 +6047,11 @@ static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
return;
g_context->cpu_role.as_u64 = new_mode.as_u64;
- g_context->get_guest_pgd = get_guest_cr3;
g_context->get_pdptr = kvm_pdptr_read;
g_context->inject_page_fault = kvm_inject_page_fault;
+ g_context->w.get_guest_pgd = get_guest_cr3;
+
/*
* L2 page tables are never shadowed, so there is no need to sync
* SPTEs.
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index ab1aebf2f73c..9c3ccea6cd6b 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -342,7 +342,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
trace_kvm_mmu_pagetable_walk(addr, access);
retry_walk:
walker->level = mmu->cpu_role.base.level;
- pte = kvm_mmu_get_guest_pgd(vcpu, mmu);
+ pte = kvm_mmu_get_guest_pgd(vcpu, w);
have_ad = PT_HAVE_ACCESSED_DIRTY(mmu);
#if PTTYPE == 64
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index aa5a1d8ea136..9f491f45eeb6 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -97,7 +97,9 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
svm->vmcb01.ptr->save.efer,
svm->nested.ctl.nested_cr3,
svm->nested.ctl.misc_ctl);
- vcpu->arch.mmu->get_guest_pgd = nested_svm_get_tdp_cr3;
+
+ vcpu->arch.mmu->w.get_guest_pgd = nested_svm_get_tdp_cr3;
+
vcpu->arch.mmu->get_pdptr = nested_svm_get_tdp_pdptr;
vcpu->arch.mmu->inject_page_fault = nested_svm_inject_npf_exit;
vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 1bd0839146fd..db63ae44c988 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -494,7 +494,8 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu = &vcpu->arch.guest_mmu;
nested_ept_new_eptp(vcpu);
- vcpu->arch.mmu->get_guest_pgd = nested_ept_get_eptp;
+ vcpu->arch.mmu->w.get_guest_pgd = nested_ept_get_eptp;
+
vcpu->arch.mmu->inject_page_fault = nested_ept_inject_page_fault;
vcpu->arch.mmu->get_pdptr = kvm_pdptr_read;
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 09/24] KVM: x86/mmu: move gva_to_gpa to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (7 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 08/24] KVM: x86/mmu: move get_guest_pgd to " Paolo Bonzini
@ 2026-06-03 10:57 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 10/24] KVM: x86/mmu: move get_pdptr " Paolo Bonzini
` (14 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:57 UTC (permalink / raw)
To: linux-kernel, kvm
gva_to_gpa is the main entry point into walk_mmu, which
is only used for guest page table walking (as opposed to building
the page tables). Moving gva_to_gpa to struct kvm_pagewalk
is a steps towards making walk_mmu a struct kvm_pagewalk.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 6 +++---
arch/x86/kvm/mmu/mmu.c | 26 +++++++++++++-------------
arch/x86/kvm/mmu/paging_tmpl.h | 6 +++---
arch/x86/kvm/svm/nested.c | 4 ++--
arch/x86/kvm/vmx/nested.c | 4 ++--
arch/x86/kvm/x86.c | 30 +++++++++++++++---------------
6 files changed, 38 insertions(+), 38 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 81c0ae3fc3f3..536a7d325d89 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -481,6 +481,9 @@ struct kvm_page_fault;
*/
struct kvm_pagewalk {
unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
+ gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
+ gpa_t gva_or_gpa, u64 access,
+ struct x86_exception *exception);
};
struct kvm_mmu {
@@ -490,9 +493,6 @@ struct kvm_mmu {
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
void (*inject_page_fault)(struct kvm_vcpu *vcpu,
struct x86_exception *fault);
- gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
- gpa_t gva_or_gpa, u64 access,
- struct x86_exception *exception);
int (*sync_spte)(struct kvm_vcpu *vcpu,
struct kvm_mmu_page *sp, int i);
struct kvm_mmu_root_info root;
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 8981e5526ba1..552a104e9496 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4342,7 +4342,7 @@ void kvm_mmu_sync_prev_roots(struct kvm_vcpu *vcpu)
kvm_mmu_free_roots(vcpu->kvm, vcpu->arch.mmu, roots_to_free);
}
-static gpa_t nonpaging_gva_to_gpa(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
+static gpa_t nonpaging_gva_to_gpa(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t vaddr, u64 access,
struct x86_exception *exception)
{
@@ -4354,7 +4354,7 @@ static gpa_t nonpaging_gva_to_gpa(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
* user-mode address if CR0.PG=0. Therefore *include* ACC_USER_MASK in
* the last argument to kvm_translate_gpa (which NPT does not use).
*/
- return kvm_translate_gpa(vcpu, &mmu->w, vaddr, access | PFERR_GUEST_FINAL_MASK,
+ return kvm_translate_gpa(vcpu, w, vaddr, access | PFERR_GUEST_FINAL_MASK,
exception, ACC_ALL);
}
@@ -5119,7 +5119,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_tdp_mmu_map_private_pfn);
static void nonpaging_init_context(struct kvm_mmu *context)
{
context->page_fault = nonpaging_page_fault;
- context->gva_to_gpa = nonpaging_gva_to_gpa;
+ context->w.gva_to_gpa = nonpaging_gva_to_gpa;
context->sync_spte = NULL;
}
@@ -5750,14 +5750,14 @@ static void reset_guest_paging_metadata(struct kvm_vcpu *vcpu,
static void paging64_init_context(struct kvm_mmu *context)
{
context->page_fault = paging64_page_fault;
- context->gva_to_gpa = paging64_gva_to_gpa;
+ context->w.gva_to_gpa = paging64_gva_to_gpa;
context->sync_spte = paging64_sync_spte;
}
static void paging32_init_context(struct kvm_mmu *context)
{
context->page_fault = paging32_page_fault;
- context->gva_to_gpa = paging32_gva_to_gpa;
+ context->w.gva_to_gpa = paging32_gva_to_gpa;
context->sync_spte = paging32_sync_spte;
}
@@ -5886,11 +5886,11 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->w.get_guest_pgd = get_guest_cr3;
if (!is_cr0_pg(context))
- context->gva_to_gpa = nonpaging_gva_to_gpa;
+ context->w.gva_to_gpa = nonpaging_gva_to_gpa;
else if (is_cr4_pae(context))
- context->gva_to_gpa = paging64_gva_to_gpa;
+ context->w.gva_to_gpa = paging64_gva_to_gpa;
else
- context->gva_to_gpa = paging32_gva_to_gpa;
+ context->w.gva_to_gpa = paging32_gva_to_gpa;
reset_guest_paging_metadata(vcpu, context);
reset_tdp_shadow_zero_bits_mask(context);
@@ -6012,7 +6012,7 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
context->root_role.word = new_mode.base.word;
context->page_fault = ept_page_fault;
- context->gva_to_gpa = ept_gva_to_gpa;
+ context->w.gva_to_gpa = ept_gva_to_gpa;
context->sync_spte = ept_sync_spte;
update_permission_bitmask(context, true, true);
@@ -6067,13 +6067,13 @@ static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
* the gva_to_gpa functions between mmu and nested_mmu are swapped.
*/
if (!is_paging(vcpu))
- g_context->gva_to_gpa = nonpaging_gva_to_gpa;
+ g_context->w.gva_to_gpa = nonpaging_gva_to_gpa;
else if (is_long_mode(vcpu))
- g_context->gva_to_gpa = paging64_gva_to_gpa;
+ g_context->w.gva_to_gpa = paging64_gva_to_gpa;
else if (is_pae(vcpu))
- g_context->gva_to_gpa = paging64_gva_to_gpa;
+ g_context->w.gva_to_gpa = paging64_gva_to_gpa;
else
- g_context->gva_to_gpa = paging32_gva_to_gpa;
+ g_context->w.gva_to_gpa = paging32_gva_to_gpa;
reset_guest_paging_metadata(vcpu, g_context);
}
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 9c3ccea6cd6b..6fcce1d9b787 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -889,7 +889,7 @@ static gpa_t FNAME(get_level1_sp_gpa)(struct kvm_mmu_page *sp)
}
/* Note, @addr is a GPA when gva_to_gpa() translates an L2 GPA to an L1 GPA. */
-static gpa_t FNAME(gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
+static gpa_t FNAME(gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t addr, u64 access,
struct x86_exception *exception)
{
@@ -899,10 +899,10 @@ static gpa_t FNAME(gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
#ifndef CONFIG_X86_64
/* A 64-bit GVA should be impossible on 32-bit KVM. */
- WARN_ON_ONCE((addr >> 32) && mmu == vcpu->arch.walk_mmu);
+ WARN_ON_ONCE((addr >> 32) && w == &vcpu->arch.walk_mmu->w);
#endif
- r = FNAME(walk_addr_generic)(&walker, vcpu, &mmu->w, addr, access);
+ r = FNAME(walk_addr_generic)(&walker, vcpu, w, addr, access);
if (r) {
gpa = gfn_to_gpa(walker.gfn);
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 9f491f45eeb6..d49e3ae28143 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -2096,7 +2096,7 @@ static gpa_t svm_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
u64 pte_access)
{
struct vcpu_svm *svm = to_svm(vcpu);
- struct kvm_mmu *mmu = vcpu->arch.mmu;
+ struct kvm_pagewalk *w = &vcpu->arch.mmu->w;
BUG_ON(!mmu_is_nested(vcpu));
@@ -2104,7 +2104,7 @@ static gpa_t svm_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
if (!(svm->nested.ctl.misc_ctl & SVM_MISC_ENABLE_GMET))
access |= PFERR_USER_MASK;
- return mmu->gva_to_gpa(vcpu, mmu, gpa, access, exception);
+ return w->gva_to_gpa(vcpu, w, gpa, access, exception);
}
struct kvm_x86_nested_ops svm_nested_ops = {
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index db63ae44c988..7d3106c2f83c 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -7450,7 +7450,7 @@ static gpa_t vmx_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
struct x86_exception *exception,
u64 pte_access)
{
- struct kvm_mmu *mmu = vcpu->arch.mmu;
+ struct kvm_pagewalk *w = &vcpu->arch.mmu->w;
BUG_ON(!mmu_is_nested(vcpu));
@@ -7462,7 +7462,7 @@ static gpa_t vmx_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
if ((pte_access & ACC_USER_MASK) && (access & PFERR_GUEST_FINAL_MASK))
access |= PFERR_USER_MASK;
- return mmu->gva_to_gpa(vcpu, mmu, gpa, access, exception);
+ return w->gva_to_gpa(vcpu, w, gpa, access, exception);
}
struct kvm_x86_nested_ops vmx_nested_ops = {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 0f44482d4be0..00566655ad05 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7851,21 +7851,21 @@ void kvm_get_segment(struct kvm_vcpu *vcpu,
gpa_t kvm_mmu_gva_to_gpa_read(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
- return mmu->gva_to_gpa(vcpu, mmu, gva, access, exception);
+ return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, access, exception);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_read);
gpa_t kvm_mmu_gva_to_gpa_write(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
access |= PFERR_WRITE_MASK;
- return mmu->gva_to_gpa(vcpu, mmu, gva, access, exception);
+ return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, access, exception);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_write);
@@ -7873,21 +7873,21 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_write);
gpa_t kvm_mmu_gva_to_gpa_system(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
- return mmu->gva_to_gpa(vcpu, mmu, gva, 0, exception);
+ return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, 0, exception);
}
static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
struct kvm_vcpu *vcpu, u64 access,
struct x86_exception *exception)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
void *data = val;
int r = X86EMUL_CONTINUE;
while (bytes) {
- gpa_t gpa = mmu->gva_to_gpa(vcpu, mmu, addr, access, exception);
+ gpa_t gpa = gva_walk->gva_to_gpa(vcpu, gva_walk, addr, access, exception);
unsigned offset = addr & (PAGE_SIZE-1);
unsigned toread = min(bytes, (unsigned)PAGE_SIZE - offset);
int ret;
@@ -7915,14 +7915,14 @@ static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
struct x86_exception *exception)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
unsigned offset;
int ret;
/* Inline kvm_read_guest_virt_helper for speed. */
- gpa_t gpa = mmu->gva_to_gpa(vcpu, mmu, addr, access|PFERR_FETCH_MASK,
- exception);
+ gpa_t gpa = gva_walk->gva_to_gpa(vcpu, gva_walk, addr, access|PFERR_FETCH_MASK,
+ exception);
if (unlikely(gpa == INVALID_GPA))
return X86EMUL_PROPAGATE_FAULT;
@@ -7974,12 +7974,12 @@ static int kvm_write_guest_virt_helper(gva_t addr, void *val, unsigned int bytes
struct kvm_vcpu *vcpu, u64 access,
struct x86_exception *exception)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
void *data = val;
int r = X86EMUL_CONTINUE;
while (bytes) {
- gpa_t gpa = mmu->gva_to_gpa(vcpu, mmu, addr, access, exception);
+ gpa_t gpa = gva_walk->gva_to_gpa(vcpu, gva_walk, addr, access, exception);
unsigned offset = addr & (PAGE_SIZE-1);
unsigned towrite = min(bytes, (unsigned)PAGE_SIZE - offset);
int ret;
@@ -8098,7 +8098,7 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
return 1;
}
- *gpa = mmu->gva_to_gpa(vcpu, mmu, gva, access, exception);
+ *gpa = mmu->w.gva_to_gpa(vcpu, &mmu->w, gva, access, exception);
if (*gpa == INVALID_GPA)
return -1;
@@ -14217,7 +14217,7 @@ void kvm_fixup_and_inject_pf_error(struct kvm_vcpu *vcpu, gva_t gva, u16 error_c
(PFERR_WRITE_MASK | PFERR_FETCH_MASK | PFERR_USER_MASK);
if (!(error_code & PFERR_PRESENT_MASK) ||
- mmu->gva_to_gpa(vcpu, mmu, gva, access, &fault) != INVALID_GPA) {
+ mmu->w.gva_to_gpa(vcpu, &mmu->w, gva, access, &fault) != INVALID_GPA) {
/*
* If vcpu->arch.walk_mmu->gva_to_gpa succeeded, the page
* tables probably do not match the TLB. Just proceed
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 10/24] KVM: x86/mmu: move get_pdptr to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (8 preceding siblings ...)
2026-06-03 10:57 ` [PATCH 09/24] KVM: x86/mmu: move gva_to_gpa " Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 11/24] KVM: x86/mmu: move inject_page_fault " Paolo Bonzini
` (13 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Continue with yet another callback used in FNAME(walk_addr_generic),
as another step towards removing container_of() from there.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 2 +-
arch/x86/kvm/mmu/mmu.c | 8 ++++----
arch/x86/kvm/mmu/paging_tmpl.h | 2 +-
arch/x86/kvm/svm/nested.c | 2 +-
arch/x86/kvm/vmx/nested.c | 2 +-
5 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 536a7d325d89..81cb9c03cf88 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -481,6 +481,7 @@ struct kvm_page_fault;
*/
struct kvm_pagewalk {
unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
+ u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t gva_or_gpa, u64 access,
struct x86_exception *exception);
@@ -489,7 +490,6 @@ struct kvm_pagewalk {
struct kvm_mmu {
struct kvm_pagewalk w;
- u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
void (*inject_page_fault)(struct kvm_vcpu *vcpu,
struct x86_exception *fault);
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 552a104e9496..a51705f53957 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4085,7 +4085,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
*/
if (mmu->cpu_role.base.level == PT32E_ROOT_LEVEL) {
for (i = 0; i < 4; ++i) {
- pdptrs[i] = mmu->get_pdptr(vcpu, i);
+ pdptrs[i] = mmu->w.get_pdptr(vcpu, i);
if (!(pdptrs[i] & PT_PRESENT_MASK))
continue;
@@ -5880,9 +5880,9 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->root_role.word = root_role.word;
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
- context->get_pdptr = kvm_pdptr_read;
context->inject_page_fault = kvm_inject_page_fault;
+ context->w.get_pdptr = kvm_pdptr_read;
context->w.get_guest_pgd = get_guest_cr3;
if (!is_cr0_pg(context))
@@ -6032,9 +6032,9 @@ static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
kvm_init_shadow_mmu(vcpu, cpu_role);
+ context->w.get_pdptr = kvm_pdptr_read;
context->w.get_guest_pgd = get_guest_cr3;
- context->get_pdptr = kvm_pdptr_read;
context->inject_page_fault = kvm_inject_page_fault;
}
@@ -6047,9 +6047,9 @@ static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
return;
g_context->cpu_role.as_u64 = new_mode.as_u64;
- g_context->get_pdptr = kvm_pdptr_read;
g_context->inject_page_fault = kvm_inject_page_fault;
+ g_context->w.get_pdptr = kvm_pdptr_read;
g_context->w.get_guest_pgd = get_guest_cr3;
/*
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 6fcce1d9b787..ef112ca1e405 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -348,7 +348,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
#if PTTYPE == 64
walk_nx_mask = 1ULL << PT64_NX_SHIFT;
if (walker->level == PT32E_ROOT_LEVEL) {
- pte = mmu->get_pdptr(vcpu, (addr >> 30) & 3);
+ pte = w->get_pdptr(vcpu, (addr >> 30) & 3);
trace_kvm_mmu_paging_element(pte, walker->level);
if (!FNAME(is_present_gpte)(mmu, pte))
goto error;
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index d49e3ae28143..3eb701454a56 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -99,8 +99,8 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
svm->nested.ctl.misc_ctl);
vcpu->arch.mmu->w.get_guest_pgd = nested_svm_get_tdp_cr3;
+ vcpu->arch.mmu->w.get_pdptr = nested_svm_get_tdp_pdptr;
- vcpu->arch.mmu->get_pdptr = nested_svm_get_tdp_pdptr;
vcpu->arch.mmu->inject_page_fault = nested_svm_inject_npf_exit;
vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
}
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 7d3106c2f83c..4af8a25926da 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -495,9 +495,9 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu = &vcpu->arch.guest_mmu;
nested_ept_new_eptp(vcpu);
vcpu->arch.mmu->w.get_guest_pgd = nested_ept_get_eptp;
+ vcpu->arch.mmu->w.get_pdptr = kvm_pdptr_read;
vcpu->arch.mmu->inject_page_fault = nested_ept_inject_page_fault;
- vcpu->arch.mmu->get_pdptr = kvm_pdptr_read;
vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
}
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 11/24] KVM: x86/mmu: move inject_page_fault to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (9 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 10/24] KVM: x86/mmu: move get_pdptr " Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 12/24] KVM: x86/mmu: move CPU-related fields " Paolo Bonzini
` (12 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Injection of page faults is also part of accesses to guest
page tables. In particular, kvm_inject_emulated_page_fault
calls it on walk_mmu. Move it to struct kvm_pagewalk as
part of converting walk_mmu to a struct kvm_pagewalk.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 4 ++--
arch/x86/kvm/mmu/mmu.c | 8 +++-----
arch/x86/kvm/svm/nested.c | 2 +-
arch/x86/kvm/vmx/nested.c | 2 +-
arch/x86/kvm/x86.c | 4 ++--
5 files changed, 9 insertions(+), 11 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 81cb9c03cf88..fb468e234b37 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -482,6 +482,8 @@ struct kvm_page_fault;
struct kvm_pagewalk {
unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
+ void (*inject_page_fault)(struct kvm_vcpu *vcpu,
+ struct x86_exception *fault);
gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t gva_or_gpa, u64 access,
struct x86_exception *exception);
@@ -491,8 +493,6 @@ struct kvm_mmu {
struct kvm_pagewalk w;
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
- void (*inject_page_fault)(struct kvm_vcpu *vcpu,
- struct x86_exception *fault);
int (*sync_spte)(struct kvm_vcpu *vcpu,
struct kvm_mmu_page *sp, int i);
struct kvm_mmu_root_info root;
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index a51705f53957..4fbb7508e241 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5880,8 +5880,8 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->root_role.word = root_role.word;
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
- context->inject_page_fault = kvm_inject_page_fault;
+ context->w.inject_page_fault = kvm_inject_page_fault;
context->w.get_pdptr = kvm_pdptr_read;
context->w.get_guest_pgd = get_guest_cr3;
@@ -6032,10 +6032,9 @@ static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
kvm_init_shadow_mmu(vcpu, cpu_role);
+ context->w.inject_page_fault = kvm_inject_page_fault;
context->w.get_pdptr = kvm_pdptr_read;
context->w.get_guest_pgd = get_guest_cr3;
-
- context->inject_page_fault = kvm_inject_page_fault;
}
static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
@@ -6047,8 +6046,7 @@ static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
return;
g_context->cpu_role.as_u64 = new_mode.as_u64;
- g_context->inject_page_fault = kvm_inject_page_fault;
-
+ g_context->w.inject_page_fault = kvm_inject_page_fault;
g_context->w.get_pdptr = kvm_pdptr_read;
g_context->w.get_guest_pgd = get_guest_cr3;
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 3eb701454a56..79ef81b878d7 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -101,7 +101,7 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.get_guest_pgd = nested_svm_get_tdp_cr3;
vcpu->arch.mmu->w.get_pdptr = nested_svm_get_tdp_pdptr;
- vcpu->arch.mmu->inject_page_fault = nested_svm_inject_npf_exit;
+ vcpu->arch.mmu->w.inject_page_fault = nested_svm_inject_npf_exit;
vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
}
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 4af8a25926da..e9e6714ccd83 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -497,7 +497,7 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.get_guest_pgd = nested_ept_get_eptp;
vcpu->arch.mmu->w.get_pdptr = kvm_pdptr_read;
- vcpu->arch.mmu->inject_page_fault = nested_ept_inject_page_fault;
+ vcpu->arch.mmu->w.inject_page_fault = nested_ept_inject_page_fault;
vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 00566655ad05..e514096f960c 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1005,7 +1005,7 @@ void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
kvm_mmu_invalidate_addr(vcpu, fault_mmu, fault->address,
KVM_MMU_ROOT_CURRENT);
- fault_mmu->inject_page_fault(vcpu, fault);
+ fault_mmu->w.inject_page_fault(vcpu, fault);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_inject_emulated_page_fault);
@@ -14230,7 +14230,7 @@ void kvm_fixup_and_inject_pf_error(struct kvm_vcpu *vcpu, gva_t gva, u16 error_c
fault.address = gva;
fault.async_page_fault = false;
}
- vcpu->arch.walk_mmu->inject_page_fault(vcpu, &fault);
+ vcpu->arch.walk_mmu->w.inject_page_fault(vcpu, &fault);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fixup_and_inject_pf_error);
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 12/24] KVM: x86/mmu: move CPU-related fields to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (10 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 11/24] KVM: x86/mmu: move inject_page_fault " Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 11:27 ` sashiko-bot
2026-06-03 10:58 ` [PATCH 13/24] KVM: x86/mmu: change CPU-role accessor fields to take " Paolo Bonzini
` (11 subsequent siblings)
23 siblings, 1 reply; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
struct kvm_pagewalk's behavior depends on the CPU state and its
page format. Move related fields so that walk_mmu remains
self contained.
Note that for now, some of the accessors still use kvm_mmu
to split the churn.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 4 +--
arch/x86/kvm/mmu/mmu.c | 52 ++++++++++++++++-----------------
arch/x86/kvm/mmu/paging_tmpl.h | 40 ++++++++++++-------------
3 files changed, 46 insertions(+), 50 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index fb468e234b37..33c505a15015 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -487,6 +487,8 @@ struct kvm_pagewalk {
gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t gva_or_gpa, u64 access,
struct x86_exception *exception);
+ union kvm_cpu_role cpu_role;
+ struct rsvd_bits_validate guest_rsvd_check;
};
struct kvm_mmu {
@@ -497,7 +499,6 @@ struct kvm_mmu {
struct kvm_mmu_page *sp, int i);
struct kvm_mmu_root_info root;
hpa_t mirror_root_hpa;
- union kvm_cpu_role cpu_role;
union kvm_mmu_page_role root_role;
/*
@@ -527,7 +528,6 @@ struct kvm_mmu {
* the bits spte never used.
*/
struct rsvd_bits_validate shadow_zero_check;
- struct rsvd_bits_validate guest_rsvd_check;
};
enum pmc_type {
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 4fbb7508e241..e2bfecf655d9 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -226,7 +226,7 @@ BUILD_MMU_ROLE_REGS_ACCESSOR(efer, lma, EFER_LMA);
#define BUILD_MMU_ROLE_ACCESSOR(base_or_ext, reg, name) \
static inline bool __maybe_unused is_##reg##_##name(struct kvm_mmu *mmu) \
{ \
- return !!(mmu->cpu_role. base_or_ext . reg##_##name); \
+ return !!(mmu->w.cpu_role. base_or_ext . reg##_##name); \
}
BUILD_MMU_ROLE_ACCESSOR(base, cr0, wp);
BUILD_MMU_ROLE_ACCESSOR(ext, cr4, pse);
@@ -239,17 +239,17 @@ BUILD_MMU_ROLE_ACCESSOR(ext, efer, lma);
static inline bool has_pferr_fetch(struct kvm_mmu *mmu)
{
- return mmu->cpu_role.ext.has_pferr_fetch;
+ return mmu->w.cpu_role.ext.has_pferr_fetch;
}
static inline bool is_cr0_pg(struct kvm_mmu *mmu)
{
- return mmu->cpu_role.base.level > 0;
+ return mmu->w.cpu_role.base.level > 0;
}
static inline bool is_cr4_pae(struct kvm_mmu *mmu)
{
- return !mmu->cpu_role.base.has_4_byte_gpte;
+ return !mmu->w.cpu_role.base.has_4_byte_gpte;
}
static struct kvm_mmu_role_regs vcpu_to_role_regs(struct kvm_vcpu *vcpu)
@@ -2478,7 +2478,7 @@ static void shadow_walk_init_using_root(struct kvm_shadow_walk_iterator *iterato
iterator->level = vcpu->arch.mmu->root_role.level;
if (iterator->level >= PT64_ROOT_4LEVEL &&
- vcpu->arch.mmu->cpu_role.base.level < PT64_ROOT_4LEVEL &&
+ vcpu->arch.mmu->w.cpu_role.base.level < PT64_ROOT_4LEVEL &&
!vcpu->arch.mmu->root_role.direct)
iterator->level = PT32E_ROOT_LEVEL;
@@ -4083,7 +4083,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
* On SVM, reading PDPTRs might access guest memory, which might fault
* and thus might sleep. Grab the PDPTRs before acquiring mmu_lock.
*/
- if (mmu->cpu_role.base.level == PT32E_ROOT_LEVEL) {
+ if (mmu->w.cpu_role.base.level == PT32E_ROOT_LEVEL) {
for (i = 0; i < 4; ++i) {
pdptrs[i] = mmu->w.get_pdptr(vcpu, i);
if (!(pdptrs[i] & PT_PRESENT_MASK))
@@ -4107,7 +4107,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
* Do we shadow a long mode page table? If so we need to
* write-protect the guests page table root.
*/
- if (mmu->cpu_role.base.level >= PT64_ROOT_4LEVEL) {
+ if (mmu->w.cpu_role.base.level >= PT64_ROOT_4LEVEL) {
root = mmu_alloc_root(vcpu, root_gfn, 0,
mmu->root_role.level);
mmu->root.hpa = root;
@@ -4146,7 +4146,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
for (i = 0; i < 4; ++i) {
WARN_ON_ONCE(IS_VALID_PAE_ROOT(mmu->pae_root[i]));
- if (mmu->cpu_role.base.level == PT32E_ROOT_LEVEL) {
+ if (mmu->w.cpu_role.base.level == PT32E_ROOT_LEVEL) {
if (!(pdptrs[i] & PT_PRESENT_MASK)) {
mmu->pae_root[i] = INVALID_PAE_ROOT;
continue;
@@ -4160,7 +4160,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
* directory. Othwerise each PAE page direct shadows one guest
* PAE page directory so that quadrant should be 0.
*/
- quadrant = (mmu->cpu_role.base.level == PT32_ROOT_LEVEL) ? i : 0;
+ quadrant = (mmu->w.cpu_role.base.level == PT32_ROOT_LEVEL) ? i : 0;
root = mmu_alloc_root(vcpu, root_gfn, quadrant, PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | pm_mask;
@@ -4196,7 +4196,7 @@ static int mmu_alloc_special_roots(struct kvm_vcpu *vcpu)
* on demand, as running a 32-bit L1 VMM on 64-bit KVM is very rare.
*/
if (mmu->root_role.direct ||
- mmu->cpu_role.base.level >= PT64_ROOT_4LEVEL ||
+ mmu->w.cpu_role.base.level >= PT64_ROOT_4LEVEL ||
mmu->root_role.level < PT64_ROOT_4LEVEL)
return 0;
@@ -4301,7 +4301,7 @@ void kvm_mmu_sync_roots(struct kvm_vcpu *vcpu)
vcpu_clear_mmio_info(vcpu, MMIO_GVA_ANY);
- if (vcpu->arch.mmu->cpu_role.base.level >= PT64_ROOT_4LEVEL) {
+ if (vcpu->arch.mmu->w.cpu_role.base.level >= PT64_ROOT_4LEVEL) {
hpa_t root = vcpu->arch.mmu->root.hpa;
if (!is_unsync_root(root))
@@ -5387,9 +5387,9 @@ static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
struct kvm_mmu *context)
{
- __reset_rsvds_bits_mask(&context->guest_rsvd_check,
+ __reset_rsvds_bits_mask(&context->w.guest_rsvd_check,
vcpu->arch.reserved_gpa_bits,
- context->cpu_role.base.level, is_efer_nx(context),
+ context->w.cpu_role.base.level, is_efer_nx(context),
guest_cpu_cap_has(vcpu, X86_FEATURE_GBPAGES),
is_cr4_pse(context),
guest_cpuid_is_amd_compatible(vcpu));
@@ -5436,7 +5436,7 @@ static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
static void reset_rsvds_bits_mask_ept(struct kvm_vcpu *vcpu,
struct kvm_mmu *context, bool execonly, int huge_page_level)
{
- __reset_rsvds_bits_mask_ept(&context->guest_rsvd_check,
+ __reset_rsvds_bits_mask_ept(&context->w.guest_rsvd_check,
vcpu->arch.reserved_gpa_bits, execonly,
huge_page_level);
}
@@ -5813,7 +5813,7 @@ void __kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
if (is_cr0_wp(mmu) == cr0_wp)
return;
- mmu->cpu_role.base.cr0_wp = cr0_wp;
+ mmu->w.cpu_role.base.cr0_wp = cr0_wp;
reset_guest_paging_metadata(vcpu, mmu);
}
@@ -5872,11 +5872,11 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
struct kvm_mmu *context = &vcpu->arch.root_mmu;
union kvm_mmu_page_role root_role = kvm_calc_tdp_mmu_root_page_role(vcpu, cpu_role);
- if (cpu_role.as_u64 == context->cpu_role.as_u64 &&
+ if (cpu_role.as_u64 == context->w.cpu_role.as_u64 &&
root_role.word == context->root_role.word)
return;
- context->cpu_role.as_u64 = cpu_role.as_u64;
+ context->w.cpu_role.as_u64 = cpu_role.as_u64;
context->root_role.word = root_role.word;
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
@@ -5900,11 +5900,11 @@ static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *conte
union kvm_cpu_role cpu_role,
union kvm_mmu_page_role root_role)
{
- if (cpu_role.as_u64 == context->cpu_role.as_u64 &&
+ if (cpu_role.as_u64 == context->w.cpu_role.as_u64 &&
root_role.word == context->root_role.word)
return;
- context->cpu_role.as_u64 = cpu_role.as_u64;
+ context->w.cpu_role.as_u64 = cpu_role.as_u64;
context->root_role.word = root_role.word;
if (!is_cr0_pg(context))
@@ -6006,9 +6006,9 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
kvm_calc_shadow_ept_root_page_role(vcpu, accessed_dirty,
execonly, level, mbec);
- if (new_mode.as_u64 != context->cpu_role.as_u64) {
+ if (new_mode.as_u64 != context->w.cpu_role.as_u64) {
/* EPT, and thus nested EPT, does not consume CR0, CR4, nor EFER. */
- context->cpu_role.as_u64 = new_mode.as_u64;
+ context->w.cpu_role.as_u64 = new_mode.as_u64;
context->root_role.word = new_mode.base.word;
context->page_fault = ept_page_fault;
@@ -6042,10 +6042,10 @@ static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
{
struct kvm_mmu *g_context = &vcpu->arch.nested_mmu;
- if (new_mode.as_u64 == g_context->cpu_role.as_u64)
+ if (new_mode.as_u64 == g_context->w.cpu_role.as_u64)
return;
- g_context->cpu_role.as_u64 = new_mode.as_u64;
+ g_context->w.cpu_role.as_u64 = new_mode.as_u64;
g_context->w.inject_page_fault = kvm_inject_page_fault;
g_context->w.get_pdptr = kvm_pdptr_read;
g_context->w.get_guest_pgd = get_guest_cr3;
@@ -6107,9 +6107,9 @@ void kvm_mmu_after_set_cpuid(struct kvm_vcpu *vcpu)
vcpu->arch.root_mmu.root_role.invalid = 1;
vcpu->arch.guest_mmu.root_role.invalid = 1;
vcpu->arch.nested_mmu.root_role.invalid = 1;
- vcpu->arch.root_mmu.cpu_role.ext.valid = 0;
- vcpu->arch.guest_mmu.cpu_role.ext.valid = 0;
- vcpu->arch.nested_mmu.cpu_role.ext.valid = 0;
+ vcpu->arch.root_mmu.w.cpu_role.ext.valid = 0;
+ vcpu->arch.guest_mmu.w.cpu_role.ext.valid = 0;
+ vcpu->arch.nested_mmu.w.cpu_role.ext.valid = 0;
kvm_mmu_reset_context(vcpu);
KVM_BUG_ON(!kvm_can_set_cpuid_and_feature_msrs(vcpu), vcpu->kvm);
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index ef112ca1e405..10b1e7a08e90 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -55,7 +55,7 @@
#define PT_LEVEL_BITS 9
#define PT_GUEST_DIRTY_SHIFT 9
#define PT_GUEST_ACCESSED_SHIFT 8
- #define PT_HAVE_ACCESSED_DIRTY(mmu) (!(mmu)->cpu_role.base.ad_disabled)
+ #define PT_HAVE_ACCESSED_DIRTY(w) (!(w)->cpu_role.base.ad_disabled)
#define PT_MAX_FULL_LEVELS PT64_ROOT_MAX_LEVEL
#else
#error Invalid PTTYPE value
@@ -109,11 +109,10 @@ static gfn_t gpte_to_gfn_lvl(pt_element_t gpte, int lvl)
static inline void FNAME(protect_clean_gpte)(struct kvm_pagewalk *w, unsigned *access,
unsigned gpte)
{
- struct kvm_mmu __maybe_unused *mmu = container_of(w, struct kvm_mmu, w);
unsigned mask;
/* dirty bit is not supported, so no need to track it */
- if (!PT_HAVE_ACCESSED_DIRTY(mmu))
+ if (!PT_HAVE_ACCESSED_DIRTY(w))
return;
BUILD_BUG_ON(PT_WRITABLE_MASK != ACC_WRITE_MASK);
@@ -125,7 +124,7 @@ static inline void FNAME(protect_clean_gpte)(struct kvm_pagewalk *w, unsigned *a
*access &= mask;
}
-static inline int FNAME(is_present_gpte)(struct kvm_mmu *mmu,
+static inline int FNAME(is_present_gpte)(struct kvm_pagewalk *w,
unsigned long pte)
{
#if PTTYPE != PTTYPE_EPT
@@ -135,7 +134,7 @@ static inline int FNAME(is_present_gpte)(struct kvm_mmu *mmu,
* For EPT, an entry is present if any of bits 2:0 are set.
* With mode-based execute control, bit 10 also indicates presence.
*/
- return pte & (7 | (mmu_has_mbec(mmu) ? VMX_EPT_USER_EXECUTABLE_MASK : 0));
+ return pte & (7 | (w->cpu_role.base.cr4_smep ? VMX_EPT_USER_EXECUTABLE_MASK : 0));
#endif
}
@@ -150,25 +149,25 @@ static bool FNAME(is_bad_mt_xwr)(struct rsvd_bits_validate *rsvd_check, u64 gpte
static bool FNAME(is_rsvd_bits_set)(struct kvm_pagewalk *w, u64 gpte, int level)
{
- struct kvm_mmu *mmu = container_of(w, struct kvm_mmu, w);
-
- return __is_rsvd_bits_set(&mmu->guest_rsvd_check, gpte, level) ||
- FNAME(is_bad_mt_xwr)(&mmu->guest_rsvd_check, gpte);
+ return __is_rsvd_bits_set(&w->guest_rsvd_check, gpte, level) ||
+ FNAME(is_bad_mt_xwr)(&w->guest_rsvd_check, gpte);
}
static bool FNAME(prefetch_invalid_gpte)(struct kvm_vcpu *vcpu,
struct kvm_mmu_page *sp, u64 *spte,
u64 gpte)
{
- if (!FNAME(is_present_gpte)(vcpu->arch.mmu, gpte))
+ struct kvm_pagewalk *w = &vcpu->arch.mmu->w;
+
+ if (!FNAME(is_present_gpte)(w, gpte))
goto no_present;
/* Prefetch only accessed entries (unless A/D bits are disabled). */
- if (PT_HAVE_ACCESSED_DIRTY(vcpu->arch.mmu) &&
+ if (PT_HAVE_ACCESSED_DIRTY(w) &&
!(gpte & PT_GUEST_ACCESSED_MASK))
goto no_present;
- if (FNAME(is_rsvd_bits_set)(&vcpu->arch.mmu->w, gpte, PG_LEVEL_4K))
+ if (FNAME(is_rsvd_bits_set)(w, gpte, PG_LEVEL_4K))
goto no_present;
return false;
@@ -213,7 +212,6 @@ static int FNAME(update_accessed_dirty_bits)(struct kvm_vcpu *vcpu,
struct guest_walker *walker,
gpa_t addr, int write_fault)
{
- struct kvm_mmu __maybe_unused *mmu = container_of(w, struct kvm_mmu, w);
unsigned level, index;
pt_element_t pte, orig_pte;
pt_element_t __user *ptep_user;
@@ -221,7 +219,7 @@ static int FNAME(update_accessed_dirty_bits)(struct kvm_vcpu *vcpu,
int ret;
/* dirty/accessed bits are not supported, so no need to update them */
- if (!PT_HAVE_ACCESSED_DIRTY(mmu))
+ if (!PT_HAVE_ACCESSED_DIRTY(w))
return 0;
for (level = walker->max_level; level >= walker->level; --level) {
@@ -285,8 +283,6 @@ static inline unsigned FNAME(gpte_pkeys)(struct kvm_vcpu *vcpu, u64 gpte)
static inline bool FNAME(is_last_gpte)(struct kvm_pagewalk *w,
unsigned int level, unsigned int gpte)
{
- struct kvm_mmu __maybe_unused *mmu = container_of(w, struct kvm_mmu, w);
-
/*
* For EPT and PAE paging (both variants), bit 7 is either reserved at
* all level or indicates a huge page (ignoring CR3/EPTP). In either
@@ -302,7 +298,7 @@ static inline bool FNAME(is_last_gpte)(struct kvm_pagewalk *w,
* is not reserved and does not indicate a large page at this level,
* so clear PT_PAGE_SIZE_MASK in gpte if that is the case.
*/
- gpte &= level - (PT32_ROOT_LEVEL + mmu->cpu_role.ext.cr4_pse);
+ gpte &= level - (PT32_ROOT_LEVEL + w->cpu_role.ext.cr4_pse);
#endif
/*
* PG_LEVEL_4K always terminates. The RHS has bit 7 set
@@ -341,16 +337,16 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
trace_kvm_mmu_pagetable_walk(addr, access);
retry_walk:
- walker->level = mmu->cpu_role.base.level;
+ walker->level = w->cpu_role.base.level;
pte = kvm_mmu_get_guest_pgd(vcpu, w);
- have_ad = PT_HAVE_ACCESSED_DIRTY(mmu);
+ have_ad = PT_HAVE_ACCESSED_DIRTY(w);
#if PTTYPE == 64
walk_nx_mask = 1ULL << PT64_NX_SHIFT;
if (walker->level == PT32E_ROOT_LEVEL) {
pte = w->get_pdptr(vcpu, (addr >> 30) & 3);
trace_kvm_mmu_paging_element(pte, walker->level);
- if (!FNAME(is_present_gpte)(mmu, pte))
+ if (!FNAME(is_present_gpte)(w, pte))
goto error;
--walker->level;
}
@@ -433,7 +429,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
*/
pte_access = pt_access & (pte ^ walk_nx_mask);
- if (unlikely(!FNAME(is_present_gpte)(mmu, pte)))
+ if (unlikely(!FNAME(is_present_gpte)(w, pte)))
goto error;
if (unlikely(FNAME(is_rsvd_bits_set)(w, pte, walker->level))) {
@@ -655,7 +651,7 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
WARN_ON_ONCE(gw->gfn != base_gfn);
direct_access = gw->pte_access;
- top_level = vcpu->arch.mmu->cpu_role.base.level;
+ top_level = vcpu->arch.mmu->w.cpu_role.base.level;
if (top_level == PT32E_ROOT_LEVEL)
top_level = PT32_ROOT_LEVEL;
/*
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 13/24] KVM: x86/mmu: change CPU-role accessor fields to take struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (11 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 12/24] KVM: x86/mmu: move CPU-related fields " Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 14/24] KVM: x86/mmu: move remaining permission fields to " Paolo Bonzini
` (10 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
With this change, walk_addr_generic and its callees do not need to use
container_of() anymore.
The next step is removing it from permission_fault() and
kvm_mmu_refresh_passthrough_bits().
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 44 +++++++++++++++++-----------------
arch/x86/kvm/mmu/paging_tmpl.h | 11 ++++-----
2 files changed, 27 insertions(+), 28 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index e2bfecf655d9..2ef04d8c6f95 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -224,9 +224,9 @@ BUILD_MMU_ROLE_REGS_ACCESSOR(efer, lma, EFER_LMA);
* and the vCPU may be incorrect/irrelevant.
*/
#define BUILD_MMU_ROLE_ACCESSOR(base_or_ext, reg, name) \
-static inline bool __maybe_unused is_##reg##_##name(struct kvm_mmu *mmu) \
+static inline bool __maybe_unused is_##reg##_##name(struct kvm_pagewalk *w) \
{ \
- return !!(mmu->w.cpu_role. base_or_ext . reg##_##name); \
+ return !!(w->cpu_role. base_or_ext . reg##_##name); \
}
BUILD_MMU_ROLE_ACCESSOR(base, cr0, wp);
BUILD_MMU_ROLE_ACCESSOR(ext, cr4, pse);
@@ -237,19 +237,19 @@ BUILD_MMU_ROLE_ACCESSOR(ext, cr4, la57);
BUILD_MMU_ROLE_ACCESSOR(base, efer, nx);
BUILD_MMU_ROLE_ACCESSOR(ext, efer, lma);
-static inline bool has_pferr_fetch(struct kvm_mmu *mmu)
+static inline bool has_pferr_fetch(struct kvm_pagewalk *w)
{
- return mmu->w.cpu_role.ext.has_pferr_fetch;
+ return w->cpu_role.ext.has_pferr_fetch;
}
-static inline bool is_cr0_pg(struct kvm_mmu *mmu)
+static inline bool is_cr0_pg(struct kvm_pagewalk *w)
{
- return mmu->w.cpu_role.base.level > 0;
+ return w->cpu_role.base.level > 0;
}
-static inline bool is_cr4_pae(struct kvm_mmu *mmu)
+static inline bool is_cr4_pae(struct kvm_pagewalk *w)
{
- return !mmu->w.cpu_role.base.has_4_byte_gpte;
+ return !w->cpu_role.base.has_4_byte_gpte;
}
static struct kvm_mmu_role_regs vcpu_to_role_regs(struct kvm_vcpu *vcpu)
@@ -5389,9 +5389,9 @@ static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
{
__reset_rsvds_bits_mask(&context->w.guest_rsvd_check,
vcpu->arch.reserved_gpa_bits,
- context->w.cpu_role.base.level, is_efer_nx(context),
+ context->w.cpu_role.base.level, is_efer_nx(&context->w),
guest_cpu_cap_has(vcpu, X86_FEATURE_GBPAGES),
- is_cr4_pse(context),
+ is_cr4_pse(&context->w),
guest_cpuid_is_amd_compatible(vcpu));
}
@@ -5573,10 +5573,10 @@ static void update_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
const u16 w = ACC_BITS_MASK(ACC_WRITE_MASK);
const u16 r = ACC_BITS_MASK(ACC_READ_MASK);
- bool cr4_smep = is_cr4_smep(mmu);
- bool cr4_smap = is_cr4_smap(mmu);
- bool cr0_wp = is_cr0_wp(mmu);
- bool efer_nx = is_efer_nx(mmu);
+ bool cr4_smep = is_cr4_smep(&mmu->w);
+ bool cr4_smap = is_cr4_smap(&mmu->w);
+ bool cr0_wp = is_cr0_wp(&mmu->w);
+ bool efer_nx = is_efer_nx(&mmu->w);
/*
* In hardware, page fault error codes are generated (as the name
@@ -5699,10 +5699,10 @@ static void update_pkru_bitmask(struct kvm_mmu *mmu)
mmu->pkru_mask = 0;
- if (!is_cr4_pke(mmu))
+ if (!is_cr4_pke(&mmu->w))
return;
- wp = is_cr0_wp(mmu);
+ wp = is_cr0_wp(&mmu->w);
for (bit = 0; bit < ARRAY_SIZE(mmu->permissions); ++bit) {
unsigned pfec, pkey_bits;
@@ -5739,7 +5739,7 @@ static void update_pkru_bitmask(struct kvm_mmu *mmu)
static void reset_guest_paging_metadata(struct kvm_vcpu *vcpu,
struct kvm_mmu *mmu)
{
- if (!is_cr0_pg(mmu))
+ if (!is_cr0_pg(&mmu->w))
return;
reset_guest_rsvds_bits_mask(vcpu, mmu);
@@ -5810,7 +5810,7 @@ void __kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
BUILD_BUG_ON((KVM_MMU_CR0_ROLE_BITS & KVM_POSSIBLE_CR0_GUEST_BITS) != X86_CR0_WP);
BUILD_BUG_ON((KVM_MMU_CR4_ROLE_BITS & KVM_POSSIBLE_CR4_GUEST_BITS));
- if (is_cr0_wp(mmu) == cr0_wp)
+ if (is_cr0_wp(&mmu->w) == cr0_wp)
return;
mmu->w.cpu_role.base.cr0_wp = cr0_wp;
@@ -5885,9 +5885,9 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->w.get_pdptr = kvm_pdptr_read;
context->w.get_guest_pgd = get_guest_cr3;
- if (!is_cr0_pg(context))
+ if (!is_cr0_pg(&context->w))
context->w.gva_to_gpa = nonpaging_gva_to_gpa;
- else if (is_cr4_pae(context))
+ else if (is_cr4_pae(&context->w))
context->w.gva_to_gpa = paging64_gva_to_gpa;
else
context->w.gva_to_gpa = paging32_gva_to_gpa;
@@ -5907,9 +5907,9 @@ static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *conte
context->w.cpu_role.as_u64 = cpu_role.as_u64;
context->root_role.word = root_role.word;
- if (!is_cr0_pg(context))
+ if (!is_cr0_pg(&context->w))
nonpaging_init_context(context);
- else if (is_cr4_pae(context))
+ else if (is_cr4_pae(&context->w))
paging64_init_context(context);
else
paging32_init_context(context);
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 10b1e7a08e90..99a0e1c95223 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -134,7 +134,7 @@ static inline int FNAME(is_present_gpte)(struct kvm_pagewalk *w,
* For EPT, an entry is present if any of bits 2:0 are set.
* With mode-based execute control, bit 10 also indicates presence.
*/
- return pte & (7 | (w->cpu_role.base.cr4_smep ? VMX_EPT_USER_EXECUTABLE_MASK : 0));
+ return pte & (7 | (is_cr4_smep(w) ? VMX_EPT_USER_EXECUTABLE_MASK : 0));
#endif
}
@@ -316,7 +316,6 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
gpa_t addr, u64 access)
{
- struct kvm_mmu *mmu = container_of(w, struct kvm_mmu, w);
int ret;
pt_element_t pte;
pt_element_t __user *ptep_user;
@@ -492,7 +491,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
error:
errcode |= write_fault | user_fault;
- if (fetch_fault && has_pferr_fetch(mmu))
+ if (fetch_fault && has_pferr_fetch(w))
errcode |= PFERR_FETCH_MASK;
walker->fault.vector = PF_VECTOR;
@@ -536,7 +535,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
* ACC_*_MASK flags!
*/
walker->fault.exit_qualification |= EPT_VIOLATION_RWX_TO_PROT(pte_access);
- if (mmu_has_mbec(mmu))
+ if (is_cr4_smep(w))
walker->fault.exit_qualification |=
EPT_VIOLATION_USER_EXEC_TO_PROT(pte_access);
}
@@ -840,7 +839,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
* otherwise KVM will cache incorrect access information in the SPTE.
*/
if (fault->write && !(walker.pte_access & ACC_WRITE_MASK) &&
- !is_cr0_wp(vcpu->arch.mmu) && !fault->user && fault->slot) {
+ !is_cr0_wp(&vcpu->arch.mmu->w) && !fault->user && fault->slot) {
walker.pte_access |= ACC_WRITE_MASK;
walker.pte_access &= ~ACC_USER_MASK;
@@ -850,7 +849,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
* then we should prevent the kernel from executing it
* if SMEP is enabled.
*/
- if (is_cr4_smep(vcpu->arch.mmu))
+ if (is_cr4_smep(&vcpu->arch.mmu->w))
walker.pte_access &= ~ACC_EXEC_MASK;
}
#endif
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 14/24] KVM: x86/mmu: move remaining permission fields to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (12 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 13/24] KVM: x86/mmu: change CPU-role accessor fields to take " Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 15/24] KVM: x86/mmu: pass struct kvm_pagewalk to kvm_mmu_invalidate_addr Paolo Bonzini
` (9 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
As promised, this removes the remaining instances of
container_of(w, struct kvm_mmu, w).
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 30 ++++++++--------
arch/x86/kvm/mmu.h | 13 +++----
arch/x86/kvm/mmu/mmu.c | 62 ++++++++++++++++-----------------
3 files changed, 51 insertions(+), 54 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 33c505a15015..860a929e3cd8 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -489,6 +489,21 @@ struct kvm_pagewalk {
struct x86_exception *exception);
union kvm_cpu_role cpu_role;
struct rsvd_bits_validate guest_rsvd_check;
+
+ /*
+ * The pkru_mask indicates if protection key checks are needed. It
+ * consists of 16 domains indexed by page fault error code bits [4:1],
+ * with PFEC.RSVD replaced by ACC_USER_MASK from the page tables.
+ * Each domain has 2 bits which are ANDed with AD and WD from PKRU.
+ */
+ u32 pkru_mask;
+
+ /*
+ * Bitmap; bit set = permission fault
+ * Array index: page fault error code [4:1]
+ * Bit index: pte permissions in ACC_* format
+ */
+ u16 permissions[16];
};
struct kvm_mmu {
@@ -501,23 +516,8 @@ struct kvm_mmu {
hpa_t mirror_root_hpa;
union kvm_mmu_page_role root_role;
- /*
- * The pkru_mask indicates if protection key checks are needed. It
- * consists of 16 domains indexed by page fault error code bits [4:1],
- * with PFEC.RSVD replaced by ACC_USER_MASK from the page tables.
- * Each domain has 2 bits which are ANDed with AD and WD from PKRU.
- */
- u32 pkru_mask;
-
struct kvm_mmu_root_info prev_roots[KVM_MMU_NUM_PREV_ROOTS];
- /*
- * Bitmap; bit set = permission fault
- * Byte index: page fault error code [4:1]
- * Bit index: pte permissions in ACC_* format
- */
- u16 permissions[16];
-
u64 *pae_root;
u64 *pml4_root;
u64 *pml5_root;
diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
index 3f8ac193a1e6..d1b5d9b0c6ad 100644
--- a/arch/x86/kvm/mmu.h
+++ b/arch/x86/kvm/mmu.h
@@ -105,7 +105,7 @@ bool kvm_can_do_async_pf(struct kvm_vcpu *vcpu);
int kvm_handle_page_fault(struct kvm_vcpu *vcpu, u64 error_code,
u64 fault_address, char *insn, int insn_len);
void __kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu);
+ struct kvm_pagewalk *pw);
int kvm_mmu_load(struct kvm_vcpu *vcpu);
void kvm_mmu_unload(struct kvm_vcpu *vcpu);
@@ -183,8 +183,7 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
if (!tdp_enabled || w == &vcpu->arch.guest_mmu.w)
return;
- __kvm_mmu_refresh_passthrough_bits(vcpu,
- container_of(w, struct kvm_mmu, w));
+ __kvm_mmu_refresh_passthrough_bits(vcpu, w);
}
/*
@@ -199,8 +198,6 @@ static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
unsigned pte_access, unsigned pte_pkey,
u64 access)
{
- struct kvm_mmu *mmu = container_of(w, struct kvm_mmu, w);
-
/* strip nested paging fault error codes */
unsigned int pfec = access;
unsigned long rflags = kvm_x86_call(get_rflags)(vcpu);
@@ -225,10 +222,10 @@ static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
kvm_mmu_refresh_passthrough_bits(vcpu, w);
- fault = (mmu->permissions[index] >> pte_access) & 1;
+ fault = (w->permissions[index] >> pte_access) & 1;
WARN_ON_ONCE(pfec & (PFERR_PK_MASK | PFERR_SS_MASK | PFERR_RSVD_MASK));
- if (unlikely(mmu->pkru_mask)) {
+ if (unlikely(w->pkru_mask)) {
u32 pkru_bits, offset;
/*
@@ -242,7 +239,7 @@ static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
/* clear present bit, replace PFEC.RSVD with ACC_USER_MASK. */
offset = (pfec & ~1) | ((pte_access & PT_USER_MASK) ? PFERR_RSVD_MASK : 0);
- pkru_bits &= mmu->pkru_mask >> offset;
+ pkru_bits &= w->pkru_mask >> offset;
errcode |= -pkru_bits & PFERR_PK_MASK;
fault |= (pkru_bits != 0);
}
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 2ef04d8c6f95..cc58b6157118 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5385,13 +5385,13 @@ static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
}
static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
- struct kvm_mmu *context)
+ struct kvm_pagewalk *w)
{
- __reset_rsvds_bits_mask(&context->w.guest_rsvd_check,
+ __reset_rsvds_bits_mask(&w->guest_rsvd_check,
vcpu->arch.reserved_gpa_bits,
- context->w.cpu_role.base.level, is_efer_nx(&context->w),
+ w->cpu_role.base.level, is_efer_nx(w),
guest_cpu_cap_has(vcpu, X86_FEATURE_GBPAGES),
- is_cr4_pse(&context->w),
+ is_cr4_pse(w),
guest_cpuid_is_amd_compatible(vcpu));
}
@@ -5566,17 +5566,17 @@ reset_ept_shadow_zero_bits_mask(struct kvm_mmu *context, bool execonly)
(14 & (access) ? 1 << 14 : 0) | \
(15 & (access) ? 1 << 15 : 0))
-static void update_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
+static void update_permission_bitmask(struct kvm_pagewalk *pw, bool tdp, bool ept)
{
unsigned index;
const u16 w = ACC_BITS_MASK(ACC_WRITE_MASK);
const u16 r = ACC_BITS_MASK(ACC_READ_MASK);
- bool cr4_smep = is_cr4_smep(&mmu->w);
- bool cr4_smap = is_cr4_smap(&mmu->w);
- bool cr0_wp = is_cr0_wp(&mmu->w);
- bool efer_nx = is_efer_nx(&mmu->w);
+ bool cr4_smep = is_cr4_smep(pw);
+ bool cr4_smap = is_cr4_smap(pw);
+ bool cr0_wp = is_cr0_wp(pw);
+ bool efer_nx = is_efer_nx(pw);
/*
* In hardware, page fault error codes are generated (as the name
@@ -5590,7 +5590,7 @@ static void update_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
* permission_fault() to indicate accesses that are *not* subject to
* SMAP restrictions.
*/
- for (index = 0; index < ARRAY_SIZE(mmu->permissions); ++index) {
+ for (index = 0; index < ARRAY_SIZE(pw->permissions); ++index) {
unsigned pfec = index << 1;
/*
@@ -5664,7 +5664,7 @@ static void update_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
smapf = (pfec & (PFERR_RSVD_MASK|PFERR_FETCH_MASK)) ? 0 : kf;
}
- mmu->permissions[index] = ff | uf | wf | rf | smapf;
+ pw->permissions[index] = ff | uf | wf | rf | smapf;
}
}
@@ -5692,19 +5692,19 @@ static void update_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
* away both AD and WD. For all reads or if the last condition holds, WD
* only will be masked away.
*/
-static void update_pkru_bitmask(struct kvm_mmu *mmu)
+static void update_pkru_bitmask(struct kvm_pagewalk *w)
{
unsigned bit;
bool wp;
- mmu->pkru_mask = 0;
+ w->pkru_mask = 0;
- if (!is_cr4_pke(&mmu->w))
+ if (!is_cr4_pke(w))
return;
- wp = is_cr0_wp(&mmu->w);
+ wp = is_cr0_wp(w);
- for (bit = 0; bit < ARRAY_SIZE(mmu->permissions); ++bit) {
+ for (bit = 0; bit < ARRAY_SIZE(w->permissions); ++bit) {
unsigned pfec, pkey_bits;
bool check_pkey, check_write, ff, uf, wf, pte_user;
@@ -5732,19 +5732,19 @@ static void update_pkru_bitmask(struct kvm_mmu *mmu)
/* PKRU.WD stops write access. */
pkey_bits |= (!!check_write) << 1;
- mmu->pkru_mask |= (pkey_bits & 3) << pfec;
+ w->pkru_mask |= (pkey_bits & 3) << pfec;
}
}
static void reset_guest_paging_metadata(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu)
+ struct kvm_pagewalk *w)
{
- if (!is_cr0_pg(&mmu->w))
+ if (!is_cr0_pg(w))
return;
- reset_guest_rsvds_bits_mask(vcpu, mmu);
- update_permission_bitmask(mmu, mmu == &vcpu->arch.guest_mmu, false);
- update_pkru_bitmask(mmu);
+ reset_guest_rsvds_bits_mask(vcpu, w);
+ update_permission_bitmask(w, w == &vcpu->arch.guest_mmu.w, false);
+ update_pkru_bitmask(w);
}
static void paging64_init_context(struct kvm_mmu *context)
@@ -5803,18 +5803,18 @@ static union kvm_cpu_role kvm_calc_cpu_role(struct kvm_vcpu *vcpu,
}
void __kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
- struct kvm_mmu *mmu)
+ struct kvm_pagewalk *w)
{
const bool cr0_wp = kvm_is_cr0_bit_set(vcpu, X86_CR0_WP);
BUILD_BUG_ON((KVM_MMU_CR0_ROLE_BITS & KVM_POSSIBLE_CR0_GUEST_BITS) != X86_CR0_WP);
BUILD_BUG_ON((KVM_MMU_CR4_ROLE_BITS & KVM_POSSIBLE_CR4_GUEST_BITS));
- if (is_cr0_wp(&mmu->w) == cr0_wp)
+ if (is_cr0_wp(w) == cr0_wp)
return;
- mmu->w.cpu_role.base.cr0_wp = cr0_wp;
- reset_guest_paging_metadata(vcpu, mmu);
+ w->cpu_role.base.cr0_wp = cr0_wp;
+ reset_guest_paging_metadata(vcpu, w);
}
static inline int kvm_mmu_get_tdp_level(struct kvm_vcpu *vcpu)
@@ -5892,7 +5892,7 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
else
context->w.gva_to_gpa = paging32_gva_to_gpa;
- reset_guest_paging_metadata(vcpu, context);
+ reset_guest_paging_metadata(vcpu, &context->w);
reset_tdp_shadow_zero_bits_mask(context);
}
@@ -5914,7 +5914,7 @@ static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *conte
else
paging32_init_context(context);
- reset_guest_paging_metadata(vcpu, context);
+ reset_guest_paging_metadata(vcpu, &context->w);
reset_shadow_zero_bits_mask(vcpu, context);
}
@@ -6015,8 +6015,8 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
context->w.gva_to_gpa = ept_gva_to_gpa;
context->sync_spte = ept_sync_spte;
- update_permission_bitmask(context, true, true);
- context->pkru_mask = 0;
+ update_permission_bitmask(&context->w, true, true);
+ context->w.pkru_mask = 0;
reset_rsvds_bits_mask_ept(vcpu, context, execonly, huge_page_level);
reset_ept_shadow_zero_bits_mask(context, execonly);
}
@@ -6073,7 +6073,7 @@ static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
else
g_context->w.gva_to_gpa = paging32_gva_to_gpa;
- reset_guest_paging_metadata(vcpu, g_context);
+ reset_guest_paging_metadata(vcpu, &g_context->w);
}
void kvm_init_mmu(struct kvm_vcpu *vcpu)
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 15/24] KVM: x86/mmu: pass struct kvm_pagewalk to kvm_mmu_invalidate_addr
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (13 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 14/24] KVM: x86/mmu: move remaining permission fields to " Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 16/24] KVM: x86/mmu: change walk_mmu to struct kvm_pagewalk Paolo Bonzini
` (8 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
kvm_mmu_invalidate_addr only needs to know if what's being invalidated
is a GVA or GPA. This will ultimately be represented by two different
kvm_pagewalk structs, so adjust the type of the parameter.
For now the GVA case is represented by both root_mmu and nested_mmu.
Since nested_mmu never has a sync_spte callback, it would exit at its
check; but really nested_mmu should not be a kvm_mmu in the first place
and the container_of() would be bogus, so introduce a separate check
for whether the invalidation is happening for a nested GVA. In that
case there's nothing needed beyond kvm_x86_call(flush_tlb_gva).
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 2 +-
arch/x86/kvm/mmu/mmu.c | 12 ++++++++----
arch/x86/kvm/vmx/nested.c | 2 +-
arch/x86/kvm/x86.c | 2 +-
4 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 860a929e3cd8..def338583a0f 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2388,7 +2388,7 @@ int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 error_code,
void *insn, int insn_len);
void kvm_mmu_print_sptes(struct kvm_vcpu *vcpu, gpa_t gpa, const char *msg);
void kvm_mmu_invlpg(struct kvm_vcpu *vcpu, gva_t gva);
-void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
+void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
u64 addr, unsigned long roots);
void kvm_mmu_invpcid_gva(struct kvm_vcpu *vcpu, gva_t gva, unsigned long pcid);
void kvm_mmu_new_pgd(struct kvm_vcpu *vcpu, gpa_t new_pgd);
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index cc58b6157118..967c2226cba0 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -6596,22 +6596,26 @@ static void __kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu
write_unlock(&vcpu->kvm->mmu_lock);
}
-void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
+void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
u64 addr, unsigned long roots)
{
+ struct kvm_mmu *mmu;
int i;
WARN_ON_ONCE(roots & ~KVM_MMU_ROOTS_ALL);
/* It's actually a GPA for vcpu->arch.guest_mmu. */
- if (mmu != &vcpu->arch.guest_mmu) {
+ if (w != &vcpu->arch.guest_mmu.w) {
/* INVLPG on a non-canonical address is a NOP according to the SDM. */
if (is_noncanonical_invlpg_address(addr, vcpu))
return;
kvm_x86_call(flush_tlb_gva)(vcpu, addr);
+ if (w == &vcpu->arch.nested_mmu.w)
+ return;
}
+ mmu = container_of(w, struct kvm_mmu, w);
if (!mmu->sync_spte)
return;
@@ -6637,7 +6641,7 @@ void kvm_mmu_invlpg(struct kvm_vcpu *vcpu, gva_t gva)
* be synced when switching to that new cr3, so nothing needs to be
* done here for them.
*/
- kvm_mmu_invalidate_addr(vcpu, vcpu->arch.walk_mmu, gva, KVM_MMU_ROOTS_ALL);
+ kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.walk_mmu->w, gva, KVM_MMU_ROOTS_ALL);
++vcpu->stat.invlpg;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_invlpg);
@@ -6659,7 +6663,7 @@ void kvm_mmu_invpcid_gva(struct kvm_vcpu *vcpu, gva_t gva, unsigned long pcid)
}
if (roots)
- kvm_mmu_invalidate_addr(vcpu, mmu, gva, roots);
+ kvm_mmu_invalidate_addr(vcpu, &mmu->w, gva, roots);
++vcpu->stat.invlpg;
/*
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index e9e6714ccd83..475fea4bf97a 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -407,7 +407,7 @@ static void nested_ept_invalidate_addr(struct kvm_vcpu *vcpu, gpa_t eptp,
roots |= KVM_MMU_ROOT_PREVIOUS(i);
}
if (roots)
- kvm_mmu_invalidate_addr(vcpu, vcpu->arch.mmu, addr, roots);
+ kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.guest_mmu.w, addr, roots);
}
static void nested_ept_inject_page_fault(struct kvm_vcpu *vcpu,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index e514096f960c..37dbf8c78376 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1002,7 +1002,7 @@ void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
*/
if ((fault->error_code & PFERR_PRESENT_MASK) &&
!(fault->error_code & PFERR_RSVD_MASK))
- kvm_mmu_invalidate_addr(vcpu, fault_mmu, fault->address,
+ kvm_mmu_invalidate_addr(vcpu, &fault_mmu->w, fault->address,
KVM_MMU_ROOT_CURRENT);
fault_mmu->w.inject_page_fault(vcpu, fault);
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 16/24] KVM: x86/mmu: change walk_mmu to struct kvm_pagewalk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (14 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 15/24] KVM: x86/mmu: pass struct kvm_pagewalk to kvm_mmu_invalidate_addr Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 17/24] KVM: x86/mmu: change nested_mmu.w to ngva_walk Paolo Bonzini
` (7 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Now that walk_mmu is only accessed for its "w" member, store
directly the pointer to it. This also means that nested_mmu
is only accessed for its "w" member.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 2 +-
arch/x86/kvm/hyperv.c | 2 +-
arch/x86/kvm/mmu/mmu.c | 4 +--
arch/x86/kvm/mmu/paging_tmpl.h | 4 +--
arch/x86/kvm/svm/nested.c | 4 +--
arch/x86/kvm/vmx/nested.c | 4 +--
arch/x86/kvm/x86.c | 44 +++++++++++++++++----------------
7 files changed, 33 insertions(+), 31 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index def338583a0f..368386aac3c3 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -883,7 +883,7 @@ struct kvm_vcpu_arch {
* Pointer to the mmu context currently used for
* gva_to_gpa translations.
*/
- struct kvm_mmu *walk_mmu;
+ struct kvm_pagewalk *gva_walk;
u64 pdptrs[4]; /* pae */
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index a6e7d6f85409..414dc57f1de3 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -2041,7 +2041,7 @@ static u64 kvm_hv_flush_tlb(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc)
* read with kvm_read_guest().
*/
if (!hc->fast) {
- hc->ingpa = kvm_translate_gpa(vcpu, &vcpu->arch.walk_mmu->w, hc->ingpa,
+ hc->ingpa = kvm_translate_gpa(vcpu, vcpu->arch.gva_walk, hc->ingpa,
PFERR_GUEST_FINAL_MASK, NULL, 0);
if (unlikely(hc->ingpa == INVALID_GPA))
return HV_STATUS_INVALID_HYPERCALL_INPUT;
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 967c2226cba0..e6952409c78a 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -6641,7 +6641,7 @@ void kvm_mmu_invlpg(struct kvm_vcpu *vcpu, gva_t gva)
* be synced when switching to that new cr3, so nothing needs to be
* done here for them.
*/
- kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.walk_mmu->w, gva, KVM_MMU_ROOTS_ALL);
+ kvm_mmu_invalidate_addr(vcpu, vcpu->arch.gva_walk, gva, KVM_MMU_ROOTS_ALL);
++vcpu->stat.invlpg;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_invlpg);
@@ -6778,7 +6778,7 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu)
vcpu->arch.mmu_shadow_page_cache.gfp_zero = __GFP_ZERO;
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- vcpu->arch.walk_mmu = &vcpu->arch.root_mmu;
+ vcpu->arch.gva_walk = &vcpu->arch.root_mmu.w;
ret = __kvm_mmu_create(vcpu, &vcpu->arch.guest_mmu);
if (ret)
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 99a0e1c95223..6b21778e8340 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -541,7 +541,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
}
#endif
walker->fault.address = addr;
- walker->fault.nested_page_fault = w != &vcpu->arch.walk_mmu->w;
+ walker->fault.nested_page_fault = w != vcpu->arch.gva_walk;
walker->fault.async_page_fault = false;
trace_kvm_mmu_walker_error(walker->fault.error_code);
@@ -894,7 +894,7 @@ static gpa_t FNAME(gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
#ifndef CONFIG_X86_64
/* A 64-bit GVA should be impossible on 32-bit KVM. */
- WARN_ON_ONCE((addr >> 32) && w == &vcpu->arch.walk_mmu->w);
+ WARN_ON_ONCE((addr >> 32) && w == vcpu->arch.gva_walk);
#endif
r = FNAME(walk_addr_generic)(&walker, vcpu, w, addr, access);
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 79ef81b878d7..7d89285b0677 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -102,13 +102,13 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.get_pdptr = nested_svm_get_tdp_pdptr;
vcpu->arch.mmu->w.inject_page_fault = nested_svm_inject_npf_exit;
- vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
+ vcpu->arch.gva_walk = &vcpu->arch.nested_mmu.w;
}
static void nested_svm_uninit_mmu_context(struct kvm_vcpu *vcpu)
{
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- vcpu->arch.walk_mmu = &vcpu->arch.root_mmu;
+ vcpu->arch.gva_walk = &vcpu->arch.root_mmu.w;
}
static bool nested_vmcb_needs_vls_intercept(struct vcpu_svm *svm)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 475fea4bf97a..5a89d5dcfb9a 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -499,13 +499,13 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.inject_page_fault = nested_ept_inject_page_fault;
- vcpu->arch.walk_mmu = &vcpu->arch.nested_mmu;
+ vcpu->arch.gva_walk = &vcpu->arch.nested_mmu.w;
}
static void nested_ept_uninit_mmu_context(struct kvm_vcpu *vcpu)
{
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- vcpu->arch.walk_mmu = &vcpu->arch.root_mmu;
+ vcpu->arch.gva_walk = &vcpu->arch.root_mmu.w;
}
static bool nested_vmx_is_page_fault_vmexit(struct vmcs12 *vmcs12,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 37dbf8c78376..147cef7b23b6 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -990,11 +990,12 @@ void kvm_inject_page_fault(struct kvm_vcpu *vcpu, struct x86_exception *fault)
void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
struct x86_exception *fault)
{
- struct kvm_mmu *fault_mmu;
+ struct kvm_pagewalk *fault_walk;
+
WARN_ON_ONCE(fault->vector != PF_VECTOR);
- fault_mmu = fault->nested_page_fault ? vcpu->arch.mmu :
- vcpu->arch.walk_mmu;
+ fault_walk = fault->nested_page_fault ? &vcpu->arch.mmu->w :
+ vcpu->arch.gva_walk;
/*
* Invalidate the TLB entry for the faulting address, if it exists,
@@ -1002,10 +1003,10 @@ void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
*/
if ((fault->error_code & PFERR_PRESENT_MASK) &&
!(fault->error_code & PFERR_RSVD_MASK))
- kvm_mmu_invalidate_addr(vcpu, &fault_mmu->w, fault->address,
+ kvm_mmu_invalidate_addr(vcpu, fault_walk, fault->address,
KVM_MMU_ROOT_CURRENT);
- fault_mmu->w.inject_page_fault(vcpu, fault);
+ fault_walk->inject_page_fault(vcpu, fault);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_inject_emulated_page_fault);
@@ -1060,7 +1061,7 @@ static inline u64 pdptr_rsvd_bits(struct kvm_vcpu *vcpu)
*/
int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *w = vcpu->arch.gva_walk;
gfn_t pdpt_gfn = cr3 >> PAGE_SHIFT;
gpa_t real_gpa;
int i;
@@ -1071,7 +1072,7 @@ int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
* If the MMU is nested, CR3 holds an L2 GPA and needs to be translated
* to an L1 GPA.
*/
- real_gpa = kvm_translate_gpa(vcpu, &mmu->w, gfn_to_gpa(pdpt_gfn),
+ real_gpa = kvm_translate_gpa(vcpu, w, gfn_to_gpa(pdpt_gfn),
PFERR_USER_MASK | PFERR_WRITE_MASK |
PFERR_GUEST_PAGE_MASK, NULL, 0);
if (real_gpa == INVALID_GPA)
@@ -1095,7 +1096,8 @@ int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
* Shadow page roots need to be reconstructed instead.
*/
if (!tdp_enabled && memcmp(vcpu->arch.pdptrs, pdpte, sizeof(vcpu->arch.pdptrs)))
- kvm_mmu_free_roots(vcpu->kvm, mmu, KVM_MMU_ROOT_CURRENT);
+ kvm_mmu_free_roots(vcpu->kvm, &vcpu->arch.root_mmu,
+ KVM_MMU_ROOT_CURRENT);
memcpy(vcpu->arch.pdptrs, pdpte, sizeof(vcpu->arch.pdptrs));
kvm_register_mark_dirty(vcpu, VCPU_REG_PDPTR);
@@ -7851,7 +7853,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu,
gpa_t kvm_mmu_gva_to_gpa_read(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, access, exception);
@@ -7861,7 +7863,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_read);
gpa_t kvm_mmu_gva_to_gpa_write(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
access |= PFERR_WRITE_MASK;
@@ -7873,7 +7875,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_write);
gpa_t kvm_mmu_gva_to_gpa_system(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, 0, exception);
}
@@ -7882,7 +7884,7 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
struct kvm_vcpu *vcpu, u64 access,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
void *data = val;
int r = X86EMUL_CONTINUE;
@@ -7915,7 +7917,7 @@ static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
struct x86_exception *exception)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
- struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
unsigned offset;
int ret;
@@ -7974,7 +7976,7 @@ static int kvm_write_guest_virt_helper(gva_t addr, void *val, unsigned int bytes
struct kvm_vcpu *vcpu, u64 access,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = &vcpu->arch.walk_mmu->w;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
void *data = val;
int r = X86EMUL_CONTINUE;
@@ -8080,7 +8082,7 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
gpa_t *gpa, struct x86_exception *exception,
bool write)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
u64 access = ((kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0)
| (write ? PFERR_WRITE_MASK : 0);
@@ -8090,7 +8092,7 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
* shadow page table for L2 guest.
*/
if (vcpu_match_mmio_gva(vcpu, gva) && (!is_paging(vcpu) ||
- !permission_fault(vcpu, &vcpu->arch.walk_mmu->w,
+ !permission_fault(vcpu, gva_walk,
vcpu->arch.mmio_access, 0, access))) {
*gpa = vcpu->arch.mmio_gfn << PAGE_SHIFT |
(gva & (PAGE_SIZE - 1));
@@ -8098,7 +8100,7 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
return 1;
}
- *gpa = mmu->w.gva_to_gpa(vcpu, &mmu->w, gva, access, exception);
+ *gpa = gva_walk->gva_to_gpa(vcpu, gva_walk, gva, access, exception);
if (*gpa == INVALID_GPA)
return -1;
@@ -14211,15 +14213,15 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_spec_ctrl_test_value);
void kvm_fixup_and_inject_pf_error(struct kvm_vcpu *vcpu, gva_t gva, u16 error_code)
{
- struct kvm_mmu *mmu = vcpu->arch.walk_mmu;
+ struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
struct x86_exception fault;
u64 access = error_code &
(PFERR_WRITE_MASK | PFERR_FETCH_MASK | PFERR_USER_MASK);
if (!(error_code & PFERR_PRESENT_MASK) ||
- mmu->w.gva_to_gpa(vcpu, &mmu->w, gva, access, &fault) != INVALID_GPA) {
+ gva_walk->gva_to_gpa(vcpu, gva_walk, gva, access, &fault) != INVALID_GPA) {
/*
- * If vcpu->arch.walk_mmu->gva_to_gpa succeeded, the page
+ * If gva_walk->gva_to_gpa succeeded, the page
* tables probably do not match the TLB. Just proceed
* with the error code that the processor gave.
*/
@@ -14230,7 +14232,7 @@ void kvm_fixup_and_inject_pf_error(struct kvm_vcpu *vcpu, gva_t gva, u16 error_c
fault.address = gva;
fault.async_page_fault = false;
}
- vcpu->arch.walk_mmu->w.inject_page_fault(vcpu, &fault);
+ gva_walk->inject_page_fault(vcpu, &fault);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fixup_and_inject_pf_error);
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 17/24] KVM: x86/mmu: change nested_mmu.w to ngva_walk
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (15 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 16/24] KVM: x86/mmu: change walk_mmu to struct kvm_pagewalk Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 18/24] KVM: x86/mmu: make gva_walk a value Paolo Bonzini
` (6 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
nested_mmu is now only used for its w member. Rename it,
and change its type.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 5 ++--
arch/x86/kvm/mmu.h | 6 ++---
arch/x86/kvm/mmu/mmu.c | 41 ++++++++++++++-------------------
arch/x86/kvm/svm/nested.c | 2 +-
arch/x86/kvm/vmx/nested.c | 2 +-
5 files changed, 24 insertions(+), 32 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 368386aac3c3..1bebd98ce846 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -877,11 +877,10 @@ struct kvm_vcpu_arch {
* walking and not for faulting since we never handle l2 page faults on
* the host.
*/
- struct kvm_mmu nested_mmu;
+ struct kvm_pagewalk ngva_walk;
/*
- * Pointer to the mmu context currently used for
- * gva_to_gpa translations.
+ * Pagewalk context used for gva_to_gpa translations.
*/
struct kvm_pagewalk *gva_walk;
diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
index d1b5d9b0c6ad..debdaff7f710 100644
--- a/arch/x86/kvm/mmu.h
+++ b/arch/x86/kvm/mmu.h
@@ -177,8 +177,8 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
* be stale. Refresh CR0.WP and the metadata on-demand when checking
* for permission faults. Exempt nested MMUs, i.e. MMUs for shadowing
* nEPT and nNPT, as CR0.WP is ignored in both cases. Note, KVM does
- * need to refresh nested_mmu, a.k.a. the walker used to translate L2
- * GVAs to GPAs, as that "MMU" needs to honor L2's CR0.WP.
+ * need to refresh ngva_walk, a.k.a. the walker used to translate L2
+ * GVAs to GPAs, so as to honor L2's CR0.WP.
*/
if (!tdp_enabled || w == &vcpu->arch.guest_mmu.w)
return;
@@ -306,7 +306,7 @@ static inline gpa_t kvm_translate_gpa(struct kvm_vcpu *vcpu,
struct x86_exception *exception,
u64 pte_access)
{
- if (w != &vcpu->arch.nested_mmu.w)
+ if (w != &vcpu->arch.ngva_walk)
return gpa;
return kvm_x86_ops.nested_ops->translate_nested_gpa(vcpu, gpa, access,
exception,
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index e6952409c78a..386fdbc34b02 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -6037,43 +6037,37 @@ static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
context->w.get_guest_pgd = get_guest_cr3;
}
-static void init_kvm_nested_mmu(struct kvm_vcpu *vcpu,
+static void init_kvm_ngva_walk(struct kvm_vcpu *vcpu,
union kvm_cpu_role new_mode)
{
- struct kvm_mmu *g_context = &vcpu->arch.nested_mmu;
+ struct kvm_pagewalk *g_context = &vcpu->arch.ngva_walk;
- if (new_mode.as_u64 == g_context->w.cpu_role.as_u64)
+ if (new_mode.as_u64 == g_context->cpu_role.as_u64)
return;
- g_context->w.cpu_role.as_u64 = new_mode.as_u64;
- g_context->w.inject_page_fault = kvm_inject_page_fault;
- g_context->w.get_pdptr = kvm_pdptr_read;
- g_context->w.get_guest_pgd = get_guest_cr3;
-
- /*
- * L2 page tables are never shadowed, so there is no need to sync
- * SPTEs.
- */
- g_context->sync_spte = NULL;
+ g_context->cpu_role.as_u64 = new_mode.as_u64;
+ g_context->inject_page_fault = kvm_inject_page_fault;
+ g_context->get_pdptr = kvm_pdptr_read;
+ g_context->get_guest_pgd = get_guest_cr3;
/*
* Note that arch.mmu->gva_to_gpa translates l2_gpa to l1_gpa using
* L1's nested page tables (e.g. EPT12). The nested translation
- * of l2_gva to l1_gpa is done by arch.nested_mmu.gva_to_gpa using
+ * of l2_gva to l1_gpa is done by arch.ngva_walk.gva_to_gpa using
* L2's page tables as the first level of translation and L1's
* nested page tables as the second level of translation. Basically
- * the gva_to_gpa functions between mmu and nested_mmu are swapped.
+ * the gva_to_gpa functions between mmu and ngva_walk are swapped.
*/
if (!is_paging(vcpu))
- g_context->w.gva_to_gpa = nonpaging_gva_to_gpa;
+ g_context->gva_to_gpa = nonpaging_gva_to_gpa;
else if (is_long_mode(vcpu))
- g_context->w.gva_to_gpa = paging64_gva_to_gpa;
+ g_context->gva_to_gpa = paging64_gva_to_gpa;
else if (is_pae(vcpu))
- g_context->w.gva_to_gpa = paging64_gva_to_gpa;
+ g_context->gva_to_gpa = paging64_gva_to_gpa;
else
- g_context->w.gva_to_gpa = paging32_gva_to_gpa;
+ g_context->gva_to_gpa = paging32_gva_to_gpa;
- reset_guest_paging_metadata(vcpu, &g_context->w);
+ reset_guest_paging_metadata(vcpu, g_context);
}
void kvm_init_mmu(struct kvm_vcpu *vcpu)
@@ -6082,7 +6076,7 @@ void kvm_init_mmu(struct kvm_vcpu *vcpu)
union kvm_cpu_role cpu_role = kvm_calc_cpu_role(vcpu, ®s);
if (mmu_is_nested(vcpu))
- init_kvm_nested_mmu(vcpu, cpu_role);
+ init_kvm_ngva_walk(vcpu, cpu_role);
else if (tdp_enabled)
init_kvm_tdp_mmu(vcpu, cpu_role);
else
@@ -6106,10 +6100,9 @@ void kvm_mmu_after_set_cpuid(struct kvm_vcpu *vcpu)
*/
vcpu->arch.root_mmu.root_role.invalid = 1;
vcpu->arch.guest_mmu.root_role.invalid = 1;
- vcpu->arch.nested_mmu.root_role.invalid = 1;
vcpu->arch.root_mmu.w.cpu_role.ext.valid = 0;
vcpu->arch.guest_mmu.w.cpu_role.ext.valid = 0;
- vcpu->arch.nested_mmu.w.cpu_role.ext.valid = 0;
+ vcpu->arch.ngva_walk.cpu_role.ext.valid = 0;
kvm_mmu_reset_context(vcpu);
KVM_BUG_ON(!kvm_can_set_cpuid_and_feature_msrs(vcpu), vcpu->kvm);
@@ -6611,7 +6604,7 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
return;
kvm_x86_call(flush_tlb_gva)(vcpu, addr);
- if (w == &vcpu->arch.nested_mmu.w)
+ if (w == &vcpu->arch.ngva_walk)
return;
}
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 7d89285b0677..20469fd83e8b 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -102,7 +102,7 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.get_pdptr = nested_svm_get_tdp_pdptr;
vcpu->arch.mmu->w.inject_page_fault = nested_svm_inject_npf_exit;
- vcpu->arch.gva_walk = &vcpu->arch.nested_mmu.w;
+ vcpu->arch.gva_walk = &vcpu->arch.ngva_walk;
}
static void nested_svm_uninit_mmu_context(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 5a89d5dcfb9a..477c0e8a6e43 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -499,7 +499,7 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.inject_page_fault = nested_ept_inject_page_fault;
- vcpu->arch.gva_walk = &vcpu->arch.nested_mmu.w;
+ vcpu->arch.gva_walk = &vcpu->arch.ngva_walk;
}
static void nested_ept_uninit_mmu_context(struct kvm_vcpu *vcpu)
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 18/24] KVM: x86/mmu: make gva_walk a value
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (16 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 17/24] KVM: x86/mmu: change nested_mmu.w to ngva_walk Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 11:24 ` sashiko-bot
2026-06-03 10:58 ` [PATCH 19/24] KVM: x86/mmu: pull struct kvm_pagewalk out of struct kvm_mmu Paolo Bonzini
` (5 subsequent siblings)
23 siblings, 1 reply; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Always use the same instance of kvm_pagewalk to do GVA->GPA translations,
instead of flipping the gva_walk pointer back and forth. After all the
page walking does behave the same no matter if you are in guest mode or
not; the difference lies in the behavior of kvm_translate_gpa and thus
in vcpu->arch.mmu, not in the page walker itself.
At this point, vcpu->arch.gva_walk and vcpu->arch.root_mmu.w contain
the same information (at least when KVM is not running a nested guest,
i.e. when root_mmu is actually in use); compare init_kvm_page_walk()
on one side with init_kvm_softmmu() + shadow_mmu_init_context() on
the other. root_mmu.w is still used by shadow paging, via
FNAME(walk_addr) and its callers.
vcpu->arch.guest_mmu.w instead is used for both guest emulation
(kvm_translate_gpa) and shadow paging.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 12 +----
arch/x86/kvm/hyperv.c | 2 +-
arch/x86/kvm/mmu.h | 8 +--
arch/x86/kvm/mmu/mmu.c | 86 +++++++++++++++------------------
arch/x86/kvm/mmu/paging_tmpl.h | 4 +-
arch/x86/kvm/svm/nested.c | 2 -
arch/x86/kvm/vmx/nested.c | 3 --
arch/x86/kvm/x86.c | 20 ++++----
8 files changed, 58 insertions(+), 79 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 1bebd98ce846..383bef0cf0f0 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -869,20 +869,10 @@ struct kvm_vcpu_arch {
/* L1 MMU when running nested */
struct kvm_mmu guest_mmu;
- /*
- * Paging state of an L2 guest (used for nested npt)
- *
- * This context will save all necessary information to walk page tables
- * of an L2 guest. This context is only initialized for page table
- * walking and not for faulting since we never handle l2 page faults on
- * the host.
- */
- struct kvm_pagewalk ngva_walk;
-
/*
* Pagewalk context used for gva_to_gpa translations.
*/
- struct kvm_pagewalk *gva_walk;
+ struct kvm_pagewalk gva_walk;
u64 pdptrs[4]; /* pae */
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 414dc57f1de3..5ccb76010a37 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -2041,7 +2041,7 @@ static u64 kvm_hv_flush_tlb(struct kvm_vcpu *vcpu, struct kvm_hv_hcall *hc)
* read with kvm_read_guest().
*/
if (!hc->fast) {
- hc->ingpa = kvm_translate_gpa(vcpu, vcpu->arch.gva_walk, hc->ingpa,
+ hc->ingpa = kvm_translate_gpa(vcpu, &vcpu->arch.gva_walk, hc->ingpa,
PFERR_GUEST_FINAL_MASK, NULL, 0);
if (unlikely(hc->ingpa == INVALID_GPA))
return HV_STATUS_INVALID_HYPERCALL_INPUT;
diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
index debdaff7f710..b8dc88eb56a5 100644
--- a/arch/x86/kvm/mmu.h
+++ b/arch/x86/kvm/mmu.h
@@ -176,9 +176,9 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
* @w's snapshot of CR0.WP and thus all related paging metadata may
* be stale. Refresh CR0.WP and the metadata on-demand when checking
* for permission faults. Exempt nested MMUs, i.e. MMUs for shadowing
- * nEPT and nNPT, as CR0.WP is ignored in both cases. Note, KVM does
- * need to refresh ngva_walk, a.k.a. the walker used to translate L2
- * GVAs to GPAs, so as to honor L2's CR0.WP.
+ * nEPT and nNPT, as CR0.WP is ignored in both cases. Note, KVM will
+ * still refresh gva_walk, so as to honor L2's CR0.WP when translating
+ * L2 GVAs to GPAs.
*/
if (!tdp_enabled || w == &vcpu->arch.guest_mmu.w)
return;
@@ -306,7 +306,7 @@ static inline gpa_t kvm_translate_gpa(struct kvm_vcpu *vcpu,
struct x86_exception *exception,
u64 pte_access)
{
- if (w != &vcpu->arch.ngva_walk)
+ if (!mmu_is_nested(vcpu) || w == &vcpu->arch.guest_mmu.w)
return gpa;
return kvm_x86_ops.nested_ops->translate_nested_gpa(vcpu, gpa, access,
exception,
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 386fdbc34b02..2fe4d5359006 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5943,6 +5943,27 @@ static void kvm_init_shadow_mmu(struct kvm_vcpu *vcpu,
shadow_mmu_init_context(vcpu, context, cpu_role, root_role);
}
+static void init_kvm_page_walk(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
+ union kvm_cpu_role cpu_role)
+{
+ if (cpu_role.as_u64 == w->cpu_role.as_u64)
+ return;
+
+ w->cpu_role.as_u64 = cpu_role.as_u64;
+ w->inject_page_fault = kvm_inject_page_fault;
+ w->get_pdptr = kvm_pdptr_read;
+ w->get_guest_pgd = get_guest_cr3;
+
+ if (!is_cr0_pg(w))
+ w->gva_to_gpa = nonpaging_gva_to_gpa;
+ else if (is_cr4_pae(w))
+ w->gva_to_gpa = paging64_gva_to_gpa;
+ else
+ w->gva_to_gpa = paging32_gva_to_gpa;
+
+ reset_guest_paging_metadata(vcpu, w);
+}
+
void kvm_init_shadow_npt_mmu(struct kvm_vcpu *vcpu, unsigned long cr4,
u64 efer, gpa_t nested_cr3, u64 misc_ctl)
{
@@ -6037,50 +6058,19 @@ static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
context->w.get_guest_pgd = get_guest_cr3;
}
-static void init_kvm_ngva_walk(struct kvm_vcpu *vcpu,
- union kvm_cpu_role new_mode)
-{
- struct kvm_pagewalk *g_context = &vcpu->arch.ngva_walk;
-
- if (new_mode.as_u64 == g_context->cpu_role.as_u64)
- return;
-
- g_context->cpu_role.as_u64 = new_mode.as_u64;
- g_context->inject_page_fault = kvm_inject_page_fault;
- g_context->get_pdptr = kvm_pdptr_read;
- g_context->get_guest_pgd = get_guest_cr3;
-
- /*
- * Note that arch.mmu->gva_to_gpa translates l2_gpa to l1_gpa using
- * L1's nested page tables (e.g. EPT12). The nested translation
- * of l2_gva to l1_gpa is done by arch.ngva_walk.gva_to_gpa using
- * L2's page tables as the first level of translation and L1's
- * nested page tables as the second level of translation. Basically
- * the gva_to_gpa functions between mmu and ngva_walk are swapped.
- */
- if (!is_paging(vcpu))
- g_context->gva_to_gpa = nonpaging_gva_to_gpa;
- else if (is_long_mode(vcpu))
- g_context->gva_to_gpa = paging64_gva_to_gpa;
- else if (is_pae(vcpu))
- g_context->gva_to_gpa = paging64_gva_to_gpa;
- else
- g_context->gva_to_gpa = paging32_gva_to_gpa;
-
- reset_guest_paging_metadata(vcpu, g_context);
-}
-
void kvm_init_mmu(struct kvm_vcpu *vcpu)
{
struct kvm_mmu_role_regs regs = vcpu_to_role_regs(vcpu);
union kvm_cpu_role cpu_role = kvm_calc_cpu_role(vcpu, ®s);
- if (mmu_is_nested(vcpu))
- init_kvm_ngva_walk(vcpu, cpu_role);
- else if (tdp_enabled)
- init_kvm_tdp_mmu(vcpu, cpu_role);
- else
- init_kvm_softmmu(vcpu, cpu_role);
+ init_kvm_page_walk(vcpu, &vcpu->arch.gva_walk, cpu_role);
+
+ if (!mmu_is_nested(vcpu)) {
+ if (tdp_enabled)
+ init_kvm_tdp_mmu(vcpu, cpu_role);
+ else
+ init_kvm_softmmu(vcpu, cpu_role);
+ }
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_init_mmu);
@@ -6102,7 +6092,7 @@ void kvm_mmu_after_set_cpuid(struct kvm_vcpu *vcpu)
vcpu->arch.guest_mmu.root_role.invalid = 1;
vcpu->arch.root_mmu.w.cpu_role.ext.valid = 0;
vcpu->arch.guest_mmu.w.cpu_role.ext.valid = 0;
- vcpu->arch.ngva_walk.cpu_role.ext.valid = 0;
+ vcpu->arch.gva_walk.cpu_role.ext.valid = 0;
kvm_mmu_reset_context(vcpu);
KVM_BUG_ON(!kvm_can_set_cpuid_and_feature_msrs(vcpu), vcpu->kvm);
@@ -6598,17 +6588,22 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
WARN_ON_ONCE(roots & ~KVM_MMU_ROOTS_ALL);
/* It's actually a GPA for vcpu->arch.guest_mmu. */
- if (w != &vcpu->arch.guest_mmu.w) {
+ if (w == &vcpu->arch.gva_walk) {
/* INVLPG on a non-canonical address is a NOP according to the SDM. */
if (is_noncanonical_invlpg_address(addr, vcpu))
return;
kvm_x86_call(flush_tlb_gva)(vcpu, addr);
- if (w == &vcpu->arch.ngva_walk)
+
+ if (tdp_enabled)
return;
+
+ mmu = &vcpu->arch.root_mmu;
+ } else {
+ mmu = &vcpu->arch.guest_mmu;
}
- mmu = container_of(w, struct kvm_mmu, w);
+ /* Invalidate shadow pages, whether GPA->GVA or nGPA->GPA. */
if (!mmu->sync_spte)
return;
@@ -6634,7 +6629,7 @@ void kvm_mmu_invlpg(struct kvm_vcpu *vcpu, gva_t gva)
* be synced when switching to that new cr3, so nothing needs to be
* done here for them.
*/
- kvm_mmu_invalidate_addr(vcpu, vcpu->arch.gva_walk, gva, KVM_MMU_ROOTS_ALL);
+ kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.gva_walk, gva, KVM_MMU_ROOTS_ALL);
++vcpu->stat.invlpg;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_invlpg);
@@ -6656,7 +6651,7 @@ void kvm_mmu_invpcid_gva(struct kvm_vcpu *vcpu, gva_t gva, unsigned long pcid)
}
if (roots)
- kvm_mmu_invalidate_addr(vcpu, &mmu->w, gva, roots);
+ kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.gva_walk, gva, roots);
++vcpu->stat.invlpg;
/*
@@ -6771,7 +6766,6 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu)
vcpu->arch.mmu_shadow_page_cache.gfp_zero = __GFP_ZERO;
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- vcpu->arch.gva_walk = &vcpu->arch.root_mmu.w;
ret = __kvm_mmu_create(vcpu, &vcpu->arch.guest_mmu);
if (ret)
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 6b21778e8340..b12c6b5e4a2f 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -541,7 +541,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
}
#endif
walker->fault.address = addr;
- walker->fault.nested_page_fault = w != vcpu->arch.gva_walk;
+ walker->fault.nested_page_fault = w != &vcpu->arch.gva_walk;
walker->fault.async_page_fault = false;
trace_kvm_mmu_walker_error(walker->fault.error_code);
@@ -894,7 +894,7 @@ static gpa_t FNAME(gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
#ifndef CONFIG_X86_64
/* A 64-bit GVA should be impossible on 32-bit KVM. */
- WARN_ON_ONCE((addr >> 32) && w == vcpu->arch.gva_walk);
+ WARN_ON_ONCE((addr >> 32) && w == &vcpu->arch.gva_walk);
#endif
r = FNAME(walk_addr_generic)(&walker, vcpu, w, addr, access);
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 20469fd83e8b..7853bd9ed6cc 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -102,13 +102,11 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.get_pdptr = nested_svm_get_tdp_pdptr;
vcpu->arch.mmu->w.inject_page_fault = nested_svm_inject_npf_exit;
- vcpu->arch.gva_walk = &vcpu->arch.ngva_walk;
}
static void nested_svm_uninit_mmu_context(struct kvm_vcpu *vcpu)
{
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- vcpu->arch.gva_walk = &vcpu->arch.root_mmu.w;
}
static bool nested_vmcb_needs_vls_intercept(struct vcpu_svm *svm)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 477c0e8a6e43..449efad7ea1f 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -498,14 +498,11 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu->w.get_pdptr = kvm_pdptr_read;
vcpu->arch.mmu->w.inject_page_fault = nested_ept_inject_page_fault;
-
- vcpu->arch.gva_walk = &vcpu->arch.ngva_walk;
}
static void nested_ept_uninit_mmu_context(struct kvm_vcpu *vcpu)
{
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- vcpu->arch.gva_walk = &vcpu->arch.root_mmu.w;
}
static bool nested_vmx_is_page_fault_vmexit(struct vmcs12 *vmcs12,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 147cef7b23b6..14af0f4d010e 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -995,7 +995,7 @@ void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
WARN_ON_ONCE(fault->vector != PF_VECTOR);
fault_walk = fault->nested_page_fault ? &vcpu->arch.mmu->w :
- vcpu->arch.gva_walk;
+ &vcpu->arch.gva_walk;
/*
* Invalidate the TLB entry for the faulting address, if it exists,
@@ -1061,7 +1061,7 @@ static inline u64 pdptr_rsvd_bits(struct kvm_vcpu *vcpu)
*/
int load_pdptrs(struct kvm_vcpu *vcpu, unsigned long cr3)
{
- struct kvm_pagewalk *w = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *w = &vcpu->arch.gva_walk;
gfn_t pdpt_gfn = cr3 >> PAGE_SHIFT;
gpa_t real_gpa;
int i;
@@ -7853,7 +7853,7 @@ void kvm_get_segment(struct kvm_vcpu *vcpu,
gpa_t kvm_mmu_gva_to_gpa_read(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, access, exception);
@@ -7863,7 +7863,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_read);
gpa_t kvm_mmu_gva_to_gpa_write(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
access |= PFERR_WRITE_MASK;
@@ -7875,7 +7875,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_gva_to_gpa_write);
gpa_t kvm_mmu_gva_to_gpa_system(struct kvm_vcpu *vcpu, gva_t gva,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
return gva_walk->gva_to_gpa(vcpu, gva_walk, gva, 0, exception);
}
@@ -7884,7 +7884,7 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
struct kvm_vcpu *vcpu, u64 access,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
void *data = val;
int r = X86EMUL_CONTINUE;
@@ -7917,7 +7917,7 @@ static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
struct x86_exception *exception)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
u64 access = (kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0;
unsigned offset;
int ret;
@@ -7976,7 +7976,7 @@ static int kvm_write_guest_virt_helper(gva_t addr, void *val, unsigned int bytes
struct kvm_vcpu *vcpu, u64 access,
struct x86_exception *exception)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
void *data = val;
int r = X86EMUL_CONTINUE;
@@ -8082,7 +8082,7 @@ static int vcpu_mmio_gva_to_gpa(struct kvm_vcpu *vcpu, unsigned long gva,
gpa_t *gpa, struct x86_exception *exception,
bool write)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
u64 access = ((kvm_x86_call(get_cpl)(vcpu) == 3) ? PFERR_USER_MASK : 0)
| (write ? PFERR_WRITE_MASK : 0);
@@ -14213,7 +14213,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_spec_ctrl_test_value);
void kvm_fixup_and_inject_pf_error(struct kvm_vcpu *vcpu, gva_t gva, u16 error_code)
{
- struct kvm_pagewalk *gva_walk = vcpu->arch.gva_walk;
+ struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
struct x86_exception fault;
u64 access = error_code &
(PFERR_WRITE_MASK | PFERR_FETCH_MASK | PFERR_USER_MASK);
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 19/24] KVM: x86/mmu: pull struct kvm_pagewalk out of struct kvm_mmu
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (17 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 18/24] KVM: x86/mmu: make gva_walk a value Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 20/24] KVM: x86/mmu: cleanup functions that initialize shadow MMU Paolo Bonzini
` (4 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Now that root_mmu.w always has the same content as gva_walk, replace
it with just a pointer to gva_walk. For guest_mmu, introduce a second
struct kvm_pagewalk and point to it. It is now clear that non-MMU code
does cares about page walks, but it funnels (almost) all interactions
with the TLB to mmu.c.
It is left as an exercise to the reader to split kvm_pagewalk to its own
file...
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 7 ++-
arch/x86/kvm/mmu.h | 4 +-
arch/x86/kvm/mmu/mmu.c | 97 +++++++++++++--------------------
arch/x86/kvm/mmu/paging_tmpl.h | 14 ++---
arch/x86/kvm/svm/nested.c | 9 ++-
arch/x86/kvm/vmx/nested.c | 11 ++--
arch/x86/kvm/x86.c | 2 +-
7 files changed, 63 insertions(+), 81 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 383bef0cf0f0..ce39230eaebb 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -507,11 +507,11 @@ struct kvm_pagewalk {
};
struct kvm_mmu {
- struct kvm_pagewalk w;
-
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
int (*sync_spte)(struct kvm_vcpu *vcpu,
struct kvm_mmu_page *sp, int i);
+ struct kvm_pagewalk *w;
+
struct kvm_mmu_root_info root;
hpa_t mirror_root_hpa;
union kvm_mmu_page_role root_role;
@@ -866,8 +866,9 @@ struct kvm_vcpu_arch {
/* Non-nested MMU for L1 */
struct kvm_mmu root_mmu;
- /* L1 MMU when running nested */
+ /* L1 TDP when running nested */
struct kvm_mmu guest_mmu;
+ struct kvm_pagewalk ngpa_walk;
/*
* Pagewalk context used for gva_to_gpa translations.
diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
index b8dc88eb56a5..58eb98585a29 100644
--- a/arch/x86/kvm/mmu.h
+++ b/arch/x86/kvm/mmu.h
@@ -180,7 +180,7 @@ static inline void kvm_mmu_refresh_passthrough_bits(struct kvm_vcpu *vcpu,
* still refresh gva_walk, so as to honor L2's CR0.WP when translating
* L2 GVAs to GPAs.
*/
- if (!tdp_enabled || w == &vcpu->arch.guest_mmu.w)
+ if (!tdp_enabled || w == &vcpu->arch.ngpa_walk)
return;
__kvm_mmu_refresh_passthrough_bits(vcpu, w);
@@ -306,7 +306,7 @@ static inline gpa_t kvm_translate_gpa(struct kvm_vcpu *vcpu,
struct x86_exception *exception,
u64 pte_access)
{
- if (!mmu_is_nested(vcpu) || w == &vcpu->arch.guest_mmu.w)
+ if (!mmu_is_nested(vcpu) || w == &vcpu->arch.ngpa_walk)
return gpa;
return kvm_x86_ops.nested_ops->translate_nested_gpa(vcpu, gpa, access,
exception,
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 2fe4d5359006..bd307e9b3fd6 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2473,12 +2473,14 @@ static void shadow_walk_init_using_root(struct kvm_shadow_walk_iterator *iterato
struct kvm_vcpu *vcpu, hpa_t root,
u64 addr)
{
+ struct kvm_pagewalk *w = vcpu->arch.mmu->w;
+
iterator->addr = addr;
iterator->shadow_addr = root;
iterator->level = vcpu->arch.mmu->root_role.level;
if (iterator->level >= PT64_ROOT_4LEVEL &&
- vcpu->arch.mmu->w.cpu_role.base.level < PT64_ROOT_4LEVEL &&
+ w->cpu_role.base.level < PT64_ROOT_4LEVEL &&
!vcpu->arch.mmu->root_role.direct)
iterator->level = PT32E_ROOT_LEVEL;
@@ -4066,12 +4068,13 @@ static int mmu_first_shadow_root_alloc(struct kvm *kvm)
static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
{
struct kvm_mmu *mmu = vcpu->arch.mmu;
+ struct kvm_pagewalk *w = mmu->w;
u64 pdptrs[4], pm_mask;
gfn_t root_gfn, root_pgd;
int quadrant, i, r;
hpa_t root;
- root_pgd = kvm_mmu_get_guest_pgd(vcpu, &mmu->w);
+ root_pgd = kvm_mmu_get_guest_pgd(vcpu, mmu->w);
root_gfn = (root_pgd & __PT_BASE_ADDR_MASK) >> PAGE_SHIFT;
if (!kvm_vcpu_is_visible_gfn(vcpu, root_gfn)) {
@@ -4083,9 +4086,9 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
* On SVM, reading PDPTRs might access guest memory, which might fault
* and thus might sleep. Grab the PDPTRs before acquiring mmu_lock.
*/
- if (mmu->w.cpu_role.base.level == PT32E_ROOT_LEVEL) {
+ if (w->cpu_role.base.level == PT32E_ROOT_LEVEL) {
for (i = 0; i < 4; ++i) {
- pdptrs[i] = mmu->w.get_pdptr(vcpu, i);
+ pdptrs[i] = w->get_pdptr(vcpu, i);
if (!(pdptrs[i] & PT_PRESENT_MASK))
continue;
@@ -4107,7 +4110,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
* Do we shadow a long mode page table? If so we need to
* write-protect the guests page table root.
*/
- if (mmu->w.cpu_role.base.level >= PT64_ROOT_4LEVEL) {
+ if (w->cpu_role.base.level >= PT64_ROOT_4LEVEL) {
root = mmu_alloc_root(vcpu, root_gfn, 0,
mmu->root_role.level);
mmu->root.hpa = root;
@@ -4146,7 +4149,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
for (i = 0; i < 4; ++i) {
WARN_ON_ONCE(IS_VALID_PAE_ROOT(mmu->pae_root[i]));
- if (mmu->w.cpu_role.base.level == PT32E_ROOT_LEVEL) {
+ if (w->cpu_role.base.level == PT32E_ROOT_LEVEL) {
if (!(pdptrs[i] & PT_PRESENT_MASK)) {
mmu->pae_root[i] = INVALID_PAE_ROOT;
continue;
@@ -4160,7 +4163,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
* directory. Othwerise each PAE page direct shadows one guest
* PAE page directory so that quadrant should be 0.
*/
- quadrant = (mmu->w.cpu_role.base.level == PT32_ROOT_LEVEL) ? i : 0;
+ quadrant = (w->cpu_role.base.level == PT32_ROOT_LEVEL) ? i : 0;
root = mmu_alloc_root(vcpu, root_gfn, quadrant, PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | pm_mask;
@@ -4184,6 +4187,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
static int mmu_alloc_special_roots(struct kvm_vcpu *vcpu)
{
struct kvm_mmu *mmu = vcpu->arch.mmu;
+ struct kvm_pagewalk *w = mmu->w;
bool need_pml5 = mmu->root_role.level > PT64_ROOT_4LEVEL;
u64 *pml5_root = NULL;
u64 *pml4_root = NULL;
@@ -4196,7 +4200,7 @@ static int mmu_alloc_special_roots(struct kvm_vcpu *vcpu)
* on demand, as running a 32-bit L1 VMM on 64-bit KVM is very rare.
*/
if (mmu->root_role.direct ||
- mmu->w.cpu_role.base.level >= PT64_ROOT_4LEVEL ||
+ w->cpu_role.base.level >= PT64_ROOT_4LEVEL ||
mmu->root_role.level < PT64_ROOT_4LEVEL)
return 0;
@@ -4301,7 +4305,7 @@ void kvm_mmu_sync_roots(struct kvm_vcpu *vcpu)
vcpu_clear_mmio_info(vcpu, MMIO_GVA_ANY);
- if (vcpu->arch.mmu->w.cpu_role.base.level >= PT64_ROOT_4LEVEL) {
+ if (vcpu->arch.mmu->w->cpu_role.base.level >= PT64_ROOT_4LEVEL) {
hpa_t root = vcpu->arch.mmu->root.hpa;
if (!is_unsync_root(root))
@@ -4543,7 +4547,7 @@ static bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu,
if (arch.direct_map)
arch.cr3 = (unsigned long)INVALID_GPA;
else
- arch.cr3 = kvm_mmu_get_guest_pgd(vcpu, &vcpu->arch.mmu->w);
+ arch.cr3 = kvm_mmu_get_guest_pgd(vcpu, vcpu->arch.mmu->w);
return kvm_setup_async_pf(vcpu, fault->addr,
kvm_vcpu_gfn_to_hva(vcpu, fault->gfn), &arch);
@@ -4565,7 +4569,7 @@ void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu, struct kvm_async_pf *work)
return;
if (!vcpu->arch.mmu->root_role.direct &&
- work->arch.cr3 != kvm_mmu_get_guest_pgd(vcpu, &vcpu->arch.mmu->w))
+ work->arch.cr3 != kvm_mmu_get_guest_pgd(vcpu, vcpu->arch.mmu->w))
return;
r = kvm_mmu_do_page_fault(vcpu, work->cr2_or_gpa, work->arch.error_code,
@@ -5119,7 +5123,6 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_tdp_mmu_map_private_pfn);
static void nonpaging_init_context(struct kvm_mmu *context)
{
context->page_fault = nonpaging_page_fault;
- context->w.gva_to_gpa = nonpaging_gva_to_gpa;
context->sync_spte = NULL;
}
@@ -5434,9 +5437,9 @@ static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
}
static void reset_rsvds_bits_mask_ept(struct kvm_vcpu *vcpu,
- struct kvm_mmu *context, bool execonly, int huge_page_level)
+ bool execonly, int huge_page_level)
{
- __reset_rsvds_bits_mask_ept(&context->w.guest_rsvd_check,
+ __reset_rsvds_bits_mask_ept(&vcpu->arch.ngpa_walk.guest_rsvd_check,
vcpu->arch.reserved_gpa_bits, execonly,
huge_page_level);
}
@@ -5743,21 +5746,19 @@ static void reset_guest_paging_metadata(struct kvm_vcpu *vcpu,
return;
reset_guest_rsvds_bits_mask(vcpu, w);
- update_permission_bitmask(w, w == &vcpu->arch.guest_mmu.w, false);
+ update_permission_bitmask(w, w == &vcpu->arch.ngpa_walk, false);
update_pkru_bitmask(w);
}
static void paging64_init_context(struct kvm_mmu *context)
{
context->page_fault = paging64_page_fault;
- context->w.gva_to_gpa = paging64_gva_to_gpa;
context->sync_spte = paging64_sync_spte;
}
static void paging32_init_context(struct kvm_mmu *context)
{
context->page_fault = paging32_page_fault;
- context->w.gva_to_gpa = paging32_gva_to_gpa;
context->sync_spte = paging32_sync_spte;
}
@@ -5872,49 +5873,31 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
struct kvm_mmu *context = &vcpu->arch.root_mmu;
union kvm_mmu_page_role root_role = kvm_calc_tdp_mmu_root_page_role(vcpu, cpu_role);
- if (cpu_role.as_u64 == context->w.cpu_role.as_u64 &&
- root_role.word == context->root_role.word)
+ if (root_role.word == context->root_role.word)
return;
- context->w.cpu_role.as_u64 = cpu_role.as_u64;
context->root_role.word = root_role.word;
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
- context->w.inject_page_fault = kvm_inject_page_fault;
- context->w.get_pdptr = kvm_pdptr_read;
- context->w.get_guest_pgd = get_guest_cr3;
-
- if (!is_cr0_pg(&context->w))
- context->w.gva_to_gpa = nonpaging_gva_to_gpa;
- else if (is_cr4_pae(&context->w))
- context->w.gva_to_gpa = paging64_gva_to_gpa;
- else
- context->w.gva_to_gpa = paging32_gva_to_gpa;
-
- reset_guest_paging_metadata(vcpu, &context->w);
reset_tdp_shadow_zero_bits_mask(context);
}
static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *context,
- union kvm_cpu_role cpu_role,
union kvm_mmu_page_role root_role)
{
- if (cpu_role.as_u64 == context->w.cpu_role.as_u64 &&
- root_role.word == context->root_role.word)
+ if (root_role.word == context->root_role.word)
return;
- context->w.cpu_role.as_u64 = cpu_role.as_u64;
context->root_role.word = root_role.word;
- if (!is_cr0_pg(&context->w))
+ if (!is_cr0_pg(context->w))
nonpaging_init_context(context);
- else if (is_cr4_pae(&context->w))
+ else if (is_cr4_pae(context->w))
paging64_init_context(context);
else
paging32_init_context(context);
- reset_guest_paging_metadata(vcpu, &context->w);
reset_shadow_zero_bits_mask(vcpu, context);
}
@@ -5940,7 +5923,7 @@ static void kvm_init_shadow_mmu(struct kvm_vcpu *vcpu,
*/
root_role.efer_nx = true;
- shadow_mmu_init_context(vcpu, context, cpu_role, root_role);
+ shadow_mmu_init_context(vcpu, context, root_role);
}
static void init_kvm_page_walk(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
@@ -5980,13 +5963,15 @@ void kvm_init_shadow_npt_mmu(struct kvm_vcpu *vcpu, unsigned long cr4,
WARN_ON_ONCE(cpu_role.base.direct || !cpu_role.base.guest_mode);
cpu_role.base.cr4_smep = (misc_ctl & SVM_MISC_ENABLE_GMET) != 0;
+ init_kvm_page_walk(vcpu, &vcpu->arch.ngpa_walk, cpu_role);
+
root_role = cpu_role.base;
root_role.level = kvm_mmu_get_tdp_level(vcpu);
if (root_role.level == PT64_ROOT_5LEVEL &&
cpu_role.base.level == PT64_ROOT_4LEVEL)
root_role.passthrough = 1;
- shadow_mmu_init_context(vcpu, context, cpu_role, root_role);
+ shadow_mmu_init_context(vcpu, context, root_role);
kvm_mmu_new_pgd(vcpu, nested_cr3);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_init_shadow_npt_mmu);
@@ -6027,18 +6012,20 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
kvm_calc_shadow_ept_root_page_role(vcpu, accessed_dirty,
execonly, level, mbec);
- if (new_mode.as_u64 != context->w.cpu_role.as_u64) {
+ struct kvm_pagewalk *ngpa_walk = &vcpu->arch.ngpa_walk;
+
+ if (new_mode.as_u64 != ngpa_walk->cpu_role.as_u64) {
/* EPT, and thus nested EPT, does not consume CR0, CR4, nor EFER. */
- context->w.cpu_role.as_u64 = new_mode.as_u64;
+ ngpa_walk->cpu_role.as_u64 = new_mode.as_u64;
context->root_role.word = new_mode.base.word;
context->page_fault = ept_page_fault;
- context->w.gva_to_gpa = ept_gva_to_gpa;
+ ngpa_walk->gva_to_gpa = ept_gva_to_gpa;
context->sync_spte = ept_sync_spte;
- update_permission_bitmask(&context->w, true, true);
- context->w.pkru_mask = 0;
- reset_rsvds_bits_mask_ept(vcpu, context, execonly, huge_page_level);
+ update_permission_bitmask(ngpa_walk, true, true);
+ ngpa_walk->pkru_mask = 0;
+ reset_rsvds_bits_mask_ept(vcpu, execonly, huge_page_level);
reset_ept_shadow_zero_bits_mask(context, execonly);
}
@@ -6049,13 +6036,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_init_shadow_ept_mmu);
static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
union kvm_cpu_role cpu_role)
{
- struct kvm_mmu *context = &vcpu->arch.root_mmu;
-
kvm_init_shadow_mmu(vcpu, cpu_role);
-
- context->w.inject_page_fault = kvm_inject_page_fault;
- context->w.get_pdptr = kvm_pdptr_read;
- context->w.get_guest_pgd = get_guest_cr3;
}
void kvm_init_mmu(struct kvm_vcpu *vcpu)
@@ -6090,8 +6071,7 @@ void kvm_mmu_after_set_cpuid(struct kvm_vcpu *vcpu)
*/
vcpu->arch.root_mmu.root_role.invalid = 1;
vcpu->arch.guest_mmu.root_role.invalid = 1;
- vcpu->arch.root_mmu.w.cpu_role.ext.valid = 0;
- vcpu->arch.guest_mmu.w.cpu_role.ext.valid = 0;
+ vcpu->arch.ngpa_walk.cpu_role.ext.valid = 0;
vcpu->arch.gva_walk.cpu_role.ext.valid = 0;
kvm_mmu_reset_context(vcpu);
@@ -6696,11 +6676,12 @@ static void free_mmu_pages(struct kvm_mmu *mmu)
free_page((unsigned long)mmu->pml5_root);
}
-static int __kvm_mmu_create(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu)
+static int __kvm_mmu_create(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu, struct kvm_pagewalk *w)
{
struct page *page;
int i;
+ mmu->w = w;
mmu->root.hpa = INVALID_PAGE;
mmu->root.pgd = 0;
mmu->mirror_root_hpa = INVALID_PAGE;
@@ -6767,11 +6748,11 @@ int kvm_mmu_create(struct kvm_vcpu *vcpu)
vcpu->arch.mmu = &vcpu->arch.root_mmu;
- ret = __kvm_mmu_create(vcpu, &vcpu->arch.guest_mmu);
+ ret = __kvm_mmu_create(vcpu, &vcpu->arch.guest_mmu, &vcpu->arch.ngpa_walk);
if (ret)
return ret;
- ret = __kvm_mmu_create(vcpu, &vcpu->arch.root_mmu);
+ ret = __kvm_mmu_create(vcpu, &vcpu->arch.root_mmu, &vcpu->arch.gva_walk);
if (ret)
goto fail_allocate_root;
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index b12c6b5e4a2f..088b86d228c3 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -157,7 +157,7 @@ static bool FNAME(prefetch_invalid_gpte)(struct kvm_vcpu *vcpu,
struct kvm_mmu_page *sp, u64 *spte,
u64 gpte)
{
- struct kvm_pagewalk *w = &vcpu->arch.mmu->w;
+ struct kvm_pagewalk *w = vcpu->arch.mmu->w;
if (!FNAME(is_present_gpte)(w, gpte))
goto no_present;
@@ -551,7 +551,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
static int FNAME(walk_addr)(struct guest_walker *walker,
struct kvm_vcpu *vcpu, gpa_t addr, u64 access)
{
- return FNAME(walk_addr_generic)(walker, vcpu, &vcpu->arch.mmu->w, addr,
+ return FNAME(walk_addr_generic)(walker, vcpu, vcpu->arch.mmu->w, addr,
access);
}
@@ -567,7 +567,7 @@ FNAME(prefetch_gpte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
gfn = gpte_to_gfn(gpte);
pte_access = sp->role.access & FNAME(gpte_access)(gpte);
- FNAME(protect_clean_gpte)(&vcpu->arch.mmu->w, &pte_access, gpte);
+ FNAME(protect_clean_gpte)(vcpu->arch.mmu->w, &pte_access, gpte);
return kvm_mmu_prefetch_sptes(vcpu, gfn, spte, 1, pte_access);
}
@@ -650,7 +650,7 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
WARN_ON_ONCE(gw->gfn != base_gfn);
direct_access = gw->pte_access;
- top_level = vcpu->arch.mmu->w.cpu_role.base.level;
+ top_level = vcpu->arch.mmu->w->cpu_role.base.level;
if (top_level == PT32E_ROOT_LEVEL)
top_level = PT32_ROOT_LEVEL;
/*
@@ -839,7 +839,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
* otherwise KVM will cache incorrect access information in the SPTE.
*/
if (fault->write && !(walker.pte_access & ACC_WRITE_MASK) &&
- !is_cr0_wp(&vcpu->arch.mmu->w) && !fault->user && fault->slot) {
+ !is_cr0_wp(vcpu->arch.mmu->w) && !fault->user && fault->slot) {
walker.pte_access |= ACC_WRITE_MASK;
walker.pte_access &= ~ACC_USER_MASK;
@@ -849,7 +849,7 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault
* then we should prevent the kernel from executing it
* if SMEP is enabled.
*/
- if (is_cr4_smep(&vcpu->arch.mmu->w))
+ if (is_cr4_smep(vcpu->arch.mmu->w))
walker.pte_access &= ~ACC_EXEC_MASK;
}
#endif
@@ -947,7 +947,7 @@ static int FNAME(sync_spte)(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp, int
gfn = gpte_to_gfn(gpte);
pte_access = sp->role.access;
pte_access &= FNAME(gpte_access)(gpte);
- FNAME(protect_clean_gpte)(&vcpu->arch.mmu->w, &pte_access, gpte);
+ FNAME(protect_clean_gpte)(vcpu->arch.mmu->w, &pte_access, gpte);
if (sync_mmio_spte(vcpu, &sp->spt[i], gfn, pte_access))
return 0;
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 7853bd9ed6cc..e93d2e9a9aa4 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -98,10 +98,9 @@ static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)
svm->nested.ctl.nested_cr3,
svm->nested.ctl.misc_ctl);
- vcpu->arch.mmu->w.get_guest_pgd = nested_svm_get_tdp_cr3;
- vcpu->arch.mmu->w.get_pdptr = nested_svm_get_tdp_pdptr;
-
- vcpu->arch.mmu->w.inject_page_fault = nested_svm_inject_npf_exit;
+ vcpu->arch.ngpa_walk.get_guest_pgd = nested_svm_get_tdp_cr3;
+ vcpu->arch.ngpa_walk.get_pdptr = nested_svm_get_tdp_pdptr;
+ vcpu->arch.ngpa_walk.inject_page_fault = nested_svm_inject_npf_exit;
}
static void nested_svm_uninit_mmu_context(struct kvm_vcpu *vcpu)
@@ -2094,7 +2093,7 @@ static gpa_t svm_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
u64 pte_access)
{
struct vcpu_svm *svm = to_svm(vcpu);
- struct kvm_pagewalk *w = &vcpu->arch.mmu->w;
+ struct kvm_pagewalk *w = &vcpu->arch.ngpa_walk;
BUG_ON(!mmu_is_nested(vcpu));
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 449efad7ea1f..974116fff635 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -407,7 +407,7 @@ static void nested_ept_invalidate_addr(struct kvm_vcpu *vcpu, gpa_t eptp,
roots |= KVM_MMU_ROOT_PREVIOUS(i);
}
if (roots)
- kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.guest_mmu.w, addr, roots);
+ kvm_mmu_invalidate_addr(vcpu, &vcpu->arch.ngpa_walk, addr, roots);
}
static void nested_ept_inject_page_fault(struct kvm_vcpu *vcpu,
@@ -494,10 +494,10 @@ static void nested_ept_init_mmu_context(struct kvm_vcpu *vcpu)
vcpu->arch.mmu = &vcpu->arch.guest_mmu;
nested_ept_new_eptp(vcpu);
- vcpu->arch.mmu->w.get_guest_pgd = nested_ept_get_eptp;
- vcpu->arch.mmu->w.get_pdptr = kvm_pdptr_read;
+ vcpu->arch.ngpa_walk.get_guest_pgd = nested_ept_get_eptp;
+ vcpu->arch.ngpa_walk.get_pdptr = kvm_pdptr_read;
- vcpu->arch.mmu->w.inject_page_fault = nested_ept_inject_page_fault;
+ vcpu->arch.ngpa_walk.inject_page_fault = nested_ept_inject_page_fault;
}
static void nested_ept_uninit_mmu_context(struct kvm_vcpu *vcpu)
@@ -7442,12 +7442,13 @@ __init int nested_vmx_hardware_setup(int (*exit_handlers[])(struct kvm_vcpu *))
return 0;
}
+
static gpa_t vmx_translate_nested_gpa(struct kvm_vcpu *vcpu, gpa_t gpa,
u64 access,
struct x86_exception *exception,
u64 pte_access)
{
- struct kvm_pagewalk *w = &vcpu->arch.mmu->w;
+ struct kvm_pagewalk *w = &vcpu->arch.ngpa_walk;
BUG_ON(!mmu_is_nested(vcpu));
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 14af0f4d010e..35094997e70a 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -994,7 +994,7 @@ void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
WARN_ON_ONCE(fault->vector != PF_VECTOR);
- fault_walk = fault->nested_page_fault ? &vcpu->arch.mmu->w :
+ fault_walk = fault->nested_page_fault ? &vcpu->arch.ngpa_walk :
&vcpu->arch.gva_walk;
/*
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 20/24] KVM: x86/mmu: cleanup functions that initialize shadow MMU
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (18 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 19/24] KVM: x86/mmu: pull struct kvm_pagewalk out of struct kvm_mmu Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 21/24] KVM: x86/mmu: pull page format to a new struct Paolo Bonzini
` (3 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Now that the GVA->GPA page walker is initialized independently,
init_kvm_softmmu() does not do anything more than calling
kvm_init_shadow_mmu() so eliminate it from the call chain.
At the same time, rename kvm_init_shadow_mmu() to
init_kvm_shadow_mmu() for consistency with init_kvm_tdp_mmu().
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index bd307e9b3fd6..e444536768ba 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5901,7 +5901,7 @@ static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *conte
reset_shadow_zero_bits_mask(vcpu, context);
}
-static void kvm_init_shadow_mmu(struct kvm_vcpu *vcpu,
+static void init_kvm_shadow_mmu(struct kvm_vcpu *vcpu,
union kvm_cpu_role cpu_role)
{
struct kvm_mmu *context = &vcpu->arch.root_mmu;
@@ -6033,12 +6033,6 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_init_shadow_ept_mmu);
-static void init_kvm_softmmu(struct kvm_vcpu *vcpu,
- union kvm_cpu_role cpu_role)
-{
- kvm_init_shadow_mmu(vcpu, cpu_role);
-}
-
void kvm_init_mmu(struct kvm_vcpu *vcpu)
{
struct kvm_mmu_role_regs regs = vcpu_to_role_regs(vcpu);
@@ -6050,7 +6044,7 @@ void kvm_init_mmu(struct kvm_vcpu *vcpu)
if (tdp_enabled)
init_kvm_tdp_mmu(vcpu, cpu_role);
else
- init_kvm_softmmu(vcpu, cpu_role);
+ init_kvm_shadow_mmu(vcpu, cpu_role);
}
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_init_mmu);
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 21/24] KVM: x86/mmu: pull page format to a new struct
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (19 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 20/24] KVM: x86/mmu: cleanup functions that initialize shadow MMU Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 22/24] KVM: x86/mmu: merge struct rsvd_bits_validate into struct kvm_page_format Paolo Bonzini
` (2 subsequent siblings)
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
KVM is doing reserved bits checks on both guest and host page tables,
though the latter are only for consistency. Create a new struct
for this common code as well as for all data that is extracted from
the CPU role.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 23 ++++++++++++++---------
arch/x86/kvm/mmu.h | 7 ++++---
arch/x86/kvm/mmu/mmu.c | 16 ++++++++--------
arch/x86/kvm/mmu/paging_tmpl.h | 10 +++++-----
4 files changed, 31 insertions(+), 25 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index ce39230eaebb..08fb47f2b7fc 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -479,15 +479,7 @@ struct kvm_page_fault;
* and 2-level 32-bit). The kvm_pagewalk structure abstracts the details of the
* current mmu mode.
*/
-struct kvm_pagewalk {
- unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
- u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
- void (*inject_page_fault)(struct kvm_vcpu *vcpu,
- struct x86_exception *fault);
- gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
- gpa_t gva_or_gpa, u64 access,
- struct x86_exception *exception);
- union kvm_cpu_role cpu_role;
+struct kvm_page_format {
struct rsvd_bits_validate guest_rsvd_check;
/*
@@ -506,6 +498,19 @@ struct kvm_pagewalk {
u16 permissions[16];
};
+struct kvm_pagewalk {
+ unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
+ u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
+ void (*inject_page_fault)(struct kvm_vcpu *vcpu,
+ struct x86_exception *fault);
+ gpa_t (*gva_to_gpa)(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
+ gpa_t gva_or_gpa, u64 access,
+ struct x86_exception *exception);
+
+ union kvm_cpu_role cpu_role;
+ struct kvm_page_format fmt;
+};
+
struct kvm_mmu {
int (*page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault);
int (*sync_spte)(struct kvm_vcpu *vcpu,
diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
index 58eb98585a29..f604726d5b29 100644
--- a/arch/x86/kvm/mmu.h
+++ b/arch/x86/kvm/mmu.h
@@ -217,15 +217,16 @@ static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
u64 implicit_access = access & PFERR_IMPLICIT_ACCESS;
bool not_smap = ((rflags & X86_EFLAGS_AC) | implicit_access) == X86_EFLAGS_AC;
int index = (pfec | (not_smap ? PFERR_RSVD_MASK : 0)) >> 1;
+ struct kvm_page_format *fmt = &w->fmt;
u32 errcode = PFERR_PRESENT_MASK;
bool fault;
kvm_mmu_refresh_passthrough_bits(vcpu, w);
- fault = (w->permissions[index] >> pte_access) & 1;
+ fault = (fmt->permissions[index] >> pte_access) & 1;
WARN_ON_ONCE(pfec & (PFERR_PK_MASK | PFERR_SS_MASK | PFERR_RSVD_MASK));
- if (unlikely(w->pkru_mask)) {
+ if (unlikely(fmt->pkru_mask)) {
u32 pkru_bits, offset;
/*
@@ -239,7 +240,7 @@ static inline u8 permission_fault(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
/* clear present bit, replace PFEC.RSVD with ACC_USER_MASK. */
offset = (pfec & ~1) | ((pte_access & PT_USER_MASK) ? PFERR_RSVD_MASK : 0);
- pkru_bits &= w->pkru_mask >> offset;
+ pkru_bits &= fmt->pkru_mask >> offset;
errcode |= -pkru_bits & PFERR_PK_MASK;
fault |= (pkru_bits != 0);
}
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index e444536768ba..420bd70fb54a 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5390,7 +5390,7 @@ static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
struct kvm_pagewalk *w)
{
- __reset_rsvds_bits_mask(&w->guest_rsvd_check,
+ __reset_rsvds_bits_mask(&w->fmt.guest_rsvd_check,
vcpu->arch.reserved_gpa_bits,
w->cpu_role.base.level, is_efer_nx(w),
guest_cpu_cap_has(vcpu, X86_FEATURE_GBPAGES),
@@ -5439,7 +5439,7 @@ static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
static void reset_rsvds_bits_mask_ept(struct kvm_vcpu *vcpu,
bool execonly, int huge_page_level)
{
- __reset_rsvds_bits_mask_ept(&vcpu->arch.ngpa_walk.guest_rsvd_check,
+ __reset_rsvds_bits_mask_ept(&vcpu->arch.ngpa_walk.fmt.guest_rsvd_check,
vcpu->arch.reserved_gpa_bits, execonly,
huge_page_level);
}
@@ -5593,7 +5593,7 @@ static void update_permission_bitmask(struct kvm_pagewalk *pw, bool tdp, bool ep
* permission_fault() to indicate accesses that are *not* subject to
* SMAP restrictions.
*/
- for (index = 0; index < ARRAY_SIZE(pw->permissions); ++index) {
+ for (index = 0; index < ARRAY_SIZE(pw->fmt.permissions); ++index) {
unsigned pfec = index << 1;
/*
@@ -5667,7 +5667,7 @@ static void update_permission_bitmask(struct kvm_pagewalk *pw, bool tdp, bool ep
smapf = (pfec & (PFERR_RSVD_MASK|PFERR_FETCH_MASK)) ? 0 : kf;
}
- pw->permissions[index] = ff | uf | wf | rf | smapf;
+ pw->fmt.permissions[index] = ff | uf | wf | rf | smapf;
}
}
@@ -5700,14 +5700,14 @@ static void update_pkru_bitmask(struct kvm_pagewalk *w)
unsigned bit;
bool wp;
- w->pkru_mask = 0;
+ w->fmt.pkru_mask = 0;
if (!is_cr4_pke(w))
return;
wp = is_cr0_wp(w);
- for (bit = 0; bit < ARRAY_SIZE(w->permissions); ++bit) {
+ for (bit = 0; bit < ARRAY_SIZE(w->fmt.permissions); ++bit) {
unsigned pfec, pkey_bits;
bool check_pkey, check_write, ff, uf, wf, pte_user;
@@ -5735,7 +5735,7 @@ static void update_pkru_bitmask(struct kvm_pagewalk *w)
/* PKRU.WD stops write access. */
pkey_bits |= (!!check_write) << 1;
- w->pkru_mask |= (pkey_bits & 3) << pfec;
+ w->fmt.pkru_mask |= (pkey_bits & 3) << pfec;
}
}
@@ -6024,7 +6024,7 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
context->sync_spte = ept_sync_spte;
update_permission_bitmask(ngpa_walk, true, true);
- ngpa_walk->pkru_mask = 0;
+ ngpa_walk->fmt.pkru_mask = 0;
reset_rsvds_bits_mask_ept(vcpu, execonly, huge_page_level);
reset_ept_shadow_zero_bits_mask(context, execonly);
}
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index 088b86d228c3..fe12e9d17b0e 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -147,10 +147,10 @@ static bool FNAME(is_bad_mt_xwr)(struct rsvd_bits_validate *rsvd_check, u64 gpte
#endif
}
-static bool FNAME(is_rsvd_bits_set)(struct kvm_pagewalk *w, u64 gpte, int level)
+static bool FNAME(is_rsvd_bits_set)(struct kvm_page_format *fmt, u64 gpte, int level)
{
- return __is_rsvd_bits_set(&w->guest_rsvd_check, gpte, level) ||
- FNAME(is_bad_mt_xwr)(&w->guest_rsvd_check, gpte);
+ return __is_rsvd_bits_set(&fmt->guest_rsvd_check, gpte, level) ||
+ FNAME(is_bad_mt_xwr)(&fmt->guest_rsvd_check, gpte);
}
static bool FNAME(prefetch_invalid_gpte)(struct kvm_vcpu *vcpu,
@@ -167,7 +167,7 @@ static bool FNAME(prefetch_invalid_gpte)(struct kvm_vcpu *vcpu,
!(gpte & PT_GUEST_ACCESSED_MASK))
goto no_present;
- if (FNAME(is_rsvd_bits_set)(w, gpte, PG_LEVEL_4K))
+ if (FNAME(is_rsvd_bits_set)(&w->fmt, gpte, PG_LEVEL_4K))
goto no_present;
return false;
@@ -431,7 +431,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
if (unlikely(!FNAME(is_present_gpte)(w, pte)))
goto error;
- if (unlikely(FNAME(is_rsvd_bits_set)(w, pte, walker->level))) {
+ if (unlikely(FNAME(is_rsvd_bits_set)(&w->fmt, pte, walker->level))) {
errcode = PFERR_RSVD_MASK | PFERR_PRESENT_MASK;
goto error;
}
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 22/24] KVM: x86/mmu: merge struct rsvd_bits_validate into struct kvm_page_format
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (20 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 21/24] KVM: x86/mmu: pull page format to a new struct Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 23/24] KVM: x86/mmu: parameterize update_permission_bitmask() Paolo Bonzini
2026-06-03 10:58 ` [PATCH 24/24] KVM: x86/mmu: use kvm_page_format to test SPTEs Paolo Bonzini
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Remove one level of indirection, and prepare for using the permission bitmask
machinery for shadow pages as well.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 38 +++++------
arch/x86/kvm/mmu/mmu.c | 116 ++++++++++++++++----------------
arch/x86/kvm/mmu/paging_tmpl.h | 8 +--
arch/x86/kvm/mmu/spte.c | 4 +-
arch/x86/kvm/mmu/spte.h | 18 ++---
arch/x86/kvm/vmx/vmx.c | 2 +-
6 files changed, 91 insertions(+), 95 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 08fb47f2b7fc..7c6ac551a2d9 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -450,9 +450,24 @@ struct kvm_pio_request {
#define PT64_ROOT_MAX_LEVEL 5
-struct rsvd_bits_validate {
+struct kvm_page_format {
u64 rsvd_bits_mask[2][PT64_ROOT_MAX_LEVEL];
u64 bad_mt_xwr;
+
+ /*
+ * The pkru_mask indicates if protection key checks are needed. It
+ * consists of 16 domains indexed by page fault error code bits [4:1],
+ * with PFEC.RSVD replaced by ACC_USER_MASK from the page tables.
+ * Each domain has 2 bits which are ANDed with AD and WD from PKRU.
+ */
+ u32 pkru_mask;
+
+ /*
+ * Bitmap; bit set = permission fault
+ * Array index: page fault error code [4:1]
+ * Bit index: pte permissions in ACC_* format
+ */
+ u16 permissions[16];
};
struct kvm_mmu_root_info {
@@ -479,25 +494,6 @@ struct kvm_page_fault;
* and 2-level 32-bit). The kvm_pagewalk structure abstracts the details of the
* current mmu mode.
*/
-struct kvm_page_format {
- struct rsvd_bits_validate guest_rsvd_check;
-
- /*
- * The pkru_mask indicates if protection key checks are needed. It
- * consists of 16 domains indexed by page fault error code bits [4:1],
- * with PFEC.RSVD replaced by ACC_USER_MASK from the page tables.
- * Each domain has 2 bits which are ANDed with AD and WD from PKRU.
- */
- u32 pkru_mask;
-
- /*
- * Bitmap; bit set = permission fault
- * Array index: page fault error code [4:1]
- * Bit index: pte permissions in ACC_* format
- */
- u16 permissions[16];
-};
-
struct kvm_pagewalk {
unsigned long (*get_guest_pgd)(struct kvm_vcpu *vcpu);
u64 (*get_pdptr)(struct kvm_vcpu *vcpu, int index);
@@ -532,7 +528,7 @@ struct kvm_mmu {
* bits include not only hardware reserved bits but also
* the bits spte never used.
*/
- struct rsvd_bits_validate shadow_zero_check;
+ struct kvm_page_format fmt;
};
enum pmc_type {
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 420bd70fb54a..29755afe5b46 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4422,7 +4422,7 @@ static int get_sptes_lockless(struct kvm_vcpu *vcpu, u64 addr, u64 *sptes,
static bool get_mmio_spte(struct kvm_vcpu *vcpu, u64 addr, u64 *sptep)
{
u64 sptes[PT64_ROOT_MAX_LEVEL + 1];
- struct rsvd_bits_validate *rsvd_check;
+ struct kvm_page_format *rsvd_check;
int root, leaf, level;
bool reserved = false;
@@ -4443,7 +4443,7 @@ static bool get_mmio_spte(struct kvm_vcpu *vcpu, u64 addr, u64 *sptep)
if (!is_shadow_present_pte(sptes[leaf]))
leaf++;
- rsvd_check = &vcpu->arch.mmu->shadow_zero_check;
+ rsvd_check = &vcpu->arch.mmu->fmt;
for (level = root; level >= leaf; level--)
reserved |= is_rsvd_spte(rsvd_check, sptes[level], level);
@@ -5298,7 +5298,7 @@ static bool sync_mmio_spte(struct kvm_vcpu *vcpu, u64 *sptep, gfn_t gfn,
#include "paging_tmpl.h"
#undef PTTYPE
-static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
+static void __reset_rsvds_bits_mask(struct kvm_page_format *fmt,
u64 pa_bits_rsvd, int level, bool nx,
bool gbpages, bool pse, bool amd)
{
@@ -5306,7 +5306,7 @@ static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
u64 nonleaf_bit8_rsvd = 0;
u64 high_bits_rsvd;
- rsvd_check->bad_mt_xwr = 0;
+ fmt->bad_mt_xwr = 0;
if (!gbpages)
gbpages_bit_rsvd = rsvd_bits(7, 7);
@@ -5330,59 +5330,59 @@ static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
switch (level) {
case PT32_ROOT_LEVEL:
/* no rsvd bits for 2 level 4K page table entries */
- rsvd_check->rsvd_bits_mask[0][1] = 0;
- rsvd_check->rsvd_bits_mask[0][0] = 0;
- rsvd_check->rsvd_bits_mask[1][0] =
- rsvd_check->rsvd_bits_mask[0][0];
+ fmt->rsvd_bits_mask[0][1] = 0;
+ fmt->rsvd_bits_mask[0][0] = 0;
+ fmt->rsvd_bits_mask[1][0] =
+ fmt->rsvd_bits_mask[0][0];
if (!pse) {
- rsvd_check->rsvd_bits_mask[1][1] = 0;
+ fmt->rsvd_bits_mask[1][1] = 0;
break;
}
if (is_cpuid_PSE36())
/* 36bits PSE 4MB page */
- rsvd_check->rsvd_bits_mask[1][1] = rsvd_bits(17, 21);
+ fmt->rsvd_bits_mask[1][1] = rsvd_bits(17, 21);
else
/* 32 bits PSE 4MB page */
- rsvd_check->rsvd_bits_mask[1][1] = rsvd_bits(13, 21);
+ fmt->rsvd_bits_mask[1][1] = rsvd_bits(13, 21);
break;
case PT32E_ROOT_LEVEL:
- rsvd_check->rsvd_bits_mask[0][2] = rsvd_bits(63, 63) |
+ fmt->rsvd_bits_mask[0][2] = rsvd_bits(63, 63) |
high_bits_rsvd |
rsvd_bits(5, 8) |
rsvd_bits(1, 2); /* PDPTE */
- rsvd_check->rsvd_bits_mask[0][1] = high_bits_rsvd; /* PDE */
- rsvd_check->rsvd_bits_mask[0][0] = high_bits_rsvd; /* PTE */
- rsvd_check->rsvd_bits_mask[1][1] = high_bits_rsvd |
+ fmt->rsvd_bits_mask[0][1] = high_bits_rsvd; /* PDE */
+ fmt->rsvd_bits_mask[0][0] = high_bits_rsvd; /* PTE */
+ fmt->rsvd_bits_mask[1][1] = high_bits_rsvd |
rsvd_bits(13, 20); /* large page */
- rsvd_check->rsvd_bits_mask[1][0] =
- rsvd_check->rsvd_bits_mask[0][0];
+ fmt->rsvd_bits_mask[1][0] =
+ fmt->rsvd_bits_mask[0][0];
break;
case PT64_ROOT_5LEVEL:
- rsvd_check->rsvd_bits_mask[0][4] = high_bits_rsvd |
+ fmt->rsvd_bits_mask[0][4] = high_bits_rsvd |
nonleaf_bit8_rsvd |
rsvd_bits(7, 7);
- rsvd_check->rsvd_bits_mask[1][4] =
- rsvd_check->rsvd_bits_mask[0][4];
+ fmt->rsvd_bits_mask[1][4] =
+ fmt->rsvd_bits_mask[0][4];
fallthrough;
case PT64_ROOT_4LEVEL:
- rsvd_check->rsvd_bits_mask[0][3] = high_bits_rsvd |
+ fmt->rsvd_bits_mask[0][3] = high_bits_rsvd |
nonleaf_bit8_rsvd |
rsvd_bits(7, 7);
- rsvd_check->rsvd_bits_mask[0][2] = high_bits_rsvd |
+ fmt->rsvd_bits_mask[0][2] = high_bits_rsvd |
gbpages_bit_rsvd;
- rsvd_check->rsvd_bits_mask[0][1] = high_bits_rsvd;
- rsvd_check->rsvd_bits_mask[0][0] = high_bits_rsvd;
- rsvd_check->rsvd_bits_mask[1][3] =
- rsvd_check->rsvd_bits_mask[0][3];
- rsvd_check->rsvd_bits_mask[1][2] = high_bits_rsvd |
+ fmt->rsvd_bits_mask[0][1] = high_bits_rsvd;
+ fmt->rsvd_bits_mask[0][0] = high_bits_rsvd;
+ fmt->rsvd_bits_mask[1][3] =
+ fmt->rsvd_bits_mask[0][3];
+ fmt->rsvd_bits_mask[1][2] = high_bits_rsvd |
gbpages_bit_rsvd |
rsvd_bits(13, 29);
- rsvd_check->rsvd_bits_mask[1][1] = high_bits_rsvd |
+ fmt->rsvd_bits_mask[1][1] = high_bits_rsvd |
rsvd_bits(13, 20); /* large page */
- rsvd_check->rsvd_bits_mask[1][0] =
- rsvd_check->rsvd_bits_mask[0][0];
+ fmt->rsvd_bits_mask[1][0] =
+ fmt->rsvd_bits_mask[0][0];
break;
}
}
@@ -5390,7 +5390,7 @@ static void __reset_rsvds_bits_mask(struct rsvd_bits_validate *rsvd_check,
static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
struct kvm_pagewalk *w)
{
- __reset_rsvds_bits_mask(&w->fmt.guest_rsvd_check,
+ __reset_rsvds_bits_mask(&w->fmt,
vcpu->arch.reserved_gpa_bits,
w->cpu_role.base.level, is_efer_nx(w),
guest_cpu_cap_has(vcpu, X86_FEATURE_GBPAGES),
@@ -5398,7 +5398,7 @@ static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
guest_cpuid_is_amd_compatible(vcpu));
}
-static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
+static void __reset_rsvds_bits_mask_ept(struct kvm_page_format *fmt,
u64 pa_bits_rsvd, bool execonly,
int huge_page_level)
{
@@ -5411,18 +5411,18 @@ static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
if (huge_page_level < PG_LEVEL_2M)
large_2m_rsvd = rsvd_bits(7, 7);
- rsvd_check->rsvd_bits_mask[0][4] = high_bits_rsvd | rsvd_bits(3, 7);
- rsvd_check->rsvd_bits_mask[0][3] = high_bits_rsvd | rsvd_bits(3, 7);
- rsvd_check->rsvd_bits_mask[0][2] = high_bits_rsvd | rsvd_bits(3, 6) | large_1g_rsvd;
- rsvd_check->rsvd_bits_mask[0][1] = high_bits_rsvd | rsvd_bits(3, 6) | large_2m_rsvd;
- rsvd_check->rsvd_bits_mask[0][0] = high_bits_rsvd;
+ fmt->rsvd_bits_mask[0][4] = high_bits_rsvd | rsvd_bits(3, 7);
+ fmt->rsvd_bits_mask[0][3] = high_bits_rsvd | rsvd_bits(3, 7);
+ fmt->rsvd_bits_mask[0][2] = high_bits_rsvd | rsvd_bits(3, 6) | large_1g_rsvd;
+ fmt->rsvd_bits_mask[0][1] = high_bits_rsvd | rsvd_bits(3, 6) | large_2m_rsvd;
+ fmt->rsvd_bits_mask[0][0] = high_bits_rsvd;
/* large page */
- rsvd_check->rsvd_bits_mask[1][4] = rsvd_check->rsvd_bits_mask[0][4];
- rsvd_check->rsvd_bits_mask[1][3] = rsvd_check->rsvd_bits_mask[0][3];
- rsvd_check->rsvd_bits_mask[1][2] = high_bits_rsvd | rsvd_bits(12, 29) | large_1g_rsvd;
- rsvd_check->rsvd_bits_mask[1][1] = high_bits_rsvd | rsvd_bits(12, 20) | large_2m_rsvd;
- rsvd_check->rsvd_bits_mask[1][0] = rsvd_check->rsvd_bits_mask[0][0];
+ fmt->rsvd_bits_mask[1][4] = fmt->rsvd_bits_mask[0][4];
+ fmt->rsvd_bits_mask[1][3] = fmt->rsvd_bits_mask[0][3];
+ fmt->rsvd_bits_mask[1][2] = high_bits_rsvd | rsvd_bits(12, 29) | large_1g_rsvd;
+ fmt->rsvd_bits_mask[1][1] = high_bits_rsvd | rsvd_bits(12, 20) | large_2m_rsvd;
+ fmt->rsvd_bits_mask[1][0] = fmt->rsvd_bits_mask[0][0];
bad_mt_xwr = 0xFFull << (2 * 8); /* bits 3..5 must not be 2 */
bad_mt_xwr |= 0xFFull << (3 * 8); /* bits 3..5 must not be 3 */
@@ -5433,13 +5433,13 @@ static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
/* bits 0..2 must not be 100 unless VMX capabilities allow it */
bad_mt_xwr |= REPEAT_BYTE(1ull << 4);
}
- rsvd_check->bad_mt_xwr = bad_mt_xwr;
+ fmt->bad_mt_xwr = bad_mt_xwr;
}
static void reset_rsvds_bits_mask_ept(struct kvm_vcpu *vcpu,
bool execonly, int huge_page_level)
{
- __reset_rsvds_bits_mask_ept(&vcpu->arch.ngpa_walk.fmt.guest_rsvd_check,
+ __reset_rsvds_bits_mask_ept(&vcpu->arch.ngpa_walk.fmt,
vcpu->arch.reserved_gpa_bits, execonly,
huge_page_level);
}
@@ -5461,13 +5461,13 @@ static void reset_shadow_zero_bits_mask(struct kvm_vcpu *vcpu,
bool is_amd = true;
/* KVM doesn't use 2-level page tables for the shadow MMU. */
bool is_pse = false;
- struct rsvd_bits_validate *shadow_zero_check;
+ struct kvm_page_format *fmt;
int i;
WARN_ON_ONCE(context->root_role.level < PT32E_ROOT_LEVEL);
- shadow_zero_check = &context->shadow_zero_check;
- __reset_rsvds_bits_mask(shadow_zero_check, reserved_hpa_bits(),
+ fmt = &context->fmt;
+ __reset_rsvds_bits_mask(fmt, reserved_hpa_bits(),
context->root_role.level,
context->root_role.efer_nx,
guest_cpu_cap_has(vcpu, X86_FEATURE_GBPAGES),
@@ -5483,10 +5483,10 @@ static void reset_shadow_zero_bits_mask(struct kvm_vcpu *vcpu,
* Bits in shadow_me_mask but not in shadow_me_value are
* not allowed to be set.
*/
- shadow_zero_check->rsvd_bits_mask[0][i] |= shadow_me_mask;
- shadow_zero_check->rsvd_bits_mask[1][i] |= shadow_me_mask;
- shadow_zero_check->rsvd_bits_mask[0][i] &= ~shadow_me_value;
- shadow_zero_check->rsvd_bits_mask[1][i] &= ~shadow_me_value;
+ fmt->rsvd_bits_mask[0][i] |= shadow_me_mask;
+ fmt->rsvd_bits_mask[1][i] |= shadow_me_mask;
+ fmt->rsvd_bits_mask[0][i] &= ~shadow_me_value;
+ fmt->rsvd_bits_mask[1][i] &= ~shadow_me_value;
}
}
@@ -5503,18 +5503,18 @@ static inline bool boot_cpu_is_amd(void)
*/
static void reset_tdp_shadow_zero_bits_mask(struct kvm_mmu *context)
{
- struct rsvd_bits_validate *shadow_zero_check;
+ struct kvm_page_format *fmt;
int i;
- shadow_zero_check = &context->shadow_zero_check;
+ fmt = &context->fmt;
if (boot_cpu_is_amd())
- __reset_rsvds_bits_mask(shadow_zero_check, reserved_hpa_bits(),
+ __reset_rsvds_bits_mask(fmt, reserved_hpa_bits(),
context->root_role.level, true,
boot_cpu_has(X86_FEATURE_GBPAGES),
false, true);
else
- __reset_rsvds_bits_mask_ept(shadow_zero_check,
+ __reset_rsvds_bits_mask_ept(fmt,
reserved_hpa_bits(), false,
max_huge_page_level);
@@ -5522,8 +5522,8 @@ static void reset_tdp_shadow_zero_bits_mask(struct kvm_mmu *context)
return;
for (i = context->root_role.level; --i >= 0;) {
- shadow_zero_check->rsvd_bits_mask[0][i] &= ~shadow_me_mask;
- shadow_zero_check->rsvd_bits_mask[1][i] &= ~shadow_me_mask;
+ fmt->rsvd_bits_mask[0][i] &= ~shadow_me_mask;
+ fmt->rsvd_bits_mask[1][i] &= ~shadow_me_mask;
}
}
@@ -5534,7 +5534,7 @@ static void reset_tdp_shadow_zero_bits_mask(struct kvm_mmu *context)
static void
reset_ept_shadow_zero_bits_mask(struct kvm_mmu *context, bool execonly)
{
- __reset_rsvds_bits_mask_ept(&context->shadow_zero_check,
+ __reset_rsvds_bits_mask_ept(&context->fmt,
reserved_hpa_bits(), execonly,
max_huge_page_level);
}
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index fe12e9d17b0e..625fe35a1911 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -138,19 +138,19 @@ static inline int FNAME(is_present_gpte)(struct kvm_pagewalk *w,
#endif
}
-static bool FNAME(is_bad_mt_xwr)(struct rsvd_bits_validate *rsvd_check, u64 gpte)
+static bool FNAME(is_bad_mt_xwr)(struct kvm_page_format *fmt, u64 gpte)
{
#if PTTYPE != PTTYPE_EPT
return false;
#else
- return __is_bad_mt_xwr(rsvd_check, gpte);
+ return __is_bad_mt_xwr(fmt, gpte);
#endif
}
static bool FNAME(is_rsvd_bits_set)(struct kvm_page_format *fmt, u64 gpte, int level)
{
- return __is_rsvd_bits_set(&fmt->guest_rsvd_check, gpte, level) ||
- FNAME(is_bad_mt_xwr)(&fmt->guest_rsvd_check, gpte);
+ return __is_rsvd_bits_set(fmt, gpte, level) ||
+ FNAME(is_bad_mt_xwr)(fmt, gpte);
}
static bool FNAME(prefetch_invalid_gpte)(struct kvm_vcpu *vcpu,
diff --git a/arch/x86/kvm/mmu/spte.c b/arch/x86/kvm/mmu/spte.c
index d2f5f7dd8fe1..bdf72a98c19c 100644
--- a/arch/x86/kvm/mmu/spte.c
+++ b/arch/x86/kvm/mmu/spte.c
@@ -280,9 +280,9 @@ bool make_spte(struct kvm_vcpu *vcpu, struct kvm_mmu_page *sp,
if (prefetch && !synchronizing)
spte = mark_spte_for_access_track(spte);
- WARN_ONCE(is_rsvd_spte(&vcpu->arch.mmu->shadow_zero_check, spte, level),
+ WARN_ONCE(is_rsvd_spte(&vcpu->arch.mmu->fmt, spte, level),
"spte = 0x%llx, level = %d, rsvd bits = 0x%llx", spte, level,
- get_rsvd_bits(&vcpu->arch.mmu->shadow_zero_check, spte, level));
+ get_rsvd_bits(&vcpu->arch.mmu->fmt, spte, level));
/*
* Mark the memslot dirty *after* modifying it for access tracking.
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 13eea94dd212..918533e61b98 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -378,33 +378,33 @@ static inline bool is_accessed_spte(u64 spte)
return spte & shadow_accessed_mask;
}
-static inline u64 get_rsvd_bits(struct rsvd_bits_validate *rsvd_check, u64 pte,
+static inline u64 get_rsvd_bits(struct kvm_page_format *fmt, u64 pte,
int level)
{
int bit7 = (pte >> 7) & 1;
- return rsvd_check->rsvd_bits_mask[bit7][level-1];
+ return fmt->rsvd_bits_mask[bit7][level-1];
}
-static inline bool __is_rsvd_bits_set(struct rsvd_bits_validate *rsvd_check,
+static inline bool __is_rsvd_bits_set(struct kvm_page_format *fmt,
u64 pte, int level)
{
- return pte & get_rsvd_bits(rsvd_check, pte, level);
+ return pte & get_rsvd_bits(fmt, pte, level);
}
-static inline bool __is_bad_mt_xwr(struct rsvd_bits_validate *rsvd_check,
+static inline bool __is_bad_mt_xwr(struct kvm_page_format *fmt,
u64 pte)
{
if (pte & VMX_EPT_USER_EXECUTABLE_MASK)
pte |= VMX_EPT_EXECUTABLE_MASK;
- return rsvd_check->bad_mt_xwr & BIT_ULL(pte & 0x3f);
+ return fmt->bad_mt_xwr & BIT_ULL(pte & 0x3f);
}
-static __always_inline bool is_rsvd_spte(struct rsvd_bits_validate *rsvd_check,
+static __always_inline bool is_rsvd_spte(struct kvm_page_format *fmt,
u64 spte, int level)
{
- return __is_bad_mt_xwr(rsvd_check, spte) ||
- __is_rsvd_bits_set(rsvd_check, spte, level);
+ return __is_bad_mt_xwr(fmt, spte) ||
+ __is_rsvd_bits_set(fmt, spte, level);
}
/*
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 5b74315f7e95..6565072760f1 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8703,7 +8703,7 @@ __init int vmx_hardware_setup(void)
/*
* Setup shadow_me_value/shadow_me_mask to include MKTME KeyID
- * bits to shadow_zero_check.
+ * bits into the MMU's struct kvm_page_format.
*/
vmx_setup_me_spte_mask();
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 23/24] KVM: x86/mmu: parameterize update_permission_bitmask()
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (21 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 22/24] KVM: x86/mmu: merge struct rsvd_bits_validate into struct kvm_page_format Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 24/24] KVM: x86/mmu: use kvm_page_format to test SPTEs Paolo Bonzini
23 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
Make it possible to apply the computation loop to both guest
and shadow PTEs formats; the latter do not have an extended role, so
pass the four parameters to the function one by one.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 29755afe5b46..386e7e05d205 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5569,18 +5569,15 @@ reset_ept_shadow_zero_bits_mask(struct kvm_mmu *context, bool execonly)
(14 & (access) ? 1 << 14 : 0) | \
(15 & (access) ? 1 << 15 : 0))
-static void update_permission_bitmask(struct kvm_pagewalk *pw, bool tdp, bool ept)
+static void __update_permission_bitmask(struct kvm_page_format *fmt, bool tdp,
+ bool ept, bool cr4_smep, bool cr4_smap,
+ bool cr0_wp, bool efer_nx)
{
unsigned index;
const u16 w = ACC_BITS_MASK(ACC_WRITE_MASK);
const u16 r = ACC_BITS_MASK(ACC_READ_MASK);
- bool cr4_smep = is_cr4_smep(pw);
- bool cr4_smap = is_cr4_smap(pw);
- bool cr0_wp = is_cr0_wp(pw);
- bool efer_nx = is_efer_nx(pw);
-
/*
* In hardware, page fault error codes are generated (as the name
* suggests) on any kind of page fault. permission_fault() and
@@ -5593,7 +5590,7 @@ static void update_permission_bitmask(struct kvm_pagewalk *pw, bool tdp, bool ep
* permission_fault() to indicate accesses that are *not* subject to
* SMAP restrictions.
*/
- for (index = 0; index < ARRAY_SIZE(pw->fmt.permissions); ++index) {
+ for (index = 0; index < ARRAY_SIZE(fmt->permissions); ++index) {
unsigned pfec = index << 1;
/*
@@ -5667,10 +5664,17 @@ static void update_permission_bitmask(struct kvm_pagewalk *pw, bool tdp, bool ep
smapf = (pfec & (PFERR_RSVD_MASK|PFERR_FETCH_MASK)) ? 0 : kf;
}
- pw->fmt.permissions[index] = ff | uf | wf | rf | smapf;
+ fmt->permissions[index] = ff | uf | wf | rf | smapf;
}
}
+static void update_permission_bitmask(struct kvm_pagewalk *w, bool tdp, bool ept)
+{
+ __update_permission_bitmask(&w->fmt, tdp, ept,
+ is_cr4_smep(w), is_cr4_smap(w),
+ is_cr0_wp(w), is_efer_nx(w));
+}
+
/*
* PKU is an additional mechanism by which the paging controls access to
* user-mode addresses based on the value in the PKRU register. Protection
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH 24/24] KVM: x86/mmu: use kvm_page_format to test SPTEs
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
` (22 preceding siblings ...)
2026-06-03 10:58 ` [PATCH 23/24] KVM: x86/mmu: parameterize update_permission_bitmask() Paolo Bonzini
@ 2026-06-03 10:58 ` Paolo Bonzini
2026-06-03 11:34 ` sashiko-bot
23 siblings, 1 reply; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 10:58 UTC (permalink / raw)
To: linux-kernel, kvm
is_access_allowed(), and is_executable_pte() within it, are effectively
a special version of permission_fault() that only supports a subset
of roles. In particular it does not allow SMEP, SMAP and PKE.
Replace its implementation with a modified version of permission_fault();
the new version will support SMEP (and hence AMD GMET) for free as soon
as update_spte_permission_bitmask() stops hardcoding cr4_smep == false.
This prepares for a possible future where TDP entries could have XS!=XU,
for example as part of implementing Hyper-V VSM natively inside KVM.
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 18 ++++++++++++---
arch/x86/kvm/mmu/spte.h | 46 +++++++++++++++++++++-----------------
arch/x86/kvm/mmu/tdp_mmu.c | 3 ++-
3 files changed, 42 insertions(+), 25 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 386e7e05d205..a4df38356988 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -3670,6 +3670,7 @@ static u64 *fast_pf_get_last_sptep(struct kvm_vcpu *vcpu, gpa_t gpa, u64 *spte)
*/
static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
{
+ struct kvm_mmu *mmu;
struct kvm_mmu_page *sp;
int ret = RET_PF_INVALID;
u64 spte;
@@ -3679,6 +3680,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
if (!page_fault_can_be_fast(vcpu->kvm, fault))
return ret;
+ mmu = vcpu->arch.mmu;
walk_shadow_page_lockless_begin(vcpu);
do {
@@ -3714,7 +3716,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
* Need not check the access of upper level table entries since
* they are always ACC_ALL.
*/
- if (is_access_allowed(fault, spte)) {
+ if (!spte_permission_fault(mmu, spte, fault)) {
ret = RET_PF_SPURIOUS;
break;
}
@@ -3737,7 +3739,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
* that were write-protected for dirty-logging or access
* tracking are handled here. Don't bother checking if the
* SPTE is writable to prioritize running with A/D bits enabled.
- * The is_access_allowed() check above handles the common case
+ * The spte_permission_fault() check above handles the common case
* of the fault being spurious, and the SPTE is known to be
* shadow-present, i.e. except for access tracking restoration
* making the new SPTE writable, the check is wasteful.
@@ -3762,7 +3764,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
/* Verify that the fault can be handled in the fast path */
if (new_spte == spte ||
- !is_access_allowed(fault, new_spte))
+ spte_permission_fault(mmu, new_spte, fault))
break;
/*
@@ -5675,6 +5677,12 @@ static void update_permission_bitmask(struct kvm_pagewalk *w, bool tdp, bool ept
is_cr0_wp(w), is_efer_nx(w));
}
+static void update_spte_permission_bitmask(struct kvm_mmu *mmu, bool tdp, bool ept)
+{
+ __update_permission_bitmask(&mmu->fmt, tdp, ept,
+ mmu->root_role.cr4_smep, false, true, true);
+}
+
/*
* PKU is an additional mechanism by which the paging controls access to
* user-mode addresses based on the value in the PKRU register. Protection
@@ -5884,6 +5892,7 @@ static void init_kvm_tdp_mmu(struct kvm_vcpu *vcpu,
context->page_fault = kvm_tdp_page_fault;
context->sync_spte = NULL;
+ update_spte_permission_bitmask(context, true, shadow_xs_mask);
reset_tdp_shadow_zero_bits_mask(context);
}
@@ -5902,6 +5911,7 @@ static void shadow_mmu_init_context(struct kvm_vcpu *vcpu, struct kvm_mmu *conte
else
paging32_init_context(context);
+ update_spte_permission_bitmask(context, context == &vcpu->arch.guest_mmu, false);
reset_shadow_zero_bits_mask(vcpu, context);
}
@@ -6030,6 +6040,8 @@ void kvm_init_shadow_ept_mmu(struct kvm_vcpu *vcpu, bool execonly,
update_permission_bitmask(ngpa_walk, true, true);
ngpa_walk->fmt.pkru_mask = 0;
reset_rsvds_bits_mask_ept(vcpu, execonly, huge_page_level);
+
+ update_spte_permission_bitmask(context, true, true);
reset_ept_shadow_zero_bits_mask(context, execonly);
}
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 918533e61b98..9bddfa0e02b9 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -357,17 +357,6 @@ static inline bool is_last_spte(u64 pte, int level)
return (level == PG_LEVEL_4K) || is_large_pte(pte);
}
-static inline bool is_executable_pte(u64 spte)
-{
- /*
- * For now, return true if either the XS or XU bit is set
- * This function is only used for fast_page_fault,
- * which never processes shadow EPT, and regular page
- * tables always have XS==XU.
- */
- return (spte & (shadow_xs_mask | shadow_xu_mask | shadow_nx_mask)) != shadow_nx_mask;
-}
-
static inline kvm_pfn_t spte_to_pfn(u64 pte)
{
return (pte & SPTE_BASE_ADDR_MASK) >> PAGE_SHIFT;
@@ -496,20 +485,35 @@ static inline bool is_mmu_writable_spte(u64 spte)
}
/*
- * Returns true if the access indicated by @fault is allowed by the existing
- * SPTE protections. Note, the caller is responsible for checking that the
- * SPTE is a shadow-present, leaf SPTE (either before or after).
+ * Returns true if the access indicated by @fault is forbidden by the existing
+ * SPTE protections.
*/
-static inline bool is_access_allowed(struct kvm_page_fault *fault, u64 spte)
+static inline bool spte_permission_fault(struct kvm_mmu *mmu, u64 spte,
+ struct kvm_page_fault *fault)
{
- if (fault->exec)
- return is_executable_pte(spte);
+ unsigned int pfec = fault->error_code;
+ int index = pfec >> 1;
+ int pte_access;
- if (fault->write)
- return is_writable_pte(spte);
+ if (!is_shadow_present_pte(spte))
+ return true;
- /* Fault was on Read access */
- return spte & PT_PRESENT_MASK;
+ BUILD_BUG_ON(PT_PRESENT_MASK != ACC_READ_MASK);
+ BUILD_BUG_ON(PT_WRITABLE_MASK != ACC_WRITE_MASK);
+ BUILD_BUG_ON(VMX_EPT_READABLE_MASK != ACC_READ_MASK);
+ BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != ACC_WRITE_MASK);
+
+ /* strip nested paging fault error codes */
+ pte_access = spte & (PT_PRESENT_MASK | PT_WRITABLE_MASK);
+ if (shadow_nx_mask) {
+ pte_access |= spte & shadow_user_mask ? ACC_USER_MASK : 0;
+ pte_access |= spte & shadow_nx_mask ? 0 : ACC_EXEC_MASK;
+ } else {
+ pte_access |= spte & shadow_xs_mask ? ACC_EXEC_MASK : 0;
+ pte_access |= spte & shadow_xu_mask ? ACC_USER_EXEC_MASK : 0;
+ }
+
+ return (mmu->fmt.permissions[index] >> pte_access) & 1;
}
/*
diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
index 5a2f8ce9a32b..839a8e416510 100644
--- a/arch/x86/kvm/mmu/tdp_mmu.c
+++ b/arch/x86/kvm/mmu/tdp_mmu.c
@@ -1169,6 +1169,7 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
struct kvm_page_fault *fault,
struct tdp_iter *iter)
{
+ struct kvm_mmu *mmu = vcpu->arch.mmu;
struct kvm_mmu_page *sp = sptep_to_sp(rcu_dereference(iter->sptep));
u64 new_spte;
int ret = RET_PF_FIXED;
@@ -1178,7 +1179,7 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
return RET_PF_RETRY;
if (is_shadow_present_pte(iter->old_spte) &&
- (fault->prefetch || is_access_allowed(fault, iter->old_spte)) &&
+ (fault->prefetch || !spte_permission_fault(mmu, iter->old_spte, fault)) &&
is_last_spte(iter->old_spte, iter->level)) {
WARN_ON_ONCE(fault->pfn != spte_to_pfn(iter->old_spte));
return RET_PF_SPURIOUS;
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH 18/24] KVM: x86/mmu: make gva_walk a value
2026-06-03 10:58 ` [PATCH 18/24] KVM: x86/mmu: make gva_walk a value Paolo Bonzini
@ 2026-06-03 11:24 ` sashiko-bot
2026-06-03 11:47 ` Paolo Bonzini
0 siblings, 1 reply; 30+ messages in thread
From: sashiko-bot @ 2026-06-03 11:24 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Decoupling `gva_walk` into an embedded struct breaks pointer equality checks, classifying L1 shadow page faults as nested and completely bypassing TLB flushes and shadow page invalidations.
--
commit e0579a768a48ab33f8084b00e1f9e241b8879e43
Author: Paolo Bonzini <pbonzini@redhat.com>
KVM: x86/mmu: make gva_walk a value
This commit changes gva_walk from a pointer to an embedded struct in
kvm_vcpu_arch. It aims to use a single instance of kvm_pagewalk for GVA
to GPA translations instead of flipping the gva_walk pointer back and
forth.
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 386fdbc34b025..2fe4d53590064 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
[ ... ]
> @@ -6598,17 +6588,22 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
> WARN_ON_ONCE(roots & ~KVM_MMU_ROOTS_ALL);
>
> /* It's actually a GPA for vcpu->arch.guest_mmu. */
> - if (w != &vcpu->arch.guest_mmu.w) {
> + if (w == &vcpu->arch.gva_walk) {
> /* INVLPG on a non-canonical address is a NOP according to the SDM. */
> if (is_noncanonical_invlpg_address(addr, vcpu))
> return;
>
> kvm_x86_call(flush_tlb_gva)(vcpu, addr);
> - if (w == &vcpu->arch.ngva_walk)
> +
> + if (tdp_enabled)
> return;
> +
> + mmu = &vcpu->arch.root_mmu;
> + } else {
> + mmu = &vcpu->arch.guest_mmu;
> }
[Severity: High]
If kvm_mmu_invalidate_addr() is called with w pointing to
&vcpu->arch.root_mmu.w, this pointer check will fail and fall through to the
else block, assigning mmu = &vcpu->arch.guest_mmu.
Since guest_mmu is inactive when not nested, mmu->sync_spte would be NULL,
causing an early return. Does this silently skip both the hardware TLB flush
and the root_mmu shadow page invalidation, potentially creating an infinite
page fault loop?
I noticed this behavior can be triggered due to the changes in paging_tmpl.h
and x86.c, which I've commented on below. I also see this is fixed later in
the series by commit 9a1a629159ef9003 ("KVM: x86/mmu: pull struct
kvm_pagewalk out of struct kvm_mmu"), but it seems this intermediate commit
breaks bisection.
> diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
> index 6b21778e83404..b12c6b5e4a2fb 100644
> --- a/arch/x86/kvm/mmu/paging_tmpl.h
> +++ b/arch/x86/kvm/mmu/paging_tmpl.h
[ ... ]
> @@ -541,7 +541,7 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker,
> }
> #endif
> walker->fault.address = addr;
> - walker->fault.nested_page_fault = w != vcpu->arch.gva_walk;
> + walker->fault.nested_page_fault = w != &vcpu->arch.gva_walk;
[Severity: High]
Does this break the pointer equality check for shadow paging?
When w points to &vcpu->arch.root_mmu.w (for L1 shadow page faults),
w != &vcpu->arch.gva_walk will now evaluate to true since they are distinct
struct instances in memory. Would this incorrectly flag L1 shadow page faults
as nested?
> walker->fault.async_page_fault = false;
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 147cef7b23b60..14af0f4d010ed 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -995,7 +995,7 @@ void kvm_inject_emulated_page_fault(struct kvm_vcpu *vcpu,
> WARN_ON_ONCE(fault->vector != PF_VECTOR);
>
> fault_walk = fault->nested_page_fault ? &vcpu->arch.mmu->w :
> - vcpu->arch.gva_walk;
> + &vcpu->arch.gva_walk;
[Severity: High]
If nested_page_fault is incorrectly set to true by walk_addr_generic(),
doesn't this select &vcpu->arch.mmu->w instead of &vcpu->arch.gva_walk?
This results in kvm_mmu_invalidate_addr() being called with root_mmu.w,
leading to the skipped invalidation mentioned above.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260603105814.10236-1-pbonzini@redhat.com?part=18
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH 12/24] KVM: x86/mmu: move CPU-related fields to struct kvm_pagewalk
2026-06-03 10:58 ` [PATCH 12/24] KVM: x86/mmu: move CPU-related fields " Paolo Bonzini
@ 2026-06-03 11:27 ` sashiko-bot
2026-06-03 12:36 ` Paolo Bonzini
0 siblings, 1 reply; 30+ messages in thread
From: sashiko-bot @ 2026-06-03 11:27 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] When using nested virtualization with shadow paging (EPT disabled), L1's PDPTRs are overwritten by L2's PDPTRs during a nested VM-Entry but are never restored on a failed VM-Entry. This allows L2 to control L1's shadow page tables.
--
commit a87d952b84013700c29cd0401e35d07fb7710cf4
Author: Paolo Bonzini <pbonzini@redhat.com>
KVM: x86/mmu: move CPU-related fields to struct kvm_pagewalk
This commit moves CPU-related fields and their page format dependencies
into struct kvm_pagewalk. This allows the walk_mmu function to remain
self-contained while cleaning up the accessor functions.
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 4fbb7508e2413..e2bfecf655d9a 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
[ ... ]
> @@ -4083,7 +4083,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
> * On SVM, reading PDPTRs might access guest memory, which might fault
> * and thus might sleep. Grab the PDPTRs before acquiring mmu_lock.
> */
> - if (mmu->cpu_role.base.level == PT32E_ROOT_LEVEL) {
> + if (mmu->w.cpu_role.base.level == PT32E_ROOT_LEVEL) {
> for (i = 0; i < 4; ++i) {
> pdptrs[i] = mmu->w.get_pdptr(vcpu, i);
[Severity: Critical]
This is a pre-existing issue, but while reviewing the PDPTR accesses here,
I noticed a problem with how PDPTRs are handled on nested VM-Entry failures.
When using nested virtualization with shadow paging (EPT disabled), L1's
PDPTRs are overwritten by L2's PDPTRs during a nested VM-Entry but are never
restored on a failed VM-Entry.
Looking at nested_vmx_restore_host_state() in arch/x86/kvm/vmx/nested.c:
vcpu->arch.cr3 = vmcs_readl(GUEST_CR3);
kvm_register_mark_available(vcpu, VCPU_REG_CR3);
/*
* Use ept_save_pdptrs(vcpu) to load the MMU's cached PDPTRs
* from vmcs01 (if necessary). The PDPTRs are not loaded on
* VMFail, like everything else we just need to ensure our
* software model is up-to-date.
*/
if (enable_ept && is_pae_paging(vcpu))
ept_save_pdptrs(vcpu);
L1's CR3 is restored here upon a VM-Entry failure, but the PDPTRs are only
restored if EPT is enabled. If shadow paging is used (enable_ept is false),
the L2 PDPTRs loaded during the aborted entry attempt remain in KVM's software
model (vcpu->arch.pdptrs).
Furthermore, in vmx_cache_reg() in arch/x86/kvm/vmx/vmx.c:
case VCPU_REG_PDPTR:
if (enable_ept)
ept_save_pdptrs(vcpu);
break;
After the failed VM-Entry, VCPU_REG_PDPTR is marked as unavailable. When KVM's
shadow MMU reads the PDPTRs via get_pdptr(), it calls vmx_cache_reg(). For
shadow paging, this does nothing and simply marks the register as available,
causing the MMU to use the stale L2 PDPTRs.
Since L2 can provide arbitrary addresses for its PDPTRs, does this enable L2
to hijack L1's memory mappings and escalate privileges from the nested guest
to the L1 hypervisor?
> if (!(pdptrs[i] & PT_PRESENT_MASK))
> continue;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260603105814.10236-1-pbonzini@redhat.com?part=12
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH 24/24] KVM: x86/mmu: use kvm_page_format to test SPTEs
2026-06-03 10:58 ` [PATCH 24/24] KVM: x86/mmu: use kvm_page_format to test SPTEs Paolo Bonzini
@ 2026-06-03 11:34 ` sashiko-bot
0 siblings, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-06-03 11:34 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] The `spte_permission_fault()` function indexes the statically-sized 16-element `mmu->fmt.permissions` array using the raw hardware page fault error code (`fault->error_code`), leading to an out-of-bounds kernel memory read and host kernel panic when higher-order error code bits are set by hardware (e.g., AMD RMP faults).
--
commit 18ca0883f7313fb2ea6fc90d61d35c33ed81ed22
Author: Paolo Bonzini <pbonzini@redhat.com>
Date: Wed Jun 3 10:58:14 2026 +0000
KVM: x86/mmu: use kvm_page_format to test SPTEs
This commit replaces the custom is_access_allowed() implementation with
spte_permission_fault(). The new version leverages kvm_page_format to check
SPTEs against permissions, adding support for SMEP and paving the way for
TDP entries with differing XS and XU bits.
> diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
> index 918533e61b983..9bddfa0e02b97 100644
> --- a/arch/x86/kvm/mmu/spte.h
> +++ b/arch/x86/kvm/mmu/spte.h
[ ... ]
> @@ -496,20 +485,35 @@ static inline bool is_mmu_writable_spte(u64 spte)
> }
>
> /*
> - * Returns true if the access indicated by @fault is allowed by the existing
> - * SPTE protections. Note, the caller is responsible for checking that the
> - * SPTE is a shadow-present, leaf SPTE (either before or after).
> + * Returns true if the access indicated by @fault is forbidden by the existing
> + * SPTE protections.
> */
> -static inline bool is_access_allowed(struct kvm_page_fault *fault, u64 spte)
> -{
> - if (fault->exec)
> - return is_executable_pte(spte);
> -
> - if (fault->write)
> - return is_writable_pte(spte);
> -
> - /* Fault was on Read access */
> - return spte & PT_PRESENT_MASK;
> +static inline bool spte_permission_fault(struct kvm_mmu *mmu, u64 spte,
> + struct kvm_page_fault *fault)
> +{
> + unsigned int pfec = fault->error_code;
> + int index = pfec >> 1;
> + int pte_access;
> +
> + if (!is_shadow_present_pte(spte))
> + return true;
> +
> + BUILD_BUG_ON(PT_PRESENT_MASK != ACC_READ_MASK);
> + BUILD_BUG_ON(PT_WRITABLE_MASK != ACC_WRITE_MASK);
> + BUILD_BUG_ON(VMX_EPT_READABLE_MASK != ACC_READ_MASK);
> + BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != ACC_WRITE_MASK);
> +
> + /* strip nested paging fault error codes */
> + pte_access = spte & (PT_PRESENT_MASK | PT_WRITABLE_MASK);
> + if (shadow_nx_mask) {
> + pte_access |= spte & shadow_user_mask ? ACC_USER_MASK : 0;
> + pte_access |= spte & shadow_nx_mask ? 0 : ACC_EXEC_MASK;
> + } else {
> + pte_access |= spte & shadow_xs_mask ? ACC_EXEC_MASK : 0;
> + pte_access |= spte & shadow_xu_mask ? ACC_USER_EXEC_MASK : 0;
> + }
> +
> + return (mmu->fmt.permissions[index] >> pte_access) & 1;
[Severity: Critical]
Could this lead to an out-of-bounds read if the hardware page fault error
code contains higher-order bits?
Looking at spte_permission_fault(), the raw fault->error_code is shifted
and used directly as an index into the mmu->fmt.permissions array. Since
this array only has 16 elements, an unmasked error code with higher bits
set could easily exceed the array bounds.
For example, if an AMD RMP fault sets bit 31 (PFERR_GUEST_RMP_MASK), the
index would be 1073741824, potentially causing a host kernel panic when
accessing mmu->fmt.permissions[index]. Even without SEV-SNP, a PKRU fault
(bit 5) under shadow paging results in an index of 16, which is also out
of bounds.
Should fault->error_code be masked before computing the index?
> }
>
> /*
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260603105814.10236-1-pbonzini@redhat.com?part=24
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH 18/24] KVM: x86/mmu: make gva_walk a value
2026-06-03 11:24 ` sashiko-bot
@ 2026-06-03 11:47 ` Paolo Bonzini
0 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 11:47 UTC (permalink / raw)
To: sashiko-reviews; +Cc: kvm
On Wed, Jun 3, 2026 at 1:24 PM <sashiko-bot@kernel.org> wrote:
> > /* It's actually a GPA for vcpu->arch.guest_mmu. */
> > - if (w != &vcpu->arch.guest_mmu.w) {
> > + if (w == &vcpu->arch.gva_walk) {
> > /* INVLPG on a non-canonical address is a NOP according to the SDM. */
> > if (is_noncanonical_invlpg_address(addr, vcpu))
> > return;
> >
> > kvm_x86_call(flush_tlb_gva)(vcpu, addr);
> > - if (w == &vcpu->arch.ngva_walk)
> > +
> > + if (tdp_enabled)
> > return;
> > +
> > + mmu = &vcpu->arch.root_mmu;
> > + } else {
> > + mmu = &vcpu->arch.guest_mmu;
> > }
>
> [Severity: High]
> If kvm_mmu_invalidate_addr() is called with w pointing to
> &vcpu->arch.root_mmu.w, this pointer check will fail and fall through to the
> else block, assigning mmu = &vcpu->arch.guest_mmu.
>
> Since guest_mmu is inactive when not nested, mmu->sync_spte would be NULL,
> causing an early return. Does this silently skip both the hardware TLB flush
> and the root_mmu shadow page invalidation, potentially creating an infinite
> page fault loop?
>
> I noticed this behavior can be triggered due to the changes in paging_tmpl.h
> and x86.c, which I've commented on below. I also see this is fixed later in
> the series by commit 9a1a629159ef9003 ("KVM: x86/mmu: pull struct
> kvm_pagewalk out of struct kvm_mmu"), but it seems this intermediate commit
> breaks bisection.
Hmm, this seems correct. It's relatively easily solved by swapping
this commit and the next.
Paolo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH 12/24] KVM: x86/mmu: move CPU-related fields to struct kvm_pagewalk
2026-06-03 11:27 ` sashiko-bot
@ 2026-06-03 12:36 ` Paolo Bonzini
0 siblings, 0 replies; 30+ messages in thread
From: Paolo Bonzini @ 2026-06-03 12:36 UTC (permalink / raw)
To: sashiko-reviews; +Cc: kvm
On 6/3/26 13:27, sashiko-bot@kernel.org wrote:
> [Severity: Critical]
> This is a pre-existing issue, but while reviewing the PDPTR accesses here,
> I noticed a problem with how PDPTRs are handled on nested VM-Entry failures.
>
> When using nested virtualization with shadow paging (EPT disabled), L1's
> PDPTRs are overwritten by L2's PDPTRs during a nested VM-Entry but are never
> restored on a failed VM-Entry.
>
> Looking at nested_vmx_restore_host_state() in arch/x86/kvm/vmx/nested.c:
>
> vcpu->arch.cr3 = vmcs_readl(GUEST_CR3);
> kvm_register_mark_available(vcpu, VCPU_REG_CR3);
>
> /*
> * Use ept_save_pdptrs(vcpu) to load the MMU's cached PDPTRs
> * from vmcs01 (if necessary). The PDPTRs are not loaded on
> * VMFail, like everything else we just need to ensure our
> * software model is up-to-date.
> */
> if (enable_ept && is_pae_paging(vcpu))
> ept_save_pdptrs(vcpu);
>
> L1's CR3 is restored here upon a VM-Entry failure, but the PDPTRs are only
> restored if EPT is enabled. If shadow paging is used (enable_ept is false),
> the L2 PDPTRs loaded during the aborted entry attempt remain in KVM's software
> model (vcpu->arch.pdptrs).
This is requesting to do the same as the non-architectural MSR unwind
that is already in nested_vmx_restore_host_state(). It's true that it's
pre-existing though.
Paolo
^ permalink raw reply [flat|nested] 30+ messages in thread
end of thread, other threads:[~2026-06-03 12:36 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-03 10:57 [PATCH 00/24] KVM: apply chainsaw to struct kvm_mmu Paolo Bonzini
2026-06-03 10:57 ` [PATCH 01/24] KVM: x86: remove nested_mmu from mmu_is_nested() Paolo Bonzini
2026-06-03 10:57 ` [PATCH 02/24] KVM: nVMX: remove unnecessary code in prepare_vmcs02_rare Paolo Bonzini
2026-06-03 10:57 ` [PATCH 03/24] KVM: nSVM: invalidate cached PDPTRs across nested NPT transitions Paolo Bonzini
2026-06-03 10:57 ` [PATCH 04/24] KVM: x86: check that kvm_handle_invpcid is only invoked with shadow paging Paolo Bonzini
2026-06-03 10:57 ` [PATCH 05/24] KVM: x86/mmu: move pdptrs out of the MMU Paolo Bonzini
2026-06-03 10:57 ` [PATCH 06/24] KVM: x86/hyperv: remove unnecessary mmu_is_nested() check Paolo Bonzini
2026-06-03 10:57 ` [PATCH 07/24] KVM: x86/mmu: introduce struct kvm_pagewalk Paolo Bonzini
2026-06-03 10:57 ` [PATCH 08/24] KVM: x86/mmu: move get_guest_pgd to " Paolo Bonzini
2026-06-03 10:57 ` [PATCH 09/24] KVM: x86/mmu: move gva_to_gpa " Paolo Bonzini
2026-06-03 10:58 ` [PATCH 10/24] KVM: x86/mmu: move get_pdptr " Paolo Bonzini
2026-06-03 10:58 ` [PATCH 11/24] KVM: x86/mmu: move inject_page_fault " Paolo Bonzini
2026-06-03 10:58 ` [PATCH 12/24] KVM: x86/mmu: move CPU-related fields " Paolo Bonzini
2026-06-03 11:27 ` sashiko-bot
2026-06-03 12:36 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 13/24] KVM: x86/mmu: change CPU-role accessor fields to take " Paolo Bonzini
2026-06-03 10:58 ` [PATCH 14/24] KVM: x86/mmu: move remaining permission fields to " Paolo Bonzini
2026-06-03 10:58 ` [PATCH 15/24] KVM: x86/mmu: pass struct kvm_pagewalk to kvm_mmu_invalidate_addr Paolo Bonzini
2026-06-03 10:58 ` [PATCH 16/24] KVM: x86/mmu: change walk_mmu to struct kvm_pagewalk Paolo Bonzini
2026-06-03 10:58 ` [PATCH 17/24] KVM: x86/mmu: change nested_mmu.w to ngva_walk Paolo Bonzini
2026-06-03 10:58 ` [PATCH 18/24] KVM: x86/mmu: make gva_walk a value Paolo Bonzini
2026-06-03 11:24 ` sashiko-bot
2026-06-03 11:47 ` Paolo Bonzini
2026-06-03 10:58 ` [PATCH 19/24] KVM: x86/mmu: pull struct kvm_pagewalk out of struct kvm_mmu Paolo Bonzini
2026-06-03 10:58 ` [PATCH 20/24] KVM: x86/mmu: cleanup functions that initialize shadow MMU Paolo Bonzini
2026-06-03 10:58 ` [PATCH 21/24] KVM: x86/mmu: pull page format to a new struct Paolo Bonzini
2026-06-03 10:58 ` [PATCH 22/24] KVM: x86/mmu: merge struct rsvd_bits_validate into struct kvm_page_format Paolo Bonzini
2026-06-03 10:58 ` [PATCH 23/24] KVM: x86/mmu: parameterize update_permission_bitmask() Paolo Bonzini
2026-06-03 10:58 ` [PATCH 24/24] KVM: x86/mmu: use kvm_page_format to test SPTEs Paolo Bonzini
2026-06-03 11:34 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox