Skip to content

Commit 2f2fad0

Browse files
Ben Gardonbonzini
authored andcommitted
kvm: x86/mmu: Add functions to handle changed TDP SPTEs
The existing bookkeeping done by KVM when a PTE is changed is spread around several functions. This makes it difficult to remember all the stats, bitmaps, and other subsystems that need to be updated whenever a PTE is modified. When a non-leaf PTE is marked non-present or becomes a leaf PTE, page table memory must also be freed. To simplify the MMU and facilitate the use of atomic operations on SPTEs in future patches, create functions to handle some of the bookkeeping required as a result of a change. Tested by running kvm-unit-tests and KVM selftests on an Intel Haswell machine. This series introduced no new failures. This series can be viewed in Gerrit at: https://linux-review.googlesource.com/c/virt/kvm/kvm/+/2538 Signed-off-by: Ben Gardon <[email protected]> Signed-off-by: Paolo Bonzini <[email protected]>
1 parent 02c00b3 commit 2f2fad0

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

arch/x86/kvm/mmu/mmu.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ static void kvm_flush_remote_tlbs_with_range(struct kvm *kvm,
213213
kvm_flush_remote_tlbs(kvm);
214214
}
215215

216-
static void kvm_flush_remote_tlbs_with_address(struct kvm *kvm,
216+
void kvm_flush_remote_tlbs_with_address(struct kvm *kvm,
217217
u64 start_gfn, u64 pages)
218218
{
219219
struct kvm_tlb_range range;

arch/x86/kvm/mmu/mmu_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ void kvm_mmu_gfn_disallow_lpage(struct kvm_memory_slot *slot, gfn_t gfn);
9292
void kvm_mmu_gfn_allow_lpage(struct kvm_memory_slot *slot, gfn_t gfn);
9393
bool kvm_mmu_slot_gfn_write_protect(struct kvm *kvm,
9494
struct kvm_memory_slot *slot, u64 gfn);
95+
void kvm_flush_remote_tlbs_with_address(struct kvm *kvm,
96+
u64 start_gfn, u64 pages);
9597

9698
static inline void kvm_mmu_get_root(struct kvm *kvm, struct kvm_mmu_page *sp)
9799
{

arch/x86/kvm/mmu/tdp_mmu.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "mmu.h"
44
#include "mmu_internal.h"
5+
#include "tdp_iter.h"
56
#include "tdp_mmu.h"
67
#include "spte.h"
78

@@ -130,3 +131,114 @@ hpa_t kvm_tdp_mmu_get_vcpu_root_hpa(struct kvm_vcpu *vcpu)
130131

131132
return __pa(root->spt);
132133
}
134+
135+
static void handle_changed_spte(struct kvm *kvm, int as_id, gfn_t gfn,
136+
u64 old_spte, u64 new_spte, int level);
137+
138+
/**
139+
* handle_changed_spte - handle bookkeeping associated with an SPTE change
140+
* @kvm: kvm instance
141+
* @as_id: the address space of the paging structure the SPTE was a part of
142+
* @gfn: the base GFN that was mapped by the SPTE
143+
* @old_spte: The value of the SPTE before the change
144+
* @new_spte: The value of the SPTE after the change
145+
* @level: the level of the PT the SPTE is part of in the paging structure
146+
*
147+
* Handle bookkeeping that might result from the modification of a SPTE.
148+
* This function must be called for all TDP SPTE modifications.
149+
*/
150+
static void __handle_changed_spte(struct kvm *kvm, int as_id, gfn_t gfn,
151+
u64 old_spte, u64 new_spte, int level)
152+
{
153+
bool was_present = is_shadow_present_pte(old_spte);
154+
bool is_present = is_shadow_present_pte(new_spte);
155+
bool was_leaf = was_present && is_last_spte(old_spte, level);
156+
bool is_leaf = is_present && is_last_spte(new_spte, level);
157+
bool pfn_changed = spte_to_pfn(old_spte) != spte_to_pfn(new_spte);
158+
u64 *pt;
159+
u64 old_child_spte;
160+
int i;
161+
162+
WARN_ON(level > PT64_ROOT_MAX_LEVEL);
163+
WARN_ON(level < PG_LEVEL_4K);
164+
WARN_ON(gfn % KVM_PAGES_PER_HPAGE(level));
165+
166+
/*
167+
* If this warning were to trigger it would indicate that there was a
168+
* missing MMU notifier or a race with some notifier handler.
169+
* A present, leaf SPTE should never be directly replaced with another
170+
* present leaf SPTE pointing to a differnt PFN. A notifier handler
171+
* should be zapping the SPTE before the main MM's page table is
172+
* changed, or the SPTE should be zeroed, and the TLBs flushed by the
173+
* thread before replacement.
174+
*/
175+
if (was_leaf && is_leaf && pfn_changed) {
176+
pr_err("Invalid SPTE change: cannot replace a present leaf\n"
177+
"SPTE with another present leaf SPTE mapping a\n"
178+
"different PFN!\n"
179+
"as_id: %d gfn: %llx old_spte: %llx new_spte: %llx level: %d",
180+
as_id, gfn, old_spte, new_spte, level);
181+
182+
/*
183+
* Crash the host to prevent error propagation and guest data
184+
* courruption.
185+
*/
186+
BUG();
187+
}
188+
189+
if (old_spte == new_spte)
190+
return;
191+
192+
/*
193+
* The only times a SPTE should be changed from a non-present to
194+
* non-present state is when an MMIO entry is installed/modified/
195+
* removed. In that case, there is nothing to do here.
196+
*/
197+
if (!was_present && !is_present) {
198+
/*
199+
* If this change does not involve a MMIO SPTE, it is
200+
* unexpected. Log the change, though it should not impact the
201+
* guest since both the former and current SPTEs are nonpresent.
202+
*/
203+
if (WARN_ON(!is_mmio_spte(old_spte) && !is_mmio_spte(new_spte)))
204+
pr_err("Unexpected SPTE change! Nonpresent SPTEs\n"
205+
"should not be replaced with another,\n"
206+
"different nonpresent SPTE, unless one or both\n"
207+
"are MMIO SPTEs.\n"
208+
"as_id: %d gfn: %llx old_spte: %llx new_spte: %llx level: %d",
209+
as_id, gfn, old_spte, new_spte, level);
210+
return;
211+
}
212+
213+
214+
if (was_leaf && is_dirty_spte(old_spte) &&
215+
(!is_dirty_spte(new_spte) || pfn_changed))
216+
kvm_set_pfn_dirty(spte_to_pfn(old_spte));
217+
218+
/*
219+
* Recursively handle child PTs if the change removed a subtree from
220+
* the paging structure.
221+
*/
222+
if (was_present && !was_leaf && (pfn_changed || !is_present)) {
223+
pt = spte_to_child_pt(old_spte, level);
224+
225+
for (i = 0; i < PT64_ENT_PER_PAGE; i++) {
226+
old_child_spte = READ_ONCE(*(pt + i));
227+
WRITE_ONCE(*(pt + i), 0);
228+
handle_changed_spte(kvm, as_id,
229+
gfn + (i * KVM_PAGES_PER_HPAGE(level - 1)),
230+
old_child_spte, 0, level - 1);
231+
}
232+
233+
kvm_flush_remote_tlbs_with_address(kvm, gfn,
234+
KVM_PAGES_PER_HPAGE(level));
235+
236+
free_page((unsigned long)pt);
237+
}
238+
}
239+
240+
static void handle_changed_spte(struct kvm *kvm, int as_id, gfn_t gfn,
241+
u64 old_spte, u64 new_spte, int level)
242+
{
243+
__handle_changed_spte(kvm, as_id, gfn, old_spte, new_spte, level);
244+
}

0 commit comments

Comments
 (0)