// 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[] = {
{0 x0, 2 },
{0 x1, 4 },
{0 x2, 8 },
{0 x3, 16 },
{0 x8, 32 },
{0 x9, 64 },
{0 xa, 128 },
{0 xb, 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 = ~0 u;
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 25 MHz for the APIC bus frequency , which is
* different enough from the default 1 GHz 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 );
}
Messung V0.5 in Prozent C=97 H=92 G=94
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-10-12)
¤
*© Formatika GbR, Deutschland