Spaces:
Runtime error
Runtime error
| static struct spinlock g_synic_alloc_lock; | |
| 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(); | |
| abort(); | |
| } | |
| 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(); | |
| abort(); | |
| } | |
| /* Now process timer expiration message */ | |
| if (payload->timer_index >= ARRAY_SIZE(svcpu->timer)) { | |
| report("invalid Hyper-V SynIC timer index", false); | |
| report_summary(); | |
| abort(); | |
| } | |
| 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 stimer_test_prepare(void *ctx) | |
| { | |
| write_cr3((ulong)ctx); | |
| synic_enable(); | |
| synic_sint_create(SINT1_NUM, SINT1_VEC, false); | |
| synic_sint_create(SINT2_NUM, SINT2_VEC, true); | |
| } | |
| 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, SINT1_NUM); | |
| stimer_start(timer2, false, true, ONE_MS_IN_100NS, SINT2_NUM); | |
| 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, SINT1_NUM); | |
| 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, SINT1_NUM); | |
| 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, SINT1_NUM); | |
| 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_one_shot_busy(int vcpu, struct stimer *timer) | |
| { | |
| struct hv_message_page *msg_page = g_synic_vcpu[vcpu].msg_page; | |
| struct hv_message *msg = &msg_page->sint_message[timer->sint]; | |
| msg->header.message_type = HVMSG_TIMER_EXPIRED; | |
| wmb(); | |
| stimer_start(timer, false, false, ONE_MS_IN_100NS, SINT1_NUM); | |
| do | |
| rmb(); | |
| while (!msg->header.message_flags.msg_pending); | |
| report("no timer fired while msg slot busy: vcpu %d", | |
| !atomic_read(&timer->fire_count), vcpu); | |
| msg->header.message_type = HVMSG_NONE; | |
| wmb(); | |
| wrmsr(HV_X64_MSR_EOM, 0); | |
| while (atomic_read(&timer->fire_count) < 1) { | |
| pause(); | |
| } | |
| report("timer resumed when msg slot released: 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); | |
| stimer_test_one_shot_busy(vcpu, timer1); | |
| irq_disable(); | |
| } | |
| static void stimer_test_cleanup(void *ctx) | |
| { | |
| stimers_shutdown(); | |
| synic_sint_destroy(SINT1_NUM); | |
| synic_sint_destroy(SINT2_NUM); | |
| synic_disable(); | |
| } | |
| 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(SINT1_VEC, stimer_isr); | |
| handle_irq(SINT2_VEC, stimer_isr_auto_eoi); | |
| 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 (!synic_supported()) { | |
| report("Hyper-V SynIC is not supported", true); | |
| goto done; | |
| } | |
| 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; | |
| } | |
| stimer_test_all(); | |
| done: | |
| return report_summary(); | |
| } | |