mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-19 16:24:21 +00:00
Use the kernel's canonical $(ARCH) paths instead of the raw target triple for KVM selftests directories. KVM selftests are quite nearly the only place in the entire kernel that using the target triple for directories, tools/testing/selftests/drivers/s390x being the lone holdout. Using the kernel's preferred nomenclature eliminates the minor, but annoying, friction of having to translate to KVM's selftests directories, e.g. for pattern matching, opening files, running selftests, etc. Opportunsitically delete file comments that reference the full path of the file, as they are obviously prone to becoming stale, and serve no known purpose. Reviewed-by: Muhammad Usama Anjum <usama.anjum@collabora.com> Acked-by: Claudio Imbrenda <imbrenda@linux.ibm.com> Acked-by: Andrew Jones <ajones@ventanamicro.com> Link: https://lore.kernel.org/r/20241128005547.4077116-16-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
195 lines
4.6 KiB
C
195 lines
4.6 KiB
C
// 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);
|
|
}
|