File Index
Files and directories
Sign in
FILE

nested_tsc_adjust_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/kvm/x86/nested_tsc_adjust_test.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (C) 2018, Google LLC.
  4 *
  5 * IA32_TSC_ADJUST test
  6 *
  7 * According to the SDM, "if an execution of WRMSR to the
  8 * IA32_TIME_STAMP_COUNTER MSR adds (or subtracts) value X from the TSC,
  9 * the logical processor also adds (or subtracts) value X from the
 10 * IA32_TSC_ADJUST MSR.
 11 *
 12 * Note that when L1 doesn't intercept writes to IA32_TSC, a
 13 * WRMSR(IA32_TSC) from L2 sets L1's TSC value, not L2's perceived TSC
 14 * value.
 15 *
 16 * This test verifies that this unusual case is handled correctly.
 17 */
 18
 19#include "test_util.h"
 20#include "kvm_util.h"
 21#include "processor.h"
 22#include "vmx.h"
 23#include "svm_util.h"
 24
 25#include <string.h>
 26#include <sys/ioctl.h>
 27
 28#include "kselftest.h"
 29
 30#ifndef MSR_IA32_TSC_ADJUST
 31#define MSR_IA32_TSC_ADJUST 0x3b
 32#endif
 33
 34#define TSC_ADJUST_VALUE (1ll << 32)
 35#define TSC_OFFSET_VALUE -(1ll << 48)
 36
 37#define L2_GUEST_STACK_SIZE 64
 38
 39enum {
 40	PORT_ABORT = 0x1000,
 41	PORT_REPORT,
 42	PORT_DONE,
 43};
 44
 45enum {
 46	VMXON_PAGE = 0,
 47	VMCS_PAGE,
 48	MSR_BITMAP_PAGE,
 49
 50	NUM_VMX_PAGES,
 51};
 52
 53/* The virtual machine object. */
 54static struct kvm_vm *vm;
 55
 56static void check_ia32_tsc_adjust(int64_t max)
 57{
 58	int64_t adjust;
 59
 60	adjust = rdmsr(MSR_IA32_TSC_ADJUST);
 61	GUEST_SYNC(adjust);
 62	GUEST_ASSERT(adjust <= max);
 63}
 64
 65static void l2_guest_code(void)
 66{
 67	uint64_t l1_tsc = rdtsc() - TSC_OFFSET_VALUE;
 68
 69	wrmsr(MSR_IA32_TSC, l1_tsc - TSC_ADJUST_VALUE);
 70	check_ia32_tsc_adjust(-2 * TSC_ADJUST_VALUE);
 71
 72	/* Exit to L1 */
 73	__asm__ __volatile__("vmcall");
 74}
 75
 76static void l1_guest_code(void *data)
 77{
 78	unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
 79
 80	/* Set TSC from L1 and make sure TSC_ADJUST is updated correctly */
 81	GUEST_ASSERT(rdtsc() < TSC_ADJUST_VALUE);
 82	wrmsr(MSR_IA32_TSC, rdtsc() - TSC_ADJUST_VALUE);
 83	check_ia32_tsc_adjust(-1 * TSC_ADJUST_VALUE);
 84
 85	/*
 86	 * Run L2 with TSC_OFFSET. L2 will write to TSC, and L1 is not
 87	 * intercepting the write so it should update L1's TSC_ADJUST.
 88	 */
 89	if (this_cpu_has(X86_FEATURE_VMX)) {
 90		struct vmx_pages *vmx_pages = data;
 91		uint32_t control;
 92
 93		GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
 94		GUEST_ASSERT(load_vmcs(vmx_pages));
 95
 96		prepare_vmcs(vmx_pages, l2_guest_code,
 97			     &l2_guest_stack[L2_GUEST_STACK_SIZE]);
 98		control = vmreadz(CPU_BASED_VM_EXEC_CONTROL);
 99		control |= CPU_BASED_USE_MSR_BITMAPS | CPU_BASED_USE_TSC_OFFSETTING;
100		vmwrite(CPU_BASED_VM_EXEC_CONTROL, control);
101		vmwrite(TSC_OFFSET, TSC_OFFSET_VALUE);
102
103		GUEST_ASSERT(!vmlaunch());
104		GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
105	} else {
106		struct svm_test_data *svm = data;
107
108		generic_svm_setup(svm, l2_guest_code,
109				  &l2_guest_stack[L2_GUEST_STACK_SIZE]);
110
111		svm->vmcb->control.tsc_offset = TSC_OFFSET_VALUE;
112		run_guest(svm->vmcb, svm->vmcb_gpa);
113		GUEST_ASSERT(svm->vmcb->control.exit_code == SVM_EXIT_VMMCALL);
114	}
115
116	check_ia32_tsc_adjust(-2 * TSC_ADJUST_VALUE);
117	GUEST_DONE();
118}
119
120static void report(int64_t val)
121{
122	pr_info("IA32_TSC_ADJUST is %ld (%lld * TSC_ADJUST_VALUE + %lld).\n",
123		val, val / TSC_ADJUST_VALUE, val % TSC_ADJUST_VALUE);
124}
125
126int main(int argc, char *argv[])
127{
128	vm_vaddr_t nested_gva;
129	struct kvm_vcpu *vcpu;
130
131	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) ||
132		     kvm_cpu_has(X86_FEATURE_SVM));
133
134	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
135	if (kvm_cpu_has(X86_FEATURE_VMX))
136		vcpu_alloc_vmx(vm, &nested_gva);
137	else
138		vcpu_alloc_svm(vm, &nested_gva);
139
140	vcpu_args_set(vcpu, 1, nested_gva);
141
142	for (;;) {
143		struct ucall uc;
144
145		vcpu_run(vcpu);
146		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
147
148		switch (get_ucall(vcpu, &uc)) {
149		case UCALL_ABORT:
150			REPORT_GUEST_ASSERT(uc);
151			/* NOT REACHED */
152		case UCALL_SYNC:
153			report(uc.args[1]);
154			break;
155		case UCALL_DONE:
156			goto done;
157		default:
158			TEST_FAIL("Unknown ucall %lu", uc.cmd);
159		}
160	}
161
162done:
163	kvm_vm_free(vm);
164	return 0;
165}
~/ Read-only file preview.