perf_test_util.c (7999B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Copyright (C) 2020, Google LLC. 4 */ 5#include <inttypes.h> 6 7#include "kvm_util.h" 8#include "perf_test_util.h" 9#include "processor.h" 10 11struct perf_test_args perf_test_args; 12 13/* 14 * Guest virtual memory offset of the testing memory slot. 15 * Must not conflict with identity mapped test code. 16 */ 17static uint64_t guest_test_virt_mem = DEFAULT_GUEST_TEST_MEM; 18 19struct vcpu_thread { 20 /* The id of the vCPU. */ 21 int vcpu_id; 22 23 /* The pthread backing the vCPU. */ 24 pthread_t thread; 25 26 /* Set to true once the vCPU thread is up and running. */ 27 bool running; 28}; 29 30/* The vCPU threads involved in this test. */ 31static struct vcpu_thread vcpu_threads[KVM_MAX_VCPUS]; 32 33/* The function run by each vCPU thread, as provided by the test. */ 34static void (*vcpu_thread_fn)(struct perf_test_vcpu_args *); 35 36/* Set to true once all vCPU threads are up and running. */ 37static bool all_vcpu_threads_running; 38 39/* 40 * Continuously write to the first 8 bytes of each page in the 41 * specified region. 42 */ 43void perf_test_guest_code(uint32_t vcpu_id) 44{ 45 struct perf_test_args *pta = &perf_test_args; 46 struct perf_test_vcpu_args *vcpu_args = &pta->vcpu_args[vcpu_id]; 47 uint64_t gva; 48 uint64_t pages; 49 int i; 50 51 /* Make sure vCPU args data structure is not corrupt. */ 52 GUEST_ASSERT(vcpu_args->vcpu_id == vcpu_id); 53 54 gva = vcpu_args->gva; 55 pages = vcpu_args->pages; 56 57 while (true) { 58 for (i = 0; i < pages; i++) { 59 uint64_t addr = gva + (i * pta->guest_page_size); 60 61 if (i % pta->wr_fract == 0) 62 *(uint64_t *)addr = 0x0123456789ABCDEF; 63 else 64 READ_ONCE(*(uint64_t *)addr); 65 } 66 67 GUEST_SYNC(1); 68 } 69} 70 71void perf_test_setup_vcpus(struct kvm_vm *vm, int vcpus, 72 uint64_t vcpu_memory_bytes, 73 bool partition_vcpu_memory_access) 74{ 75 struct perf_test_args *pta = &perf_test_args; 76 struct perf_test_vcpu_args *vcpu_args; 77 int vcpu_id; 78 79 for (vcpu_id = 0; vcpu_id < vcpus; vcpu_id++) { 80 vcpu_args = &pta->vcpu_args[vcpu_id]; 81 82 vcpu_args->vcpu_id = vcpu_id; 83 if (partition_vcpu_memory_access) { 84 vcpu_args->gva = guest_test_virt_mem + 85 (vcpu_id * vcpu_memory_bytes); 86 vcpu_args->pages = vcpu_memory_bytes / 87 pta->guest_page_size; 88 vcpu_args->gpa = pta->gpa + (vcpu_id * vcpu_memory_bytes); 89 } else { 90 vcpu_args->gva = guest_test_virt_mem; 91 vcpu_args->pages = (vcpus * vcpu_memory_bytes) / 92 pta->guest_page_size; 93 vcpu_args->gpa = pta->gpa; 94 } 95 96 vcpu_args_set(vm, vcpu_id, 1, vcpu_id); 97 98 pr_debug("Added VCPU %d with test mem gpa [%lx, %lx)\n", 99 vcpu_id, vcpu_args->gpa, vcpu_args->gpa + 100 (vcpu_args->pages * pta->guest_page_size)); 101 } 102} 103 104struct kvm_vm *perf_test_create_vm(enum vm_guest_mode mode, int vcpus, 105 uint64_t vcpu_memory_bytes, int slots, 106 enum vm_mem_backing_src_type backing_src, 107 bool partition_vcpu_memory_access) 108{ 109 struct perf_test_args *pta = &perf_test_args; 110 struct kvm_vm *vm; 111 uint64_t guest_num_pages, slot0_pages = DEFAULT_GUEST_PHY_PAGES; 112 uint64_t backing_src_pagesz = get_backing_src_pagesz(backing_src); 113 uint64_t region_end_gfn; 114 int i; 115 116 pr_info("Testing guest mode: %s\n", vm_guest_mode_string(mode)); 117 118 /* By default vCPUs will write to memory. */ 119 pta->wr_fract = 1; 120 121 /* 122 * Snapshot the non-huge page size. This is used by the guest code to 123 * access/dirty pages at the logging granularity. 124 */ 125 pta->guest_page_size = vm_guest_mode_params[mode].page_size; 126 127 guest_num_pages = vm_adjust_num_guest_pages(mode, 128 (vcpus * vcpu_memory_bytes) / pta->guest_page_size); 129 130 TEST_ASSERT(vcpu_memory_bytes % getpagesize() == 0, 131 "Guest memory size is not host page size aligned."); 132 TEST_ASSERT(vcpu_memory_bytes % pta->guest_page_size == 0, 133 "Guest memory size is not guest page size aligned."); 134 TEST_ASSERT(guest_num_pages % slots == 0, 135 "Guest memory cannot be evenly divided into %d slots.", 136 slots); 137 138 /* 139 * If using nested, allocate extra pages for the nested page tables and 140 * in-memory data structures. 141 */ 142 if (pta->nested) 143 slot0_pages += perf_test_nested_pages(vcpus); 144 145 /* 146 * Pass guest_num_pages to populate the page tables for test memory. 147 * The memory is also added to memslot 0, but that's a benign side 148 * effect as KVM allows aliasing HVAs in meslots. 149 */ 150 vm = vm_create_with_vcpus(mode, vcpus, slot0_pages, guest_num_pages, 0, 151 perf_test_guest_code, NULL); 152 153 pta->vm = vm; 154 155 /* Put the test region at the top guest physical memory. */ 156 region_end_gfn = vm_get_max_gfn(vm) + 1; 157 158#ifdef __x86_64__ 159 /* 160 * When running vCPUs in L2, restrict the test region to 48 bits to 161 * avoid needing 5-level page tables to identity map L2. 162 */ 163 if (pta->nested) 164 region_end_gfn = min(region_end_gfn, (1UL << 48) / pta->guest_page_size); 165#endif 166 /* 167 * If there should be more memory in the guest test region than there 168 * can be pages in the guest, it will definitely cause problems. 169 */ 170 TEST_ASSERT(guest_num_pages < region_end_gfn, 171 "Requested more guest memory than address space allows.\n" 172 " guest pages: %" PRIx64 " max gfn: %" PRIx64 173 " vcpus: %d wss: %" PRIx64 "]\n", 174 guest_num_pages, region_end_gfn - 1, vcpus, 175 vcpu_memory_bytes); 176 177 pta->gpa = (region_end_gfn - guest_num_pages) * pta->guest_page_size; 178 pta->gpa = align_down(pta->gpa, backing_src_pagesz); 179#ifdef __s390x__ 180 /* Align to 1M (segment size) */ 181 pta->gpa = align_down(pta->gpa, 1 << 20); 182#endif 183 pta->size = guest_num_pages * pta->guest_page_size; 184 pr_info("guest physical test memory: [0x%lx, 0x%lx)\n", 185 pta->gpa, pta->gpa + pta->size); 186 187 /* Add extra memory slots for testing */ 188 for (i = 0; i < slots; i++) { 189 uint64_t region_pages = guest_num_pages / slots; 190 vm_paddr_t region_start = pta->gpa + region_pages * pta->guest_page_size * i; 191 192 vm_userspace_mem_region_add(vm, backing_src, region_start, 193 PERF_TEST_MEM_SLOT_INDEX + i, 194 region_pages, 0); 195 } 196 197 /* Do mapping for the demand paging memory slot */ 198 virt_map(vm, guest_test_virt_mem, pta->gpa, guest_num_pages); 199 200 perf_test_setup_vcpus(vm, vcpus, vcpu_memory_bytes, partition_vcpu_memory_access); 201 202 if (pta->nested) { 203 pr_info("Configuring vCPUs to run in L2 (nested).\n"); 204 perf_test_setup_nested(vm, vcpus); 205 } 206 207 ucall_init(vm, NULL); 208 209 /* Export the shared variables to the guest. */ 210 sync_global_to_guest(vm, perf_test_args); 211 212 return vm; 213} 214 215void perf_test_destroy_vm(struct kvm_vm *vm) 216{ 217 ucall_uninit(vm); 218 kvm_vm_free(vm); 219} 220 221void perf_test_set_wr_fract(struct kvm_vm *vm, int wr_fract) 222{ 223 perf_test_args.wr_fract = wr_fract; 224 sync_global_to_guest(vm, perf_test_args); 225} 226 227uint64_t __weak perf_test_nested_pages(int nr_vcpus) 228{ 229 return 0; 230} 231 232void __weak perf_test_setup_nested(struct kvm_vm *vm, int nr_vcpus) 233{ 234 pr_info("%s() not support on this architecture, skipping.\n", __func__); 235 exit(KSFT_SKIP); 236} 237 238static void *vcpu_thread_main(void *data) 239{ 240 struct vcpu_thread *vcpu = data; 241 242 WRITE_ONCE(vcpu->running, true); 243 244 /* 245 * Wait for all vCPU threads to be up and running before calling the test- 246 * provided vCPU thread function. This prevents thread creation (which 247 * requires taking the mmap_sem in write mode) from interfering with the 248 * guest faulting in its memory. 249 */ 250 while (!READ_ONCE(all_vcpu_threads_running)) 251 ; 252 253 vcpu_thread_fn(&perf_test_args.vcpu_args[vcpu->vcpu_id]); 254 255 return NULL; 256} 257 258void perf_test_start_vcpu_threads(int vcpus, void (*vcpu_fn)(struct perf_test_vcpu_args *)) 259{ 260 int vcpu_id; 261 262 vcpu_thread_fn = vcpu_fn; 263 WRITE_ONCE(all_vcpu_threads_running, false); 264 265 for (vcpu_id = 0; vcpu_id < vcpus; vcpu_id++) { 266 struct vcpu_thread *vcpu = &vcpu_threads[vcpu_id]; 267 268 vcpu->vcpu_id = vcpu_id; 269 WRITE_ONCE(vcpu->running, false); 270 271 pthread_create(&vcpu->thread, NULL, vcpu_thread_main, vcpu); 272 } 273 274 for (vcpu_id = 0; vcpu_id < vcpus; vcpu_id++) { 275 while (!READ_ONCE(vcpu_threads[vcpu_id].running)) 276 ; 277 } 278 279 WRITE_ONCE(all_vcpu_threads_running, true); 280} 281 282void perf_test_join_vcpu_threads(int vcpus) 283{ 284 int vcpu_id; 285 286 for (vcpu_id = 0; vcpu_id < vcpus; vcpu_id++) 287 pthread_join(vcpu_threads[vcpu_id].thread, NULL); 288}