root/tools/testing/selftests/kvm/x86/apic_bus_clock_test.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);
}