Skip to content

Commit f5238c2

Browse files
committed
KVM: x86/mmu: Check for usable TDP MMU root while holding mmu_lock for read
When allocating a new TDP MMU root, check for a usable root while holding mmu_lock for read and only acquire mmu_lock for write if a new root needs to be created. There is no need to serialize other MMU operations if a vCPU is simply grabbing a reference to an existing root, holding mmu_lock for write is "necessary" (spoiler alert, it's not strictly necessary) only to ensure KVM doesn't end up with duplicate roots. Allowing vCPUs to get "new" roots in parallel is beneficial to VM boot and to setups that frequently delete memslots, i.e. which force all vCPUs to reload all roots. Link: https://lore.kernel.org/r/20240111020048.844847-7-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
1 parent d746182 commit f5238c2

File tree

3 files changed

+55
-15
lines changed

3 files changed

+55
-15
lines changed

arch/x86/kvm/mmu/mmu.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3693,15 +3693,15 @@ static int mmu_alloc_direct_roots(struct kvm_vcpu *vcpu)
36933693
unsigned i;
36943694
int r;
36953695

3696+
if (tdp_mmu_enabled)
3697+
return kvm_tdp_mmu_alloc_root(vcpu);
3698+
36963699
write_lock(&vcpu->kvm->mmu_lock);
36973700
r = make_mmu_pages_available(vcpu);
36983701
if (r < 0)
36993702
goto out_unlock;
37003703

3701-
if (tdp_mmu_enabled) {
3702-
root = kvm_tdp_mmu_get_vcpu_root_hpa(vcpu);
3703-
mmu->root.hpa = root;
3704-
} else if (shadow_root_level >= PT64_ROOT_4LEVEL) {
3704+
if (shadow_root_level >= PT64_ROOT_4LEVEL) {
37053705
root = mmu_alloc_root(vcpu, 0, 0, shadow_root_level);
37063706
mmu->root.hpa = root;
37073707
} else if (shadow_root_level == PT32E_ROOT_LEVEL) {

arch/x86/kvm/mmu/tdp_mmu.c

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,21 +223,52 @@ static void tdp_mmu_init_child_sp(struct kvm_mmu_page *child_sp,
223223
tdp_mmu_init_sp(child_sp, iter->sptep, iter->gfn, role);
224224
}
225225

226-
hpa_t kvm_tdp_mmu_get_vcpu_root_hpa(struct kvm_vcpu *vcpu)
226+
static struct kvm_mmu_page *kvm_tdp_mmu_try_get_root(struct kvm_vcpu *vcpu)
227227
{
228228
union kvm_mmu_page_role role = vcpu->arch.mmu->root_role;
229+
int as_id = kvm_mmu_role_as_id(role);
229230
struct kvm *kvm = vcpu->kvm;
230231
struct kvm_mmu_page *root;
231232

232-
lockdep_assert_held_write(&kvm->mmu_lock);
233-
234-
/* Check for an existing root before allocating a new one. */
235-
for_each_valid_tdp_mmu_root(kvm, root, kvm_mmu_role_as_id(role)) {
236-
if (root->role.word == role.word &&
237-
kvm_tdp_mmu_get_root(root))
238-
goto out;
233+
for_each_valid_tdp_mmu_root_yield_safe(kvm, root, as_id) {
234+
if (root->role.word == role.word)
235+
return root;
239236
}
240237

238+
return NULL;
239+
}
240+
241+
int kvm_tdp_mmu_alloc_root(struct kvm_vcpu *vcpu)
242+
{
243+
struct kvm_mmu *mmu = vcpu->arch.mmu;
244+
union kvm_mmu_page_role role = mmu->root_role;
245+
struct kvm *kvm = vcpu->kvm;
246+
struct kvm_mmu_page *root;
247+
248+
/*
249+
* Check for an existing root while holding mmu_lock for read to avoid
250+
* unnecessary serialization if multiple vCPUs are loading a new root.
251+
* E.g. when bringing up secondary vCPUs, KVM will already have created
252+
* a valid root on behalf of the primary vCPU.
253+
*/
254+
read_lock(&kvm->mmu_lock);
255+
root = kvm_tdp_mmu_try_get_root(vcpu);
256+
read_unlock(&kvm->mmu_lock);
257+
258+
if (root)
259+
goto out;
260+
261+
write_lock(&kvm->mmu_lock);
262+
263+
/*
264+
* Recheck for an existing root after acquiring mmu_lock for write. It
265+
* is possible a new usable root was created between dropping mmu_lock
266+
* (for read) and acquiring it for write.
267+
*/
268+
root = kvm_tdp_mmu_try_get_root(vcpu);
269+
if (root)
270+
goto out_unlock;
271+
241272
root = tdp_mmu_alloc_sp(vcpu);
242273
tdp_mmu_init_sp(root, NULL, 0, role);
243274

@@ -254,8 +285,17 @@ hpa_t kvm_tdp_mmu_get_vcpu_root_hpa(struct kvm_vcpu *vcpu)
254285
list_add_rcu(&root->link, &kvm->arch.tdp_mmu_roots);
255286
spin_unlock(&kvm->arch.tdp_mmu_pages_lock);
256287

288+
out_unlock:
289+
write_unlock(&kvm->mmu_lock);
257290
out:
258-
return __pa(root->spt);
291+
/*
292+
* Note, KVM_REQ_MMU_FREE_OBSOLETE_ROOTS will prevent entering the guest
293+
* and actually consuming the root if it's invalidated after dropping
294+
* mmu_lock, and the root can't be freed as this vCPU holds a reference.
295+
*/
296+
mmu->root.hpa = __pa(root->spt);
297+
mmu->root.pgd = 0;
298+
return 0;
259299
}
260300

261301
static void handle_changed_spte(struct kvm *kvm, int as_id, gfn_t gfn,
@@ -917,7 +957,7 @@ void kvm_tdp_mmu_zap_invalidated_roots(struct kvm *kvm)
917957
* the VM is being destroyed).
918958
*
919959
* Note, kvm_tdp_mmu_zap_invalidated_roots() is gifted the TDP MMU's reference.
920-
* See kvm_tdp_mmu_get_vcpu_root_hpa().
960+
* See kvm_tdp_mmu_alloc_root().
921961
*/
922962
void kvm_tdp_mmu_invalidate_all_roots(struct kvm *kvm)
923963
{

arch/x86/kvm/mmu/tdp_mmu.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
void kvm_mmu_init_tdp_mmu(struct kvm *kvm);
1111
void kvm_mmu_uninit_tdp_mmu(struct kvm *kvm);
1212

13-
hpa_t kvm_tdp_mmu_get_vcpu_root_hpa(struct kvm_vcpu *vcpu);
13+
int kvm_tdp_mmu_alloc_root(struct kvm_vcpu *vcpu);
1414

1515
__must_check static inline bool kvm_tdp_mmu_get_root(struct kvm_mmu_page *root)
1616
{

0 commit comments

Comments
 (0)