Signed-off-by: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx> --- x86/Makefile.common | 3 + x86/hyperv.h | 7 ++ x86/hyperv_stimer_direct.c | 250 +++++++++++++++++++++++++++++++++++++ x86/unittests.cfg | 6 + 4 files changed, 266 insertions(+) create mode 100644 x86/hyperv_stimer_direct.c diff --git a/x86/Makefile.common b/x86/Makefile.common index e612dbe..115189a 100644 --- a/x86/Makefile.common +++ b/x86/Makefile.common @@ -57,6 +57,7 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \ $(TEST_DIR)/tsc_adjust.flat $(TEST_DIR)/asyncpf.flat \ $(TEST_DIR)/init.flat $(TEST_DIR)/smap.flat \ $(TEST_DIR)/hyperv_synic.flat $(TEST_DIR)/hyperv_stimer.flat \ + $(TEST_DIR)/hyperv_stimer_direct.flat \ $(TEST_DIR)/hyperv_connections.flat \ $(TEST_DIR)/umip.flat @@ -81,6 +82,8 @@ $(TEST_DIR)/hyperv_synic.elf: $(TEST_DIR)/hyperv.o $(TEST_DIR)/hyperv_stimer.elf: $(TEST_DIR)/hyperv.o +$(TEST_DIR)/hyperv_stimer_direct.elf: $(TEST_DIR)/hyperv.o + $(TEST_DIR)/hyperv_connections.elf: $(TEST_DIR)/hyperv.o arch_clean: diff --git a/x86/hyperv.h b/x86/hyperv.h index 9a83da4..718744c 100644 --- a/x86/hyperv.h +++ b/x86/hyperv.h @@ -10,6 +10,8 @@ #define HV_X64_MSR_SYNIC_AVAILABLE (1 << 2) #define HV_X64_MSR_SYNTIMER_AVAILABLE (1 << 3) +#define HV_STIMER_DIRECT_MODE_AVAILABLE (1 << 19) + #define HV_X64_MSR_GUEST_OS_ID 0x40000000 #define HV_X64_MSR_HYPERCALL 0x40000001 @@ -205,6 +207,11 @@ static inline bool stimer_supported(void) return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_SYNIC_AVAILABLE; } +static inline bool stimer_direct_supported(void) +{ + return cpuid(HYPERV_CPUID_FEATURES).d & HV_STIMER_DIRECT_MODE_AVAILABLE; +} + static inline bool hv_time_ref_counter_supported(void) { return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE; diff --git a/x86/hyperv_stimer_direct.c b/x86/hyperv_stimer_direct.c new file mode 100644 index 0000000..e93f633 --- /dev/null +++ b/x86/hyperv_stimer_direct.c @@ -0,0 +1,250 @@ +#include "libcflat.h" +#include "processor.h" +#include "msr.h" +#include "isr.h" +#include "vm.h" +#include "apic.h" +#include "desc.h" +#include "smp.h" +#include "atomic.h" +#include "hyperv.h" +#include "asm/barrier.h" +#include "alloc_page.h" + +#define MAX_CPUS 4 + +#define INT1_VEC 0xF1 +#define INT2_VEC 0xF2 + +#define ONE_MS_IN_100NS 10000 + +struct stimer { + int sint; + int index; + atomic_t fire_count; +}; + +struct svcpu { + int vcpu; + struct stimer timer[HV_SYNIC_STIMER_COUNT]; +}; + +static struct svcpu g_synic_vcpu[MAX_CPUS]; + +static void stimer_init(struct stimer *timer, int index) +{ + memset(timer, 0, sizeof(*timer)); + timer->index = index; +} + +static void synic_enable(void) +{ + int vcpu = smp_id(), i; + struct svcpu *svcpu = &g_synic_vcpu[vcpu]; + + memset(svcpu, 0, sizeof(*svcpu)); + svcpu->vcpu = vcpu; + for (i = 0; i < ARRAY_SIZE(svcpu->timer); i++) { + stimer_init(&svcpu->timer[i], i); + } +} + +static void stimer_shutdown(struct stimer *timer) +{ + wrmsr(HV_X64_MSR_STIMER0_CONFIG + 2*timer->index, 0); +} + +static void process_stimer_expired(struct stimer *timer) +{ + atomic_inc(&timer->fire_count); +} + +static void __stimer_isr_direct(int vcpu, int timer_index) +{ + struct svcpu *svcpu = &g_synic_vcpu[vcpu]; + struct stimer *timer = &svcpu->timer[timer_index]; + + process_stimer_expired(timer); +} + +static void stimer_isr_direct1(isr_regs_t *regs) +{ + int vcpu = smp_id(); + + __stimer_isr_direct(vcpu, 0); + + eoi(); +} + +static void stimer_isr_direct2(isr_regs_t *regs) +{ + int vcpu = smp_id(); + + __stimer_isr_direct(vcpu, 1); + + eoi(); +} + +static void stimer_start(struct stimer *timer, + bool auto_enable, bool periodic, + u64 tick_100ns, int apic_vector) +{ + u64 count; + union hv_stimer_config config = {.as_uint64 = 0}; + + timer->sint = 0; + atomic_set(&timer->fire_count, 0); + + config.periodic = periodic; + config.enable = 1; + config.auto_enable = auto_enable; + config.direct_mode = 1; + config.sintx = 0; + config.apic_vector = apic_vector; + + if (periodic) { + count = tick_100ns; + } else { + count = rdmsr(HV_X64_MSR_TIME_REF_COUNT) + tick_100ns; + } + + if (!auto_enable) { + wrmsr(HV_X64_MSR_STIMER0_COUNT + timer->index*2, count); + wrmsr(HV_X64_MSR_STIMER0_CONFIG + timer->index*2, config.as_uint64); + } else { + wrmsr(HV_X64_MSR_STIMER0_CONFIG + timer->index*2, config.as_uint64); + wrmsr(HV_X64_MSR_STIMER0_COUNT + timer->index*2, count); + } +} + +static void stimers_shutdown(void) +{ + int vcpu = smp_id(), i; + struct svcpu *svcpu = &g_synic_vcpu[vcpu]; + + for (i = 0; i < ARRAY_SIZE(svcpu->timer); i++) { + stimer_shutdown(&svcpu->timer[i]); + } +} + +static void stimer_test_prepare(void *ctx) +{ + write_cr3((ulong)ctx); + synic_enable(); +} + +static void stimer_test_periodic(int vcpu, struct stimer *timer1, + struct stimer *timer2) +{ + /* Check periodic timers */ + stimer_start(timer1, false, true, ONE_MS_IN_100NS, INT1_VEC); + stimer_start(timer2, false, true, ONE_MS_IN_100NS, INT2_VEC); + while ((atomic_read(&timer1->fire_count) < 1000) || + (atomic_read(&timer2->fire_count) < 1000)) { + pause(); + } + report("Hyper-V SynIC periodic timers test vcpu %d", true, vcpu); + stimer_shutdown(timer1); + stimer_shutdown(timer2); +} + +static void stimer_test_one_shot(int vcpu, struct stimer *timer) +{ + /* Check one-shot timer */ + stimer_start(timer, false, false, ONE_MS_IN_100NS, INT1_VEC); + while (atomic_read(&timer->fire_count) < 1) { + pause(); + } + report("Hyper-V SynIC one-shot test vcpu %d", true, vcpu); + stimer_shutdown(timer); +} + +static void stimer_test_auto_enable_one_shot(int vcpu, struct stimer *timer) +{ + /* Check auto-enable one-shot timer */ + stimer_start(timer, true, false, ONE_MS_IN_100NS, INT2_VEC); + while (atomic_read(&timer->fire_count) < 1) { + pause(); + } + report("Hyper-V SynIC auto-enable one-shot timer test vcpu %d", true, vcpu); + stimer_shutdown(timer); +} + +static void stimer_test_auto_enable_periodic(int vcpu, struct stimer *timer) +{ + /* Check auto-enable periodic timer */ + stimer_start(timer, true, true, ONE_MS_IN_100NS, INT1_VEC); + while (atomic_read(&timer->fire_count) < 1000) { + pause(); + } + report("Hyper-V SynIC auto-enable periodic timer test vcpu %d", true, vcpu); + stimer_shutdown(timer); +} + +static void stimer_test(void *ctx) +{ + int vcpu = smp_id(); + struct svcpu *svcpu = &g_synic_vcpu[vcpu]; + struct stimer *timer1, *timer2; + + irq_enable(); + + timer1 = &svcpu->timer[0]; + timer2 = &svcpu->timer[1]; + + stimer_test_periodic(vcpu, timer1, timer2); + stimer_test_one_shot(vcpu, timer1); + stimer_test_auto_enable_one_shot(vcpu, timer2); + stimer_test_auto_enable_periodic(vcpu, timer1); + + irq_disable(); +} + +static void stimer_test_cleanup(void *ctx) +{ + stimers_shutdown(); +} + +static void stimer_test_all(void) +{ + int ncpus; + + setup_vm(); + smp_init(); + enable_apic(); + + ncpus = cpu_count(); + if (ncpus > MAX_CPUS) + report_abort("number cpus exceeds %d", MAX_CPUS); + printf("cpus = %d\n", ncpus); + + handle_irq(INT1_VEC, stimer_isr_direct1); + handle_irq(INT2_VEC, stimer_isr_direct2); + + on_cpus(stimer_test_prepare, (void *)read_cr3()); + on_cpus(stimer_test, NULL); + on_cpus(stimer_test_cleanup, NULL); +} + +int main(int ac, char **av) +{ + + if (!stimer_supported()) { + report("Hyper-V SynIC timers are not supported", true); + goto done; + } + + if (!hv_time_ref_counter_supported()) { + report("Hyper-V time reference counter is not supported", true); + goto done; + } + + if (!stimer_direct_supported()) { + report("Hyper-V direct mode for synthetic timers is not supported", true); + goto done; + } + + stimer_test_all(); +done: + return report_summary(); +} diff --git a/x86/unittests.cfg b/x86/unittests.cfg index c744a6d..36c0657 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -635,6 +635,12 @@ smp = 2 extra_params = -cpu kvm64,hv_vpindex,hv_time,hv_synic,hv_stimer -device hyperv-testdev groups = hyperv +[hyperv_stimer_direct] +file = hyperv_stimer_direct.flat +smp = 2 +extra_params = -cpu kvm64,hv_vpindex,hv_time,hv_synic,hv_stimer,hv_stimer_direct -device hyperv-testdev +groups = hyperv + [hyperv_clock] file = hyperv_clock.flat smp = 2 -- 2.17.2