// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2024 Intel Corporation * * Verify KVM correctly emulates the APIC bus frequency when the VMM configures * the frequency via KVM_CAP_X86_APIC_BUS_CYCLES_NS. Start the APIC timer by * programming TMICT (timer initial count) to the largest value possible (so * that the timer will not expire during the test). Then, after an arbitrary * amount of time has elapsed, verify TMCCT (timer current count) is within 1% * of the expected value based on the time elapsed, the APIC bus frequency, and * the programmed TDCR (timer divide configuration register). */ #include "apic.h" #include "test_util.h" /* * Possible TDCR values with matching divide count. Used to modify APIC * timer frequency. */ static const struct { const uint32_t tdcr; const uint32_t divide_count; } tdcrs[] = { {0x0, 2}, {0x1, 4}, {0x2, 8}, {0x3, 16}, {0x8, 32}, {0x9, 64}, {0xa, 128}, {0xb, 1}, }; static bool is_x2apic; static void apic_enable(void) { if (is_x2apic) x2apic_enable(); else xapic_enable(); } static uint32_t apic_read_reg(unsigned int reg) { return is_x2apic ? x2apic_read_reg(reg) : xapic_read_reg(reg); } static void apic_write_reg(unsigned int reg, uint32_t val) { if (is_x2apic) x2apic_write_reg(reg, val); else xapic_write_reg(reg, val); } static void apic_guest_code(uint64_t apic_hz, uint64_t delay_ms) { uint64_t tsc_hz = guest_tsc_khz * 1000; const uint32_t tmict = ~0u; uint64_t tsc0, tsc1, freq; uint32_t tmcct; int i; apic_enable(); /* * Setup one-shot timer. The vector does not matter because the * interrupt should not fire. */ apic_write_reg(APIC_LVTT, APIC_LVT_TIMER_ONESHOT | APIC_LVT_MASKED); for (i = 0; i < ARRAY_SIZE(tdcrs); i++) { apic_write_reg(APIC_TDCR, tdcrs[i].tdcr); apic_write_reg(APIC_TMICT, tmict); tsc0 = rdtsc(); udelay(delay_ms * 1000); tmcct = apic_read_reg(APIC_TMCCT); tsc1 = rdtsc(); /* * Stop the timer _after_ reading the current, final count, as * writing the initial counter also modifies the current count. */ apic_write_reg(APIC_TMICT, 0); freq = (tmict - tmcct) * tdcrs[i].divide_count * tsc_hz / (tsc1 - tsc0); /* Check if measured frequency is within 5% of configured frequency. */ __GUEST_ASSERT(freq < apic_hz * 105 / 100 && freq > apic_hz * 95 / 100, "Frequency = %lu (wanted %lu - %lu), bus = %lu, div = %u, tsc = %lu", freq, apic_hz * 95 / 100, apic_hz * 105 / 100, apic_hz, tdcrs[i].divide_count, tsc_hz); } GUEST_DONE(); } static void test_apic_bus_clock(struct kvm_vcpu *vcpu) { bool done = false; struct ucall uc; while (!done) { vcpu_run(vcpu); TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); switch (get_ucall(vcpu, &uc)) { case UCALL_DONE: done = true; break; case UCALL_ABORT: REPORT_GUEST_ASSERT(uc); break; default: TEST_FAIL("Unknown ucall %lu", uc.cmd); break; } } } static void run_apic_bus_clock_test(uint64_t apic_hz, uint64_t delay_ms, bool x2apic) { struct kvm_vcpu *vcpu; struct kvm_vm *vm; int ret; is_x2apic = x2apic; vm = vm_create(1); sync_global_to_guest(vm, is_x2apic); vm_enable_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS, NSEC_PER_SEC / apic_hz); vcpu = vm_vcpu_add(vm, 0, apic_guest_code); vcpu_args_set(vcpu, 2, apic_hz, delay_ms); ret = __vm_enable_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS, NSEC_PER_SEC / apic_hz); TEST_ASSERT(ret < 0 && errno == EINVAL, "Setting of APIC bus frequency after vCPU is created should fail."); if (!is_x2apic) virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA); test_apic_bus_clock(vcpu); kvm_vm_free(vm); } static void help(char *name) { puts(""); printf("usage: %s [-h] [-d delay] [-f APIC bus freq]\n", name); puts(""); printf("-d: Delay (in msec) guest uses to measure APIC bus frequency.\n"); printf("-f: The APIC bus frequency (in MHz) to be configured for the guest.\n"); puts(""); } int main(int argc, char *argv[]) { /* * Arbitrarilty default to 25MHz for the APIC bus frequency, which is * different enough from the default 1GHz to be interesting. */ uint64_t apic_hz = 25 * 1000 * 1000; uint64_t delay_ms = 100; int opt; TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_APIC_BUS_CYCLES_NS)); while ((opt = getopt(argc, argv, "d:f:h")) != -1) { switch (opt) { case 'f': apic_hz = atoi_positive("APIC bus frequency", optarg) * 1000 * 1000; break; case 'd': delay_ms = atoi_positive("Delay in milliseconds", optarg); break; case 'h': default: help(argv[0]); exit(KSFT_SKIP); } } run_apic_bus_clock_test(apic_hz, delay_ms, false); run_apic_bus_clock_test(apic_hz, delay_ms, true); }