File Index
Files and directories
Sign in
FILE

apic_bus_clock_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/kvm/x86/apic_bus_clock_test.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (c) 2024 Intel Corporation
  4 *
  5 * Verify KVM correctly emulates the APIC bus frequency when the VMM configures
  6 * the frequency via KVM_CAP_X86_APIC_BUS_CYCLES_NS.  Start the APIC timer by
  7 * programming TMICT (timer initial count) to the largest value possible (so
  8 * that the timer will not expire during the test).  Then, after an arbitrary
  9 * amount of time has elapsed, verify TMCCT (timer current count) is within 1%
 10 * of the expected value based on the time elapsed, the APIC bus frequency, and
 11 * the programmed TDCR (timer divide configuration register).
 12 */
 13
 14#include "apic.h"
 15#include "test_util.h"
 16
 17/*
 18 * Possible TDCR values with matching divide count. Used to modify APIC
 19 * timer frequency.
 20 */
 21static const struct {
 22	const uint32_t tdcr;
 23	const uint32_t divide_count;
 24} tdcrs[] = {
 25	{0x0, 2},
 26	{0x1, 4},
 27	{0x2, 8},
 28	{0x3, 16},
 29	{0x8, 32},
 30	{0x9, 64},
 31	{0xa, 128},
 32	{0xb, 1},
 33};
 34
 35static bool is_x2apic;
 36
 37static void apic_enable(void)
 38{
 39	if (is_x2apic)
 40		x2apic_enable();
 41	else
 42		xapic_enable();
 43}
 44
 45static uint32_t apic_read_reg(unsigned int reg)
 46{
 47	return is_x2apic ? x2apic_read_reg(reg) : xapic_read_reg(reg);
 48}
 49
 50static void apic_write_reg(unsigned int reg, uint32_t val)
 51{
 52	if (is_x2apic)
 53		x2apic_write_reg(reg, val);
 54	else
 55		xapic_write_reg(reg, val);
 56}
 57
 58static void apic_guest_code(uint64_t apic_hz, uint64_t delay_ms)
 59{
 60	uint64_t tsc_hz = guest_tsc_khz * 1000;
 61	const uint32_t tmict = ~0u;
 62	uint64_t tsc0, tsc1, freq;
 63	uint32_t tmcct;
 64	int i;
 65
 66	apic_enable();
 67
 68	/*
 69	 * Setup one-shot timer.  The vector does not matter because the
 70	 * interrupt should not fire.
 71	 */
 72	apic_write_reg(APIC_LVTT, APIC_LVT_TIMER_ONESHOT | APIC_LVT_MASKED);
 73
 74	for (i = 0; i < ARRAY_SIZE(tdcrs); i++) {
 75		apic_write_reg(APIC_TDCR, tdcrs[i].tdcr);
 76		apic_write_reg(APIC_TMICT, tmict);
 77
 78		tsc0 = rdtsc();
 79		udelay(delay_ms * 1000);
 80		tmcct = apic_read_reg(APIC_TMCCT);
 81		tsc1 = rdtsc();
 82
 83		/*
 84		 * Stop the timer _after_ reading the current, final count, as
 85		 * writing the initial counter also modifies the current count.
 86		 */
 87		apic_write_reg(APIC_TMICT, 0);
 88
 89		freq = (tmict - tmcct) * tdcrs[i].divide_count * tsc_hz / (tsc1 - tsc0);
 90		/* Check if measured frequency is within 5% of configured frequency. */
 91		__GUEST_ASSERT(freq < apic_hz * 105 / 100 && freq > apic_hz * 95 / 100,
 92			       "Frequency = %lu (wanted %lu - %lu), bus = %lu, div = %u, tsc = %lu",
 93			       freq, apic_hz * 95 / 100, apic_hz * 105 / 100,
 94			       apic_hz, tdcrs[i].divide_count, tsc_hz);
 95	}
 96
 97	GUEST_DONE();
 98}
 99
100static void test_apic_bus_clock(struct kvm_vcpu *vcpu)
101{
102	bool done = false;
103	struct ucall uc;
104
105	while (!done) {
106		vcpu_run(vcpu);
107
108		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
109
110		switch (get_ucall(vcpu, &uc)) {
111		case UCALL_DONE:
112			done = true;
113			break;
114		case UCALL_ABORT:
115			REPORT_GUEST_ASSERT(uc);
116			break;
117		default:
118			TEST_FAIL("Unknown ucall %lu", uc.cmd);
119			break;
120		}
121	}
122}
123
124static void run_apic_bus_clock_test(uint64_t apic_hz, uint64_t delay_ms,
125				    bool x2apic)
126{
127	struct kvm_vcpu *vcpu;
128	struct kvm_vm *vm;
129	int ret;
130
131	is_x2apic = x2apic;
132
133	vm = vm_create(1);
134
135	sync_global_to_guest(vm, is_x2apic);
136
137	vm_enable_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS,
138		      NSEC_PER_SEC / apic_hz);
139
140	vcpu = vm_vcpu_add(vm, 0, apic_guest_code);
141	vcpu_args_set(vcpu, 2, apic_hz, delay_ms);
142
143	ret = __vm_enable_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS,
144			      NSEC_PER_SEC / apic_hz);
145	TEST_ASSERT(ret < 0 && errno == EINVAL,
146		    "Setting of APIC bus frequency after vCPU is created should fail.");
147
148	if (!is_x2apic)
149		virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
150
151	test_apic_bus_clock(vcpu);
152	kvm_vm_free(vm);
153}
154
155static void help(char *name)
156{
157	puts("");
158	printf("usage: %s [-h] [-d delay] [-f APIC bus freq]\n", name);
159	puts("");
160	printf("-d: Delay (in msec) guest uses to measure APIC bus frequency.\n");
161	printf("-f: The APIC bus frequency (in MHz) to be configured for the guest.\n");
162	puts("");
163}
164
165int main(int argc, char *argv[])
166{
167	/*
168	 * Arbitrarilty default to 25MHz for the APIC bus frequency, which is
169	 * different enough from the default 1GHz to be interesting.
170	 */
171	uint64_t apic_hz = 25 * 1000 * 1000;
172	uint64_t delay_ms = 100;
173	int opt;
174
175	TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_APIC_BUS_CYCLES_NS));
176
177	while ((opt = getopt(argc, argv, "d:f:h")) != -1) {
178		switch (opt) {
179		case 'f':
180			apic_hz = atoi_positive("APIC bus frequency", optarg) * 1000 * 1000;
181			break;
182		case 'd':
183			delay_ms = atoi_positive("Delay in milliseconds", optarg);
184			break;
185		case 'h':
186		default:
187			help(argv[0]);
188			exit(KSFT_SKIP);
189		}
190	}
191
192	run_apic_bus_clock_test(apic_hz, delay_ms, false);
193	run_apic_bus_clock_test(apic_hz, delay_ms, true);
194}
~/ Read-only file preview.