blob: 0b9ec7a5b1b4236540d54120267bdebc20257a63 [file] [log] [blame]
#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 SINT1_VEC 0xF1
#define SINT2_VEC 0xF2
#define SINT1_NUM 2
#define SINT2_NUM 3
#define ONE_MS_IN_100NS 10000
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_fail("invalid Hyper-V SynIC msg type");
report_summary();
abort();
}
if (msg->header.message_type == HVMSG_NONE) {
return;
}
if (msg->header.payload_size < sizeof(*payload)) {
report_fail("invalid Hyper-V SynIC msg payload size");
report_summary();
abort();
}
/* Now process timer expiration message */
if (payload->timer_index >= ARRAY_SIZE(svcpu->timer)) {
report_fail("invalid Hyper-V SynIC timer index");
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)
{
u64 count;
union hv_stimer_config config = {.as_uint64 = 0};
atomic_set(&timer->fire_count, 0);
config.periodic = periodic;
config.enable = 1;
config.auto_enable = auto_enable;
config.sintx = timer->sint;
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 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)
{
int vcpu = smp_id();
struct svcpu *svcpu = &g_synic_vcpu[vcpu];
struct stimer *timer1, *timer2;
write_cr3((ulong)ctx);
synic_enable();
synic_sint_create(SINT1_NUM, SINT1_VEC, false);
synic_sint_create(SINT2_NUM, SINT2_VEC, true);
timer1 = &svcpu->timer[0];
timer2 = &svcpu->timer[1];
timer1->sint = SINT1_NUM;
timer2->sint = SINT2_NUM;
}
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);
stimer_start(timer2, false, true, ONE_MS_IN_100NS);
while ((atomic_read(&timer1->fire_count) < 1000) ||
(atomic_read(&timer2->fire_count) < 1000)) {
pause();
}
report_pass("Hyper-V SynIC periodic timers test vcpu %d", 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);
while (atomic_read(&timer->fire_count) < 1) {
pause();
}
report_pass("Hyper-V SynIC one-shot test vcpu %d", 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);
while (atomic_read(&timer->fire_count) < 1) {
pause();
}
report_pass("Hyper-V SynIC auto-enable one-shot timer test vcpu %d", 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);
while (atomic_read(&timer->fire_count) < 1000) {
pause();
}
report_pass("Hyper-V SynIC auto-enable periodic timer test vcpu %d", 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);
do
rmb();
while (!msg->header.message_flags.msg_pending);
report(!atomic_read(&timer->fire_count),
"no timer fired while msg slot busy: vcpu %d", vcpu);
msg->header.message_type = HVMSG_NONE;
wmb();
wrmsr(HV_X64_MSR_EOM, 0);
while (atomic_read(&timer->fire_count) < 1) {
pause();
}
report_pass("timer resumed when msg slot released: vcpu %d", 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;
sti();
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);
cli();
}
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();
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 (!hv_synic_supported()) {
report_skip("Hyper-V SynIC is not supported");
goto done;
}
if (!hv_stimer_supported()) {
report_skip("Hyper-V SynIC timers are not supported");
goto done;
}
if (!hv_time_ref_counter_supported()) {
report_skip("Hyper-V time reference counter is not supported");
goto done;
}
stimer_test_all();
done:
return report_summary();
}