337 lines
8.1 KiB
C
337 lines
8.1 KiB
C
#include "libcflat.h"
|
|
#include "vm.h"
|
|
#include "smp.h"
|
|
#include "isr.h"
|
|
#include "atomic.h"
|
|
#include "hyperv.h"
|
|
#include "bitops.h"
|
|
|
|
#define MAX_CPUS 64
|
|
|
|
#define MSG_VEC 0xb0
|
|
#define EVT_VEC 0xb1
|
|
#define MSG_SINT 0x8
|
|
#define EVT_SINT 0x9
|
|
#define MSG_CONN_BASE 0x10
|
|
#define EVT_CONN_BASE 0x20
|
|
#define MSG_TYPE 0x12345678
|
|
|
|
#define WAIT_CYCLES 10000000
|
|
|
|
static atomic_t ncpus_done;
|
|
|
|
struct hv_vcpu {
|
|
struct hv_message_page *msg_page;
|
|
struct hv_event_flags_page *evt_page;
|
|
struct hv_input_post_message *post_msg;
|
|
u8 msg_conn;
|
|
u8 evt_conn;
|
|
u64 hvcall_status;
|
|
atomic_t sint_received;
|
|
};
|
|
|
|
static struct hv_vcpu hv_vcpus[MAX_CPUS];
|
|
|
|
static void sint_isr(isr_regs_t *regs)
|
|
{
|
|
atomic_inc(&hv_vcpus[smp_id()].sint_received);
|
|
}
|
|
|
|
static void *hypercall_page;
|
|
|
|
static void setup_hypercall(void)
|
|
{
|
|
u64 guestid = (0x8f00ull << 48);
|
|
|
|
hypercall_page = alloc_page();
|
|
if (!hypercall_page)
|
|
report_abort("failed to allocate hypercall page");
|
|
memset(hypercall_page, 0, PAGE_SIZE);
|
|
|
|
wrmsr(HV_X64_MSR_GUEST_OS_ID, guestid);
|
|
|
|
wrmsr(HV_X64_MSR_HYPERCALL,
|
|
(u64)virt_to_phys(hypercall_page) | HV_X64_MSR_HYPERCALL_ENABLE);
|
|
}
|
|
|
|
static void teardown_hypercall(void)
|
|
{
|
|
wrmsr(HV_X64_MSR_HYPERCALL, 0);
|
|
wrmsr(HV_X64_MSR_GUEST_OS_ID, 0);
|
|
free_page(hypercall_page);
|
|
}
|
|
|
|
static u64 do_hypercall(u16 code, u64 arg, bool fast)
|
|
{
|
|
u64 ret;
|
|
u64 ctl = code;
|
|
if (fast)
|
|
ctl |= HV_HYPERCALL_FAST;
|
|
|
|
asm volatile ("call *%[hcall_page]"
|
|
#ifdef __x86_64__
|
|
"\n mov $0,%%r8"
|
|
: "=a"(ret)
|
|
: "c"(ctl), "d"(arg),
|
|
#else
|
|
: "=A"(ret)
|
|
: "A"(ctl),
|
|
"b" ((u32)(arg >> 32)), "c" ((u32)arg),
|
|
"D"(0), "S"(0),
|
|
#endif
|
|
[hcall_page] "m" (hypercall_page)
|
|
#ifdef __x86_64__
|
|
: "r8"
|
|
#endif
|
|
);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void setup_cpu(void *ctx)
|
|
{
|
|
int vcpu;
|
|
struct hv_vcpu *hv;
|
|
|
|
write_cr3((ulong)ctx);
|
|
irq_enable();
|
|
|
|
vcpu = smp_id();
|
|
hv = &hv_vcpus[vcpu];
|
|
|
|
hv->msg_page = alloc_page();
|
|
hv->evt_page = alloc_page();
|
|
hv->post_msg = alloc_page();
|
|
if (!hv->msg_page || !hv->evt_page || !hv->post_msg)
|
|
report_abort("failed to allocate synic pages for vcpu");
|
|
memset(hv->msg_page, 0, sizeof(*hv->msg_page));
|
|
memset(hv->evt_page, 0, sizeof(*hv->evt_page));
|
|
memset(hv->post_msg, 0, sizeof(*hv->post_msg));
|
|
hv->msg_conn = MSG_CONN_BASE + vcpu;
|
|
hv->evt_conn = EVT_CONN_BASE + vcpu;
|
|
|
|
wrmsr(HV_X64_MSR_SIMP,
|
|
(u64)virt_to_phys(hv->msg_page) | HV_SYNIC_SIMP_ENABLE);
|
|
wrmsr(HV_X64_MSR_SIEFP,
|
|
(u64)virt_to_phys(hv->evt_page) | HV_SYNIC_SIEFP_ENABLE);
|
|
wrmsr(HV_X64_MSR_SCONTROL, HV_SYNIC_CONTROL_ENABLE);
|
|
|
|
msg_conn_create(MSG_SINT, MSG_VEC, hv->msg_conn);
|
|
evt_conn_create(EVT_SINT, EVT_VEC, hv->evt_conn);
|
|
|
|
hv->post_msg->connectionid = hv->msg_conn;
|
|
hv->post_msg->message_type = MSG_TYPE;
|
|
hv->post_msg->payload_size = 8;
|
|
hv->post_msg->payload[0] = (u64)vcpu << 16;
|
|
}
|
|
|
|
static void teardown_cpu(void *ctx)
|
|
{
|
|
int vcpu = smp_id();
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
|
|
evt_conn_destroy(EVT_SINT, hv->evt_conn);
|
|
msg_conn_destroy(MSG_SINT, hv->msg_conn);
|
|
|
|
wrmsr(HV_X64_MSR_SCONTROL, 0);
|
|
wrmsr(HV_X64_MSR_SIEFP, 0);
|
|
wrmsr(HV_X64_MSR_SIMP, 0);
|
|
|
|
free_page(hv->post_msg);
|
|
free_page(hv->evt_page);
|
|
free_page(hv->msg_page);
|
|
}
|
|
|
|
static void do_msg(void *ctx)
|
|
{
|
|
int vcpu = (ulong)ctx;
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
struct hv_input_post_message *msg = hv->post_msg;
|
|
|
|
msg->payload[0]++;
|
|
atomic_set(&hv->sint_received, 0);
|
|
hv->hvcall_status = do_hypercall(HVCALL_POST_MESSAGE,
|
|
virt_to_phys(msg), 0);
|
|
atomic_inc(&ncpus_done);
|
|
}
|
|
|
|
static void clear_msg(void *ctx)
|
|
{
|
|
/* should only be done on the current vcpu */
|
|
int vcpu = smp_id();
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
struct hv_message *msg = &hv->msg_page->sint_message[MSG_SINT];
|
|
|
|
atomic_set(&hv->sint_received, 0);
|
|
msg->header.message_type = 0;
|
|
barrier();
|
|
wrmsr(HV_X64_MSR_EOM, 0);
|
|
atomic_inc(&ncpus_done);
|
|
}
|
|
|
|
static bool msg_ok(int vcpu)
|
|
{
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
struct hv_input_post_message *post_msg = hv->post_msg;
|
|
struct hv_message *msg = &hv->msg_page->sint_message[MSG_SINT];
|
|
|
|
return msg->header.message_type == post_msg->message_type &&
|
|
msg->header.payload_size == post_msg->payload_size &&
|
|
msg->header.message_flags.msg_pending == 0 &&
|
|
msg->u.payload[0] == post_msg->payload[0] &&
|
|
hv->hvcall_status == 0 &&
|
|
atomic_read(&hv->sint_received) == 1;
|
|
}
|
|
|
|
static bool msg_busy(int vcpu)
|
|
{
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
struct hv_input_post_message *post_msg = hv->post_msg;
|
|
struct hv_message *msg = &hv->msg_page->sint_message[MSG_SINT];
|
|
|
|
return msg->header.message_type == post_msg->message_type &&
|
|
msg->header.payload_size == post_msg->payload_size &&
|
|
msg->header.message_flags.msg_pending == 1 &&
|
|
msg->u.payload[0] == post_msg->payload[0] - 1 &&
|
|
hv->hvcall_status == 0 &&
|
|
atomic_read(&hv->sint_received) == 0;
|
|
}
|
|
|
|
static void do_evt(void *ctx)
|
|
{
|
|
int vcpu = (ulong)ctx;
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
|
|
atomic_set(&hv->sint_received, 0);
|
|
hv->hvcall_status = do_hypercall(HVCALL_SIGNAL_EVENT,
|
|
hv->evt_conn, 1);
|
|
atomic_inc(&ncpus_done);
|
|
}
|
|
|
|
static void clear_evt(void *ctx)
|
|
{
|
|
/* should only be done on the current vcpu */
|
|
int vcpu = smp_id();
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
ulong *flags = hv->evt_page->slot[EVT_SINT].flags;
|
|
|
|
atomic_set(&hv->sint_received, 0);
|
|
flags[BIT_WORD(hv->evt_conn)] &= ~BIT_MASK(hv->evt_conn);
|
|
barrier();
|
|
atomic_inc(&ncpus_done);
|
|
}
|
|
|
|
static bool evt_ok(int vcpu)
|
|
{
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
ulong *flags = hv->evt_page->slot[EVT_SINT].flags;
|
|
|
|
return flags[BIT_WORD(hv->evt_conn)] == BIT_MASK(hv->evt_conn) &&
|
|
hv->hvcall_status == 0 &&
|
|
atomic_read(&hv->sint_received) == 1;
|
|
}
|
|
|
|
static bool evt_busy(int vcpu)
|
|
{
|
|
struct hv_vcpu *hv = &hv_vcpus[vcpu];
|
|
ulong *flags = hv->evt_page->slot[EVT_SINT].flags;
|
|
|
|
return flags[BIT_WORD(hv->evt_conn)] == BIT_MASK(hv->evt_conn) &&
|
|
hv->hvcall_status == 0 &&
|
|
atomic_read(&hv->sint_received) == 0;
|
|
}
|
|
|
|
static int run_test(int ncpus, int dst_add, ulong wait_cycles,
|
|
void (*func)(void *), bool (*is_ok)(int))
|
|
{
|
|
int i, ret = 0;
|
|
|
|
atomic_set(&ncpus_done, 0);
|
|
for (i = 0; i < ncpus; i++) {
|
|
ulong dst = (i + dst_add) % ncpus;
|
|
on_cpu_async(i, func, (void *)dst);
|
|
}
|
|
while (atomic_read(&ncpus_done) != ncpus)
|
|
pause();
|
|
|
|
while (wait_cycles--)
|
|
pause();
|
|
|
|
if (is_ok)
|
|
for (i = 0; i < ncpus; i++)
|
|
ret += is_ok(i);
|
|
return ret;
|
|
}
|
|
|
|
#define HV_STATUS_INVALID_HYPERCALL_CODE 2
|
|
|
|
int main(int ac, char **av)
|
|
{
|
|
int ncpus, ncpus_ok, i;
|
|
|
|
if (!synic_supported()) {
|
|
report_skip("Hyper-V SynIC is not supported");
|
|
goto summary;
|
|
}
|
|
|
|
setup_vm();
|
|
smp_init();
|
|
ncpus = cpu_count();
|
|
if (ncpus > MAX_CPUS)
|
|
report_abort("# cpus: %d > %d", ncpus, MAX_CPUS);
|
|
|
|
handle_irq(MSG_VEC, sint_isr);
|
|
handle_irq(EVT_VEC, sint_isr);
|
|
|
|
setup_hypercall();
|
|
|
|
if (do_hypercall(HVCALL_SIGNAL_EVENT, 0x1234, 1) ==
|
|
HV_STATUS_INVALID_HYPERCALL_CODE) {
|
|
report_skip("Hyper-V SynIC connections are not supported");
|
|
goto summary;
|
|
}
|
|
|
|
for (i = 0; i < ncpus; i++)
|
|
on_cpu(i, setup_cpu, (void *)read_cr3());
|
|
|
|
ncpus_ok = run_test(ncpus, 0, WAIT_CYCLES, do_msg, msg_ok);
|
|
report("send message to self: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
run_test(ncpus, 0, 0, clear_msg, NULL);
|
|
|
|
ncpus_ok = run_test(ncpus, 1, WAIT_CYCLES, do_msg, msg_ok);
|
|
report("send message to another cpu: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
ncpus_ok = run_test(ncpus, 1, WAIT_CYCLES, do_msg, msg_busy);
|
|
report("send message to busy slot: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
ncpus_ok = run_test(ncpus, 0, WAIT_CYCLES, clear_msg, msg_ok);
|
|
report("receive pending message: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
ncpus_ok = run_test(ncpus, 0, WAIT_CYCLES, do_evt, evt_ok);
|
|
report("signal event on self: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
run_test(ncpus, 0, 0, clear_evt, NULL);
|
|
|
|
ncpus_ok = run_test(ncpus, 1, WAIT_CYCLES, do_evt, evt_ok);
|
|
report("signal event on another cpu: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
ncpus_ok = run_test(ncpus, 1, WAIT_CYCLES, do_evt, evt_busy);
|
|
report("signal event already set: %d/%d",
|
|
ncpus_ok == ncpus, ncpus_ok, ncpus);
|
|
|
|
for (i = 0; i < ncpus; i++)
|
|
on_cpu(i, teardown_cpu, NULL);
|
|
|
|
teardown_hypercall();
|
|
|
|
summary:
|
|
return report_summary();
|
|
}
|