Skip to content

Commit 71d4896

Browse files
dmatlackbonzini
authored andcommitted
KVM: selftests: Add option to run dirty_log_perf_test vCPUs in L2
Add an option to dirty_log_perf_test that configures the vCPUs to run in L2 instead of L1. This makes it possible to benchmark the dirty logging performance of nested virtualization, which is particularly interesting because KVM must shadow L1's EPT/NPT tables. For now this support only works on x86_64 CPUs with VMX. Otherwise passing -n results in the test being skipped. Signed-off-by: David Matlack <dmatlack@google.com> Message-Id: <20220520233249.3776001-11-dmatlack@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent cf97d5e commit 71d4896

File tree

8 files changed

+182
-8
lines changed

8 files changed

+182
-8
lines changed

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ LIBKVM += lib/test_util.c
4949

5050
LIBKVM_x86_64 += lib/x86_64/apic.c
5151
LIBKVM_x86_64 += lib/x86_64/handlers.S
52+
LIBKVM_x86_64 += lib/x86_64/perf_test_util.c
5253
LIBKVM_x86_64 += lib/x86_64/processor.c
5354
LIBKVM_x86_64 += lib/x86_64/svm.c
5455
LIBKVM_x86_64 += lib/x86_64/ucall.c

tools/testing/selftests/kvm/dirty_log_perf_test.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,8 @@ static void run_test(enum vm_guest_mode mode, void *arg)
336336
static void help(char *name)
337337
{
338338
puts("");
339-
printf("usage: %s [-h] [-i iterations] [-p offset] [-g]"
340-
"[-m mode] [-b vcpu bytes] [-v vcpus] [-o] [-s mem type]"
339+
printf("usage: %s [-h] [-i iterations] [-p offset] [-g] "
340+
"[-m mode] [-n] [-b vcpu bytes] [-v vcpus] [-o] [-s mem type]"
341341
"[-x memslots]\n", name);
342342
puts("");
343343
printf(" -i: specify iteration counts (default: %"PRIu64")\n",
@@ -351,6 +351,7 @@ static void help(char *name)
351351
printf(" -p: specify guest physical test memory offset\n"
352352
" Warning: a low offset can conflict with the loaded test code.\n");
353353
guest_modes_help();
354+
printf(" -n: Run the vCPUs in nested mode (L2)\n");
354355
printf(" -b: specify the size of the memory region which should be\n"
355356
" dirtied by each vCPU. e.g. 10M or 3G.\n"
356357
" (default: 1G)\n");
@@ -387,7 +388,7 @@ int main(int argc, char *argv[])
387388

388389
guest_modes_append_default();
389390

390-
while ((opt = getopt(argc, argv, "ghi:p:m:b:f:v:os:x:")) != -1) {
391+
while ((opt = getopt(argc, argv, "ghi:p:m:nb:f:v:os:x:")) != -1) {
391392
switch (opt) {
392393
case 'g':
393394
dirty_log_manual_caps = 0;
@@ -401,6 +402,9 @@ int main(int argc, char *argv[])
401402
case 'm':
402403
guest_modes_cmdline(optarg);
403404
break;
405+
case 'n':
406+
perf_test_args.nested = true;
407+
break;
404408
case 'b':
405409
guest_percpu_mem_size = parse_size(optarg);
406410
break;

tools/testing/selftests/kvm/include/perf_test_util.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,15 @@ struct perf_test_vcpu_args {
3030

3131
struct perf_test_args {
3232
struct kvm_vm *vm;
33+
/* The starting address and size of the guest test region. */
3334
uint64_t gpa;
35+
uint64_t size;
3436
uint64_t guest_page_size;
3537
int wr_fract;
3638

39+
/* Run vCPUs in L2 instead of L1, if the architecture supports it. */
40+
bool nested;
41+
3742
struct perf_test_vcpu_args vcpu_args[KVM_MAX_VCPUS];
3843
};
3944

@@ -49,5 +54,9 @@ void perf_test_set_wr_fract(struct kvm_vm *vm, int wr_fract);
4954

5055
void perf_test_start_vcpu_threads(int vcpus, void (*vcpu_fn)(struct perf_test_vcpu_args *));
5156
void perf_test_join_vcpu_threads(int vcpus);
57+
void perf_test_guest_code(uint32_t vcpu_id);
58+
59+
uint64_t perf_test_nested_pages(int nr_vcpus);
60+
void perf_test_setup_nested(struct kvm_vm *vm, int nr_vcpus);
5261

5362
#endif /* SELFTEST_KVM_PERF_TEST_UTIL_H */

tools/testing/selftests/kvm/include/x86_64/processor.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,10 @@ enum pg_level {
494494
#define PG_LEVEL_SHIFT(_level) ((_level - 1) * 9 + 12)
495495
#define PG_LEVEL_SIZE(_level) (1ull << PG_LEVEL_SHIFT(_level))
496496

497+
#define PG_SIZE_4K PG_LEVEL_SIZE(PG_LEVEL_4K)
498+
#define PG_SIZE_2M PG_LEVEL_SIZE(PG_LEVEL_2M)
499+
#define PG_SIZE_1G PG_LEVEL_SIZE(PG_LEVEL_1G)
500+
497501
void __virt_pg_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, int level);
498502

499503
/*

tools/testing/selftests/kvm/include/x86_64/vmx.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
#define VMX_MISC_PREEMPTION_TIMER_RATE_MASK 0x0000001f
9797
#define VMX_MISC_SAVE_EFER_LMA 0x00000020
9898

99+
#define VMX_EPT_VPID_CAP_1G_PAGES 0x00020000
99100
#define VMX_EPT_VPID_CAP_AD_BITS 0x00200000
100101

101102
#define EXIT_REASON_FAILED_VMENTRY 0x80000000
@@ -608,13 +609,16 @@ bool load_vmcs(struct vmx_pages *vmx);
608609

609610
bool nested_vmx_supported(void);
610611
void nested_vmx_check_supported(void);
612+
bool ept_1g_pages_supported(void);
611613

612614
void nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm,
613615
uint64_t nested_paddr, uint64_t paddr);
614616
void nested_map(struct vmx_pages *vmx, struct kvm_vm *vm,
615617
uint64_t nested_paddr, uint64_t paddr, uint64_t size);
616618
void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm,
617619
uint32_t memslot);
620+
void nested_identity_map_1g(struct vmx_pages *vmx, struct kvm_vm *vm,
621+
uint64_t addr, uint64_t size);
618622
void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm,
619623
uint32_t eptp_memslot);
620624
void prepare_virtualize_apic_accesses(struct vmx_pages *vmx, struct kvm_vm *vm);

tools/testing/selftests/kvm/lib/perf_test_util.c

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ static bool all_vcpu_threads_running;
4040
* Continuously write to the first 8 bytes of each page in the
4141
* specified region.
4242
*/
43-
static void guest_code(uint32_t vcpu_id)
43+
void perf_test_guest_code(uint32_t vcpu_id)
4444
{
4545
struct perf_test_args *pta = &perf_test_args;
4646
struct perf_test_vcpu_args *vcpu_args = &pta->vcpu_args[vcpu_id];
@@ -108,7 +108,7 @@ struct kvm_vm *perf_test_create_vm(enum vm_guest_mode mode, int vcpus,
108108
{
109109
struct perf_test_args *pta = &perf_test_args;
110110
struct kvm_vm *vm;
111-
uint64_t guest_num_pages;
111+
uint64_t guest_num_pages, slot0_pages = DEFAULT_GUEST_PHY_PAGES;
112112
uint64_t backing_src_pagesz = get_backing_src_pagesz(backing_src);
113113
int i;
114114

@@ -134,13 +134,20 @@ struct kvm_vm *perf_test_create_vm(enum vm_guest_mode mode, int vcpus,
134134
"Guest memory cannot be evenly divided into %d slots.",
135135
slots);
136136

137+
/*
138+
* If using nested, allocate extra pages for the nested page tables and
139+
* in-memory data structures.
140+
*/
141+
if (pta->nested)
142+
slot0_pages += perf_test_nested_pages(vcpus);
143+
137144
/*
138145
* Pass guest_num_pages to populate the page tables for test memory.
139146
* The memory is also added to memslot 0, but that's a benign side
140147
* effect as KVM allows aliasing HVAs in meslots.
141148
*/
142-
vm = vm_create_with_vcpus(mode, vcpus, DEFAULT_GUEST_PHY_PAGES,
143-
guest_num_pages, 0, guest_code, NULL);
149+
vm = vm_create_with_vcpus(mode, vcpus, slot0_pages, guest_num_pages, 0,
150+
perf_test_guest_code, NULL);
144151

145152
pta->vm = vm;
146153

@@ -161,7 +168,9 @@ struct kvm_vm *perf_test_create_vm(enum vm_guest_mode mode, int vcpus,
161168
/* Align to 1M (segment size) */
162169
pta->gpa = align_down(pta->gpa, 1 << 20);
163170
#endif
164-
pr_info("guest physical test memory offset: 0x%lx\n", pta->gpa);
171+
pta->size = guest_num_pages * pta->guest_page_size;
172+
pr_info("guest physical test memory: [0x%lx, 0x%lx)\n",
173+
pta->gpa, pta->gpa + pta->size);
165174

166175
/* Add extra memory slots for testing */
167176
for (i = 0; i < slots; i++) {
@@ -178,6 +187,11 @@ struct kvm_vm *perf_test_create_vm(enum vm_guest_mode mode, int vcpus,
178187

179188
perf_test_setup_vcpus(vm, vcpus, vcpu_memory_bytes, partition_vcpu_memory_access);
180189

190+
if (pta->nested) {
191+
pr_info("Configuring vCPUs to run in L2 (nested).\n");
192+
perf_test_setup_nested(vm, vcpus);
193+
}
194+
181195
ucall_init(vm, NULL);
182196

183197
/* Export the shared variables to the guest. */
@@ -198,6 +212,17 @@ void perf_test_set_wr_fract(struct kvm_vm *vm, int wr_fract)
198212
sync_global_to_guest(vm, perf_test_args);
199213
}
200214

215+
uint64_t __weak perf_test_nested_pages(int nr_vcpus)
216+
{
217+
return 0;
218+
}
219+
220+
void __weak perf_test_setup_nested(struct kvm_vm *vm, int nr_vcpus)
221+
{
222+
pr_info("%s() not support on this architecture, skipping.\n", __func__);
223+
exit(KSFT_SKIP);
224+
}
225+
201226
static void *vcpu_thread_main(void *data)
202227
{
203228
struct vcpu_thread *vcpu = data;
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* x86_64-specific extensions to perf_test_util.c.
4+
*
5+
* Copyright (C) 2022, Google, Inc.
6+
*/
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <linux/bitmap.h>
10+
#include <linux/bitops.h>
11+
12+
#include "test_util.h"
13+
#include "kvm_util.h"
14+
#include "perf_test_util.h"
15+
#include "../kvm_util_internal.h"
16+
#include "processor.h"
17+
#include "vmx.h"
18+
19+
void perf_test_l2_guest_code(uint64_t vcpu_id)
20+
{
21+
perf_test_guest_code(vcpu_id);
22+
vmcall();
23+
}
24+
25+
extern char perf_test_l2_guest_entry[];
26+
__asm__(
27+
"perf_test_l2_guest_entry:"
28+
" mov (%rsp), %rdi;"
29+
" call perf_test_l2_guest_code;"
30+
" ud2;"
31+
);
32+
33+
static void perf_test_l1_guest_code(struct vmx_pages *vmx, uint64_t vcpu_id)
34+
{
35+
#define L2_GUEST_STACK_SIZE 64
36+
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
37+
unsigned long *rsp;
38+
39+
GUEST_ASSERT(vmx->vmcs_gpa);
40+
GUEST_ASSERT(prepare_for_vmx_operation(vmx));
41+
GUEST_ASSERT(load_vmcs(vmx));
42+
GUEST_ASSERT(ept_1g_pages_supported());
43+
44+
rsp = &l2_guest_stack[L2_GUEST_STACK_SIZE - 1];
45+
*rsp = vcpu_id;
46+
prepare_vmcs(vmx, perf_test_l2_guest_entry, rsp);
47+
48+
GUEST_ASSERT(!vmlaunch());
49+
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
50+
GUEST_DONE();
51+
}
52+
53+
uint64_t perf_test_nested_pages(int nr_vcpus)
54+
{
55+
/*
56+
* 513 page tables is enough to identity-map 256 TiB of L2 with 1G
57+
* pages and 4-level paging, plus a few pages per-vCPU for data
58+
* structures such as the VMCS.
59+
*/
60+
return 513 + 10 * nr_vcpus;
61+
}
62+
63+
void perf_test_setup_ept(struct vmx_pages *vmx, struct kvm_vm *vm)
64+
{
65+
uint64_t start, end;
66+
67+
prepare_eptp(vmx, vm, 0);
68+
69+
/*
70+
* Identity map the first 4G and the test region with 1G pages so that
71+
* KVM can shadow the EPT12 with the maximum huge page size supported
72+
* by the backing source.
73+
*/
74+
nested_identity_map_1g(vmx, vm, 0, 0x100000000ULL);
75+
76+
start = align_down(perf_test_args.gpa, PG_SIZE_1G);
77+
end = align_up(perf_test_args.gpa + perf_test_args.size, PG_SIZE_1G);
78+
nested_identity_map_1g(vmx, vm, start, end - start);
79+
}
80+
81+
void perf_test_setup_nested(struct kvm_vm *vm, int nr_vcpus)
82+
{
83+
struct vmx_pages *vmx, *vmx0 = NULL;
84+
struct kvm_regs regs;
85+
vm_vaddr_t vmx_gva;
86+
int vcpu_id;
87+
88+
nested_vmx_check_supported();
89+
90+
for (vcpu_id = 0; vcpu_id < nr_vcpus; vcpu_id++) {
91+
vmx = vcpu_alloc_vmx(vm, &vmx_gva);
92+
93+
if (vcpu_id == 0) {
94+
perf_test_setup_ept(vmx, vm);
95+
vmx0 = vmx;
96+
} else {
97+
/* Share the same EPT table across all vCPUs. */
98+
vmx->eptp = vmx0->eptp;
99+
vmx->eptp_hva = vmx0->eptp_hva;
100+
vmx->eptp_gpa = vmx0->eptp_gpa;
101+
}
102+
103+
/*
104+
* Override the vCPU to run perf_test_l1_guest_code() which will
105+
* bounce it into L2 before calling perf_test_guest_code().
106+
*/
107+
vcpu_regs_get(vm, vcpu_id, &regs);
108+
regs.rip = (unsigned long) perf_test_l1_guest_code;
109+
vcpu_regs_set(vm, vcpu_id, &regs);
110+
vcpu_args_set(vm, vcpu_id, 2, vmx_gva, vcpu_id);
111+
}
112+
}

tools/testing/selftests/kvm/lib/x86_64/vmx.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ static bool ept_vpid_cap_supported(uint64_t mask)
203203
return rdmsr(MSR_IA32_VMX_EPT_VPID_CAP) & mask;
204204
}
205205

206+
bool ept_1g_pages_supported(void)
207+
{
208+
return ept_vpid_cap_supported(VMX_EPT_VPID_CAP_1G_PAGES);
209+
}
210+
206211
/*
207212
* Initialize the control fields to the most basic settings possible.
208213
*/
@@ -439,6 +444,9 @@ void __nested_pg_map(struct vmx_pages *vmx, struct kvm_vm *vm,
439444
TEST_ASSERT(vm->mode == VM_MODE_PXXV48_4K, "Attempt to use "
440445
"unknown or unsupported guest mode, mode: 0x%x", vm->mode);
441446

447+
TEST_ASSERT((nested_paddr >> 48) == 0,
448+
"Nested physical address 0x%lx requires 5-level paging",
449+
nested_paddr);
442450
TEST_ASSERT((nested_paddr % page_size) == 0,
443451
"Nested physical address not on page boundary,\n"
444452
" nested_paddr: 0x%lx page_size: 0x%lx",
@@ -547,6 +555,13 @@ void nested_map_memslot(struct vmx_pages *vmx, struct kvm_vm *vm,
547555
}
548556
}
549557

558+
/* Identity map a region with 1GiB Pages. */
559+
void nested_identity_map_1g(struct vmx_pages *vmx, struct kvm_vm *vm,
560+
uint64_t addr, uint64_t size)
561+
{
562+
__nested_map(vmx, vm, addr, addr, size, PG_LEVEL_1G);
563+
}
564+
550565
void prepare_eptp(struct vmx_pages *vmx, struct kvm_vm *vm,
551566
uint32_t eptp_memslot)
552567
{

0 commit comments

Comments
 (0)