root/tools/testing/selftests/kvm/riscv/arch_timer.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * arch_timer.c - Tests the riscv64 sstc timer IRQ functionality
 *
 * The test validates the sstc timer IRQs using vstimecmp registers.
 * It's ported from the aarch64 arch_timer test.
 *
 * Copyright (c) 2024, Intel Corporation.
 */
#include "arch_timer.h"
#include "kvm_util.h"
#include "processor.h"
#include "timer_test.h"
#include "ucall_common.h"

static int timer_irq = IRQ_S_TIMER;

static void guest_irq_handler(struct pt_regs *regs)
{
        uint64_t xcnt, xcnt_diff_us, cmp;
        unsigned int intid = regs->cause & ~CAUSE_IRQ_FLAG;
        uint32_t cpu = guest_get_vcpuid();
        struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];

        timer_irq_disable();

        xcnt = timer_get_cycles();
        cmp = timer_get_cmp();
        xcnt_diff_us = cycles_to_usec(xcnt - shared_data->xcnt);

        /* Make sure we are dealing with the correct timer IRQ */
        GUEST_ASSERT_EQ(intid, timer_irq);

        __GUEST_ASSERT(xcnt >= cmp,
                        "xcnt = 0x%"PRIx64", cmp = 0x%"PRIx64", xcnt_diff_us = 0x%" PRIx64,
                        xcnt, cmp, xcnt_diff_us);

        WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter + 1);
}

static void guest_run(struct test_vcpu_shared_data *shared_data)
{
        uint32_t irq_iter, config_iter;

        shared_data->nr_iter = 0;
        shared_data->guest_stage = 0;

        for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
                /* Setup the next interrupt */
                timer_set_next_cmp_ms(test_args.timer_period_ms);
                shared_data->xcnt = timer_get_cycles();
                timer_irq_enable();

                /* Setup a timeout for the interrupt to arrive */
                udelay(msecs_to_usecs(test_args.timer_period_ms) +
                        test_args.timer_err_margin_us);

                irq_iter = READ_ONCE(shared_data->nr_iter);
                __GUEST_ASSERT(config_iter + 1 == irq_iter,
                                "config_iter + 1 = 0x%x, irq_iter = 0x%x.\n"
                                "  Guest timer interrupt was not triggered within the specified\n"
                                "  interval, try to increase the error margin by [-e] option.\n",
                                config_iter + 1, irq_iter);
        }
}

static void guest_code(void)
{
        uint32_t cpu = guest_get_vcpuid();
        struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];

        timer_irq_disable();
        local_irq_enable();

        guest_run(shared_data);

        GUEST_DONE();
}

struct kvm_vm *test_vm_create(void)
{
        struct kvm_vm *vm;
        int nr_vcpus = test_args.nr_vcpus;

        vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus);
        __TEST_REQUIRE(__vcpu_has_isa_ext(vcpus[0], KVM_RISCV_ISA_EXT_SSTC),
                                   "SSTC not available, skipping test\n");

        vm_init_vector_tables(vm);
        vm_install_interrupt_handler(vm, guest_irq_handler);

        for (int i = 0; i < nr_vcpus; i++)
                vcpu_init_vector_tables(vcpus[i]);

        /* Initialize guest timer frequency. */
        timer_freq = vcpu_get_reg(vcpus[0], RISCV_TIMER_REG(frequency));
        sync_global_to_guest(vm, timer_freq);
        pr_debug("timer_freq: %lu\n", timer_freq);

        /* Make all the test's cmdline args visible to the guest */
        sync_global_to_guest(vm, test_args);

        return vm;
}

void test_vm_cleanup(struct kvm_vm *vm)
{
        kvm_vm_free(vm);
}