Module Name:    src
Committed By:   maxv
Date:           Thu Feb 21 12:17:52 UTC 2019

Modified Files:
        src/sys/arch/x86/x86: x86_tlb.c
        src/sys/dev/nvmm/x86: nvmm_x86_svm.c nvmm_x86_vmx.c

Log Message:
Another locking issue in NVMM: the {svm,vmx}_tlb_flush functions take VCPU
mutexes which can sleep, but their context does not allow it.

Rewrite the TLB handling code to fix that. It becomes a bit complex. In
short, we use a per-VM generation number, which we increase on each TLB
flush, before sending a broadcast IPI to everybody. The IPIs cause a
#VMEXIT of each VCPU, and each VCPU Loop will synchronize the per-VM gen
with a per-VCPU copy, and apply the flushes as neededi lazily.

The behavior differs between AMD and Intel; in short, on Intel we don't
flush the hTLB (EPT cache) if a context switch of a VCPU occurs, so now,
we need to maintain a kcpuset to know which VCPU's hTLBs are active on
which hCPU. This creates some redundancy on Intel, ie there are cases
where we flush the hTLB several times unnecessarily; but hTLB flushes are
very rare, so there is no real performance regression.

The thing is lock-less and non-blocking, so it solves our problem.


To generate a diff of this commit:
cvs rdiff -u -r1.5 -r1.6 src/sys/arch/x86/x86/x86_tlb.c
cvs rdiff -u -r1.28 -r1.29 src/sys/dev/nvmm/x86/nvmm_x86_svm.c
cvs rdiff -u -r1.8 -r1.9 src/sys/dev/nvmm/x86/nvmm_x86_vmx.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/x86/x86/x86_tlb.c
diff -u src/sys/arch/x86/x86/x86_tlb.c:1.5 src/sys/arch/x86/x86/x86_tlb.c:1.6
--- src/sys/arch/x86/x86/x86_tlb.c:1.5	Mon Feb 11 14:59:33 2019
+++ src/sys/arch/x86/x86/x86_tlb.c	Thu Feb 21 12:17:52 2019
@@ -1,4 +1,4 @@
-/*	$NetBSD: x86_tlb.c,v 1.5 2019/02/11 14:59:33 cherry Exp $	*/
+/*	$NetBSD: x86_tlb.c,v 1.6 2019/02/21 12:17:52 maxv Exp $	*/
 
 /*-
  * Copyright (c) 2008-2012 The NetBSD Foundation, Inc.
@@ -40,7 +40,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: x86_tlb.c,v 1.5 2019/02/11 14:59:33 cherry Exp $");
+__KERNEL_RCSID(0, "$NetBSD: x86_tlb.c,v 1.6 2019/02/21 12:17:52 maxv Exp $");
 
 #include <sys/param.h>
 #include <sys/kernel.h>
@@ -229,6 +229,7 @@ pmap_tlb_shootdown(struct pmap *pm, vadd
 
 	if (__predict_false(pm->pm_tlb_flush != NULL)) {
 		(*pm->pm_tlb_flush)(pm);
+		return;
 	}
 
 	/*

Index: src/sys/dev/nvmm/x86/nvmm_x86_svm.c
diff -u src/sys/dev/nvmm/x86/nvmm_x86_svm.c:1.28 src/sys/dev/nvmm/x86/nvmm_x86_svm.c:1.29
--- src/sys/dev/nvmm/x86/nvmm_x86_svm.c:1.28	Thu Feb 21 11:58:04 2019
+++ src/sys/dev/nvmm/x86/nvmm_x86_svm.c	Thu Feb 21 12:17:52 2019
@@ -1,4 +1,4 @@
-/*	$NetBSD: nvmm_x86_svm.c,v 1.28 2019/02/21 11:58:04 maxv Exp $	*/
+/*	$NetBSD: nvmm_x86_svm.c,v 1.29 2019/02/21 12:17:52 maxv Exp $	*/
 
 /*
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nvmm_x86_svm.c,v 1.28 2019/02/21 11:58:04 maxv Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nvmm_x86_svm.c,v 1.29 2019/02/21 12:17:52 maxv Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -493,6 +493,7 @@ static uint64_t svm_xcr0_mask __read_mos
 struct svm_machdata {
 	bool cpuidpresent[SVM_NCPUIDS];
 	struct nvmm_x86_conf_cpuid cpuid[SVM_NCPUIDS];
+	volatile uint64_t mach_htlb_gen;
 };
 
 static const size_t svm_conf_sizes[NVMM_X86_NCONF] = {
@@ -503,6 +504,7 @@ struct svm_cpudata {
 	/* General */
 	bool shared_asid;
 	bool gtlb_want_flush;
+	uint64_t vcpu_htlb_gen;
 
 	/* VMCB */
 	struct vmcb *vmcb;
@@ -1101,6 +1103,8 @@ error:
 	svm_inject_gp(mach, vcpu);
 }
 
+/* -------------------------------------------------------------------------- */
+
 static void
 svm_vcpu_guest_fpu_enter(struct nvmm_cpu *vcpu)
 {
@@ -1197,18 +1201,57 @@ svm_gtlb_catchup(struct nvmm_cpu *vcpu, 
 	}
 }
 
+static inline void
+svm_htlb_catchup(struct nvmm_cpu *vcpu, int hcpu)
+{
+	/*
+	 * Nothing to do. If an hTLB flush was needed, either the VCPU was
+	 * executing on this hCPU and the hTLB already got flushed, or it
+	 * was executing on another hCPU in which case the catchup is done
+	 * in svm_gtlb_catchup().
+	 */
+}
+
+static inline uint64_t
+svm_htlb_flush(struct svm_machdata *machdata, struct svm_cpudata *cpudata)
+{
+	struct vmcb *vmcb = cpudata->vmcb;
+	uint64_t machgen;
+
+	machgen = machdata->mach_htlb_gen;
+	if (__predict_true(machgen == cpudata->vcpu_htlb_gen)) {
+		return machgen;
+	}
+
+	vmcb->ctrl.tlb_ctrl = svm_ctrl_tlb_flush;
+	return machgen;
+}
+
+static inline void
+svm_htlb_flush_ack(struct svm_cpudata *cpudata, uint64_t machgen)
+{
+	struct vmcb *vmcb = cpudata->vmcb;
+
+	if (__predict_true(vmcb->ctrl.exitcode != VMCB_EXITCODE_INVALID)) {
+		cpudata->vcpu_htlb_gen = machgen;
+	}
+}
+
 static int
 svm_vcpu_run(struct nvmm_machine *mach, struct nvmm_cpu *vcpu,
     struct nvmm_exit *exit)
 {
+	struct svm_machdata *machdata = mach->machdata;
 	struct svm_cpudata *cpudata = vcpu->cpudata;
 	struct vmcb *vmcb = cpudata->vmcb;
+	uint64_t machgen;
 	int hcpu, s;
 
 	kpreempt_disable();
 	hcpu = cpu_number();
 
 	svm_gtlb_catchup(vcpu, hcpu);
+	svm_htlb_catchup(vcpu, hcpu);
 
 	if (vcpu->hcpu_last != hcpu) {
 		vmcb->ctrl.tsc_offset = cpudata->tsc_offset +
@@ -1227,9 +1270,11 @@ svm_vcpu_run(struct nvmm_machine *mach, 
 		}
 
 		s = splhigh();
+		machgen = svm_htlb_flush(machdata, cpudata);
 		svm_vcpu_guest_fpu_enter(vcpu);
 		svm_vmrun(cpudata->vmcb_pa, cpudata->gprs);
 		svm_vcpu_guest_fpu_leave(vcpu);
+		svm_htlb_flush_ack(cpudata, machgen);
 		splx(s);
 
 		svm_vmcb_cache_default(vmcb);
@@ -1982,30 +2027,28 @@ static void
 svm_tlb_flush(struct pmap *pm)
 {
 	struct nvmm_machine *mach = pm->pm_data;
-	struct svm_cpudata *cpudata;
-	struct nvmm_cpu *vcpu;
-	int error;
-	size_t i;
+	struct svm_machdata *machdata = mach->machdata;
 
-	/* Request TLB flushes. */
-	for (i = 0; i < NVMM_MAX_VCPUS; i++) {
-		error = nvmm_vcpu_get(mach, i, &vcpu);
-		if (error)
-			continue;
-		cpudata = vcpu->cpudata;
-		cpudata->gtlb_want_flush = true;
-		nvmm_vcpu_put(vcpu);
-	}
+	atomic_inc_64(&machdata->mach_htlb_gen);
+
+	/* Generates IPIs, which cause #VMEXITs. */
+	pmap_tlb_shootdown(pmap_kernel(), -1, PG_G, TLBSHOOT_UPDATE);
 }
 
 static void
 svm_machine_create(struct nvmm_machine *mach)
 {
+	struct svm_machdata *machdata;
+
 	/* Fill in pmap info. */
 	mach->vm->vm_map.pmap->pm_data = (void *)mach;
 	mach->vm->vm_map.pmap->pm_tlb_flush = svm_tlb_flush;
 
-	mach->machdata = kmem_zalloc(sizeof(struct svm_machdata), KM_SLEEP);
+	machdata = kmem_zalloc(sizeof(struct svm_machdata), KM_SLEEP);
+	mach->machdata = machdata;
+
+	/* Start with an hTLB flush everywhere. */
+	machdata->mach_htlb_gen = 1;
 }
 
 static void

Index: src/sys/dev/nvmm/x86/nvmm_x86_vmx.c
diff -u src/sys/dev/nvmm/x86/nvmm_x86_vmx.c:1.8 src/sys/dev/nvmm/x86/nvmm_x86_vmx.c:1.9
--- src/sys/dev/nvmm/x86/nvmm_x86_vmx.c:1.8	Thu Feb 21 11:58:04 2019
+++ src/sys/dev/nvmm/x86/nvmm_x86_vmx.c	Thu Feb 21 12:17:52 2019
@@ -1,4 +1,4 @@
-/*	$NetBSD: nvmm_x86_vmx.c,v 1.8 2019/02/21 11:58:04 maxv Exp $	*/
+/*	$NetBSD: nvmm_x86_vmx.c,v 1.9 2019/02/21 12:17:52 maxv Exp $	*/
 
 /*
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nvmm_x86_vmx.c,v 1.8 2019/02/21 11:58:04 maxv Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nvmm_x86_vmx.c,v 1.9 2019/02/21 12:17:52 maxv Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -627,7 +627,7 @@ static uint64_t vmx_xcr0_mask __read_mos
 struct vmx_machdata {
 	bool cpuidpresent[VMX_NCPUIDS];
 	struct nvmm_x86_conf_cpuid cpuid[VMX_NCPUIDS];
-	kcpuset_t *ept_want_flush;
+	volatile uint64_t mach_htlb_gen;
 };
 
 static const size_t vmx_conf_sizes[NVMM_X86_NCONF] = {
@@ -638,6 +638,8 @@ struct vmx_cpudata {
 	/* General */
 	uint64_t asid;
 	bool gtlb_want_flush;
+	uint64_t vcpu_htlb_gen;
+	kcpuset_t *htlb_want_flush;
 
 	/* VMCS */
 	struct vmcs *vmcs;
@@ -1510,6 +1512,8 @@ vmx_exit_epf(struct nvmm_machine *mach, 
 	exit->u.mem.inst_len = 0;
 }
 
+/* -------------------------------------------------------------------------- */
+
 static void
 vmx_vcpu_guest_fpu_enter(struct nvmm_cpu *vcpu)
 {
@@ -1601,7 +1605,7 @@ vmx_vcpu_guest_misc_leave(struct nvmm_cp
 	wrmsr(MSR_KERNELGSBASE, cpudata->kernelgsbase);
 }
 
-/* --------------------------------------------------------------------- */
+/* -------------------------------------------------------------------------- */
 
 #define VMX_INVVPID_ADDRESS		0
 #define VMX_INVVPID_CONTEXT		1
@@ -1621,6 +1625,49 @@ vmx_gtlb_catchup(struct nvmm_cpu *vcpu, 
 	}
 }
 
+static inline void
+vmx_htlb_catchup(struct nvmm_cpu *vcpu, int hcpu)
+{
+	struct vmx_cpudata *cpudata = vcpu->cpudata;
+	struct ept_desc ept_desc;
+
+	if (__predict_true(!kcpuset_isset(cpudata->htlb_want_flush, hcpu))) {
+		return;
+	}
+
+	vmx_vmread(VMCS_EPTP, &ept_desc.eptp);
+	ept_desc.mbz = 0;
+	vmx_invept(vmx_ept_flush_op, &ept_desc);
+	kcpuset_clear(cpudata->htlb_want_flush, hcpu);
+}
+
+static inline uint64_t
+vmx_htlb_flush(struct vmx_machdata *machdata, struct vmx_cpudata *cpudata)
+{
+	struct ept_desc ept_desc;
+	uint64_t machgen;
+
+	machgen = machdata->mach_htlb_gen;
+	if (__predict_true(machgen == cpudata->vcpu_htlb_gen)) {
+		return machgen;
+	}
+
+	kcpuset_copy(cpudata->htlb_want_flush, kcpuset_running);
+
+	vmx_vmread(VMCS_EPTP, &ept_desc.eptp);
+	ept_desc.mbz = 0;
+	vmx_invept(vmx_ept_flush_op, &ept_desc);
+
+	return machgen;
+}
+
+static inline void
+vmx_htlb_flush_ack(struct vmx_cpudata *cpudata, uint64_t machgen)
+{
+	cpudata->vcpu_htlb_gen = machgen;
+	kcpuset_clear(cpudata->htlb_want_flush, cpu_number());
+}
+
 static int
 vmx_vcpu_run(struct nvmm_machine *mach, struct nvmm_cpu *vcpu,
     struct nvmm_exit *exit)
@@ -1628,10 +1675,10 @@ vmx_vcpu_run(struct nvmm_machine *mach, 
 	struct vmx_machdata *machdata = mach->machdata;
 	struct vmx_cpudata *cpudata = vcpu->cpudata;
 	struct vpid_desc vpid_desc;
-	struct ept_desc ept_desc;
 	struct cpu_info *ci;
 	uint64_t exitcode;
 	uint64_t intstate;
+	uint64_t machgen;
 	int hcpu, s, ret;
 	bool launched = false;
 
@@ -1640,13 +1687,7 @@ vmx_vcpu_run(struct nvmm_machine *mach, 
 	hcpu = cpu_number();
 
 	vmx_gtlb_catchup(vcpu, hcpu);
-
-	if (__predict_false(kcpuset_isset(machdata->ept_want_flush, hcpu))) {
-		vmx_vmread(VMCS_EPTP, &ept_desc.eptp);
-		ept_desc.mbz = 0;
-		vmx_invept(vmx_ept_flush_op, &ept_desc);
-		kcpuset_clear(machdata->ept_want_flush, hcpu);
-	}
+	vmx_htlb_catchup(vcpu, hcpu);
 
 	if (vcpu->hcpu_last != hcpu) {
 		vmx_vmwrite(VMCS_HOST_TR_SELECTOR, ci->ci_tss_sel);
@@ -1670,6 +1711,7 @@ vmx_vcpu_run(struct nvmm_machine *mach, 
 		}
 
 		s = splhigh();
+		machgen = vmx_htlb_flush(machdata, cpudata);
 		vmx_vcpu_guest_fpu_enter(vcpu);
 		lcr2(cpudata->gcr2);
 		if (launched) {
@@ -1679,6 +1721,7 @@ vmx_vcpu_run(struct nvmm_machine *mach, 
 		}
 		cpudata->gcr2 = rcr2();
 		vmx_vcpu_guest_fpu_leave(vcpu);
+		vmx_htlb_flush_ack(cpudata, machgen);
 		splx(s);
 
 		if (__predict_false(ret != 0)) {
@@ -2089,6 +2132,8 @@ vmx_vcpu_create(struct nvmm_machine *mac
 	if (error)
 		goto error;
 
+	kcpuset_create(&cpudata->htlb_want_flush, true);
+
 	/* Init the VCPU info. */
 	vmx_vcpu_init(mach, vcpu);
 
@@ -2120,6 +2165,8 @@ vmx_vcpu_destroy(struct nvmm_machine *ma
 	vmx_asid_free(vcpu);
 	vmx_vmcs_leave(vcpu);
 
+	kcpuset_destroy(cpudata->htlb_want_flush);
+
 	vmx_memfree(cpudata->vmcs_pa, (vaddr_t)cpudata->vmcs, VMCS_NPAGES);
 	vmx_memfree(cpudata->msrbm_pa, (vaddr_t)cpudata->msrbm, MSRBM_NPAGES);
 	vmx_memfree(cpudata->gmsr_pa, (vaddr_t)cpudata->gmsr, 1);
@@ -2450,23 +2497,11 @@ vmx_tlb_flush(struct pmap *pm)
 {
 	struct nvmm_machine *mach = pm->pm_data;
 	struct vmx_machdata *machdata = mach->machdata;
-	struct nvmm_cpu *vcpu;
-	int error;
-	size_t i;
 
-	kcpuset_atomicly_merge(machdata->ept_want_flush, kcpuset_running);
+	atomic_inc_64(&machdata->mach_htlb_gen);
 
-	/*
-	 * Not as dumb as it seems. We want to make sure that when we leave
-	 * this function, each VCPU got halted at some point, and possibly
-	 * resumed with the updated kcpuset.
-	 */
-	for (i = 0; i < NVMM_MAX_VCPUS; i++) {
-		error = nvmm_vcpu_get(mach, i, &vcpu);
-		if (error)
-			continue;
-		nvmm_vcpu_put(vcpu);
-	}
+	/* Generates IPIs, which cause #VMEXITs. */
+	pmap_tlb_shootdown(pmap_kernel(), -1, PG_G, TLBSHOOT_UPDATE);
 }
 
 static void
@@ -2483,11 +2518,10 @@ vmx_machine_create(struct nvmm_machine *
 	pmap->pm_tlb_flush = vmx_tlb_flush;
 
 	machdata = kmem_zalloc(sizeof(struct vmx_machdata), KM_SLEEP);
-	kcpuset_create(&machdata->ept_want_flush, true);
 	mach->machdata = machdata;
 
-	/* Start with an EPT flush everywhere. */
-	kcpuset_copy(machdata->ept_want_flush, kcpuset_running);
+	/* Start with an hTLB flush everywhere. */
+	machdata->mach_htlb_gen = 1;
 }
 
 static void
@@ -2495,7 +2529,6 @@ vmx_machine_destroy(struct nvmm_machine 
 {
 	struct vmx_machdata *machdata = mach->machdata;
 
-	kcpuset_destroy(machdata->ept_want_flush);
 	kmem_free(machdata, sizeof(struct vmx_machdata));
 }
 

Reply via email to