The test checks Hyper-V SynIC timers functionality.
The test runs on every vCPU and performs start/stop
of periodic/one-shot timers (with period=1ms) and checks
validity of received expiration messages in appropriate
ISR's.
Signed-off-by: Andrey Smetanin <asmetanin@xxxxxxxxxxxxx>
Reviewed-by: Roman Kagan <rkagan@xxxxxxxxxxxxx>
CC: Paolo Bonzini <pbonzini@xxxxxxxxxx>
CC: Marcelo Tosatti <mtosatti@xxxxxxxxxx>
CC: Roman Kagan <rkagan@xxxxxxxxxxxxx>
CC: Denis V. Lunev <den@xxxxxxxxxx>
CC: qemu-devel@xxxxxxxxxx
---
config/config-x86-common.mak | 4 +-
x86/hyperv_stimer.c | 500 +++++++++++++++++++++++++++++++++++++++++++
x86/unittests.cfg | 5 +
3 files changed, 508 insertions(+), 1 deletion(-)
create mode 100644 x86/hyperv_stimer.c
diff --git a/config/config-x86-common.mak b/config/config-x86-common.mak
index f64874d..a75be87 100644
--- a/config/config-x86-common.mak
+++ b/config/config-x86-common.mak
@@ -37,7 +37,7 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \
$(TEST_DIR)/s3.flat $(TEST_DIR)/pmu.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_synic.flat $(TEST_DIR)/hyperv_stimer.flat \
ifdef API
tests-common += api/api-sample
@@ -115,6 +115,8 @@ $(TEST_DIR)/memory.elf: $(cstart.o) $(TEST_DIR)/memory.o
$(TEST_DIR)/hyperv_synic.elf: $(cstart.o) $(TEST_DIR)/hyperv_synic.o
+$(TEST_DIR)/hyperv_stimer.elf: $(cstart.o) $(TEST_DIR)/hyperv_stimer.o
+
arch_clean:
$(RM) $(TEST_DIR)/*.o $(TEST_DIR)/*.flat $(TEST_DIR)/*.elf \
$(TEST_DIR)/.*.d lib/x86/.*.d
diff --git a/x86/hyperv_stimer.c b/x86/hyperv_stimer.c
new file mode 100644
index 0000000..e9186ca
--- /dev/null
+++ b/x86/hyperv_stimer.c
@@ -0,0 +1,500 @@
+#include "libcflat.h"
+#include "processor.h"
+#include "msr.h"
+#include "isr.h"
+#include "vm.h"
+#include "apic.h"
+#include "desc.h"
+#include "io.h"
+#include "smp.h"
+#include "atomic.h"
+
+#define MAX_CPUS 4
+#define HYPERV_CPUID_FEATURES 0x40000003
+
+#define HV_SYNIC_CONTROL_ENABLE (1ULL << 0)
+#define HV_SYNIC_SIMP_ENABLE (1ULL << 0)
+#define HV_SYNIC_SIEFP_ENABLE (1ULL << 0)
+#define HV_SYNIC_SINT_MASKED (1ULL << 16)
+#define HV_SYNIC_SINT_AUTO_EOI (1ULL << 17)
+#define HV_SYNIC_SINT_VECTOR_MASK (0xFF)
+#define HV_SYNIC_SINT_COUNT 16
+
+#define HV_STIMER_ENABLE (1ULL << 0)
+#define HV_STIMER_PERIODIC (1ULL << 1)
+#define HV_STIMER_LAZY (1ULL << 2)
+#define HV_STIMER_AUTOENABLE (1ULL << 3)
+#define HV_STIMER_SINT(config) (__u8)(((config) >> 16) & 0x0F)
+
+#define HV_SYNIC_STIMER_COUNT (4)
+
+/* Define synthetic interrupt controller message constants. */
+#define HV_MESSAGE_SIZE (256)
+#define HV_MESSAGE_PAYLOAD_BYTE_COUNT (240)
+#define HV_MESSAGE_PAYLOAD_QWORD_COUNT (30)
+
+/* Define hypervisor message types. */
+enum hv_message_type {
+ HVMSG_NONE = 0x00000000,
+
+ /* Memory access messages. */
+ HVMSG_UNMAPPED_GPA = 0x80000000,
+ HVMSG_GPA_INTERCEPT = 0x80000001,
+
+ /* Timer notification messages. */
+ HVMSG_TIMER_EXPIRED = 0x80000010,
+
+ /* Error messages. */
+ HVMSG_INVALID_VP_REGISTER_VALUE = 0x80000020,
+ HVMSG_UNRECOVERABLE_EXCEPTION = 0x80000021,
+ HVMSG_UNSUPPORTED_FEATURE = 0x80000022,
+
+ /* Trace buffer complete messages. */
+ HVMSG_EVENTLOG_BUFFERCOMPLETE = 0x80000040,
+
+ /* Platform-specific processor intercept messages. */
+ HVMSG_X64_IOPORT_INTERCEPT = 0x80010000,
+ HVMSG_X64_MSR_INTERCEPT = 0x80010001,
+ HVMSG_X64_CPUID_INTERCEPT = 0x80010002,
+ HVMSG_X64_EXCEPTION_INTERCEPT = 0x80010003,
+ HVMSG_X64_APIC_EOI = 0x80010004,
+ HVMSG_X64_LEGACY_FP_ERROR = 0x80010005
+};
+
+/* Define synthetic interrupt controller message flags. */
+union hv_message_flags {
+ uint8_t asu8;
+ struct {
+ uint8_t msg_pending:1;
+ uint8_t reserved:7;
+ };
+};
+
+union hv_port_id {
+ uint32_t asu32;
+ struct {
+ uint32_t id:24;
+ uint32_t reserved:8;
+ } u;
+};
+
+/* Define port type. */
+enum hv_port_type {
+ HVPORT_MSG = 1,
+ HVPORT_EVENT = 2,
+ HVPORT_MONITOR = 3
+};
+
+/* Define synthetic interrupt controller message header. */
+struct hv_message_header {
+ enum hv_message_type message_type;
+ uint8_t payload_size;
+ union hv_message_flags message_flags;
+ uint8_t reserved[2];
+ union {
+ uint64_t sender;
+ union hv_port_id port;
+ };
+};
+
+/* Define timer message payload structure. */
+struct hv_timer_message_payload {
+ uint32_t timer_index;
+ uint32_t reserved;
+ uint64_t expiration_time; /* When the timer expired */
+ uint64_t delivery_time; /* When the message was delivered */
+};
+
+/* Define synthetic interrupt controller message format. */
+struct hv_message {
+ struct hv_message_header header;
+ union {
+ uint64_t payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT];
+ } u;
+};
+
+/* Define the synthetic interrupt message page layout. */
+struct hv_message_page {
+ struct hv_message sint_message[HV_SYNIC_SINT_COUNT];
+};
+
+enum {
+ HV_TEST_DEV_SINT_ROUTE_CREATE = 1,
+ HV_TEST_DEV_SINT_ROUTE_DESTROY,
+ HV_TEST_DEV_SINT_ROUTE_SET_SINT
+};
+
+static atomic_t g_cpus_comp_count;
+static int g_cpus_count;
+static struct spinlock g_synic_alloc_lock;
+
+static bool synic_supported(void)
+{
+ return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_SYNIC_AVAILABLE;
+}
+
+static bool stimer_supported(void)
+{
+ return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_SYNIC_AVAILABLE;
+}
+
+static bool hv_time_ref_counter_supported(void)
+{
+ return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE;
+}
+
+static void synic_ctl(u8 ctl, u8 vcpu_id, u8 sint)
+{
+ outl((ctl << 16)|((vcpu_id) << 8)|sint, 0x3000);
+}
+
+static void synic_sint_create(int sint, int vec, bool auto_eoi)
+{
+ int vcpu = smp_id();
+
+ wrmsr(HV_X64_MSR_SINT0 + sint,
+ (u64)vec | ((auto_eoi) ? HV_SYNIC_SINT_AUTO_EOI : 0));
+ synic_ctl(HV_TEST_DEV_SINT_ROUTE_CREATE, vcpu, sint);
+}
+
+static void synic_sint_destroy(int sint)
+{
+ int vcpu = smp_id();
+
+ wrmsr(HV_X64_MSR_SINT0 + sint, 0xFF|HV_SYNIC_SINT_MASKED);
+ synic_ctl(HV_TEST_DEV_SINT_ROUTE_DESTROY, vcpu, sint);
+}
+
+struct stimer {
+ int sint;
+ int index;
+ atomic_t fire_count;
+};
+
+struct svcpu {
+ int vcpu;
+ void *msg_page;
+ void *evt_page;
+ struct stimer timer[HV_SYNIC_STIMER_COUNT];
+};
+
+static struct svcpu g_synic_vcpu[MAX_CPUS];
+
+static void *synic_alloc_page(void)
+{
+ void *page;
+
+ spin_lock(&g_synic_alloc_lock);
+ page = alloc_page();
+ spin_unlock(&g_synic_alloc_lock);
+ return page;
+}
+
+static void synic_free_page(void *page)
+{
+ spin_lock(&g_synic_alloc_lock);
+ free_page(page);
+ spin_unlock(&g_synic_alloc_lock);
+}
+
+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;
+ svcpu->msg_page = synic_alloc_page();
+ for (i = 0; i < ARRAY_SIZE(svcpu->timer); i++) {
+ stimer_init(&svcpu->timer[i], i);
+ }
+ wrmsr(HV_X64_MSR_SIMP, (u64)virt_to_phys(svcpu->msg_page) |
+ HV_SYNIC_SIMP_ENABLE);
+ wrmsr(HV_X64_MSR_SCONTROL, HV_SYNIC_CONTROL_ENABLE);
+}
+
+static void stimer_shutdown(struct stimer *timer)
+{
+ wrmsr(HV_X64_MSR_STIMER0_CONFIG + 2*timer->index, 0);
+}
+
+static void process_stimer_expired(struct svcpu *svcpu, struct stimer *timer,
+ u64 expiration_time, u64 delivery_time)
+{
+ atomic_inc(&timer->fire_count);
+}
+
+static void process_stimer_msg(struct svcpu *svcpu,
+ struct hv_message *msg, int sint)
+{
+ struct hv_timer_message_payload *payload =
+ (struct hv_timer_message_payload *)msg->u.payload;
+ struct stimer *timer;
+
+ if (msg->header.message_type != HVMSG_TIMER_EXPIRED &&
+ msg->header.message_type != HVMSG_NONE) {
+ report("invalid Hyper-V SynIC msg type", false);
+ report_summary();
+ exit(-1);
+ return;
+ }
+
+ if (msg->header.message_type == HVMSG_NONE) {
+ return;
+ }
+
+ if (msg->header.payload_size < sizeof(*payload)) {
+ report("invalid Hyper-V SynIC msg payload size", false);
+ report_summary();
+ exit(-1);
+ return;
+ }
+
+ /* Now process timer expiration message */
+
+ if (payload->timer_index >= ARRAY_SIZE(svcpu->timer)) {
+ report("invalid Hyper-V SynIC timer index", false);
+ report_summary();
+ exit(-1);
+ return;
+ }
+ timer = &svcpu->timer[payload->timer_index];
+ process_stimer_expired(svcpu, timer, payload->expiration_time,
+ payload->delivery_time);
+
+ msg->header.message_type = HVMSG_NONE;
+ mb();
+ if (msg->header.message_flags.msg_pending) {
+ wrmsr(HV_X64_MSR_EOM, 0);
+ }
+}
+
+static void __stimer_isr(int vcpu)
+{
+ struct svcpu *svcpu = &g_synic_vcpu[vcpu];
+ struct hv_message_page *msg_page;
+ struct hv_message *msg;
+ int i;
+
+
+ msg_page = (struct hv_message_page *)svcpu->msg_page;
+ for (i = 0; i < ARRAY_SIZE(msg_page->sint_message); i++) {
+ msg = &msg_page->sint_message[i];
+ process_stimer_msg(svcpu, msg, i);
+ }
+}
+
+static void stimer_isr(isr_regs_t *regs)
+{
+ int vcpu = smp_id();
+
+ __stimer_isr(vcpu);
+ eoi();
+}
+
+static void stimer_isr_auto_eoi(isr_regs_t *regs)
+{
+ int vcpu = smp_id();
+
+ __stimer_isr(vcpu);
+}
+
+static void stimer_start(struct stimer *timer,
+ bool auto_enable, bool periodic,
+ u64 tick_100ns, int sint)
+{
+ u64 config, count;
+
+ timer->sint = sint;
+ atomic_set(&timer->fire_count, 0);
+
+ config = 0;
+ if (periodic) {
+ config |= HV_STIMER_PERIODIC;
+ }
+
+ config |= ((u8)(sint & 0xFF)) << 16;
+ config |= HV_STIMER_ENABLE;
+ if (auto_enable) {
+ config |= HV_STIMER_AUTOENABLE;
+ }
+
+ 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);
+ } else {
+ wrmsr(HV_X64_MSR_STIMER0_CONFIG + timer->index*2, config);
+ 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 synic_disable(void)
+{
+ int vcpu = smp_id();
+ struct svcpu *svcpu = &g_synic_vcpu[vcpu];
+
+ wrmsr(HV_X64_MSR_SCONTROL, 0);
+ wrmsr(HV_X64_MSR_SIMP, 0);
+ wrmsr(HV_X64_MSR_SIEFP, 0);
+ synic_free_page(svcpu->msg_page);
+}
+
+static void cpu_comp(void)
+{
+ atomic_inc(&g_cpus_comp_count);
+}
+
+static void stimer_test_prepare(void *ctx)
+{
+ write_cr3((ulong)ctx);
+ irq_enable();
+ synic_enable();
+ cpu_comp();
+}
+
+static void stimer_test(void *ctx)
+{
+ int vcpu = smp_id();
+ struct svcpu *svcpu = &g_synic_vcpu[vcpu];
+ struct stimer *timer1, *timer2;
+ int sint1, sint2;
+ u64 one_ms_in_100ns = 10000;
+ irq_enable();