This is a simple test which performs the following: * setup hypecall page * do some hypercalls and output their results Signed-off-by: Andrey Smetanin <asmetanin@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 ++ lib/x86/vm.h | 2 + x86/hyperv.c | 95 ++++++++++++++++++++++++++++++++++++++++++ x86/hyperv.h | 28 +++++++++++++ x86/hyperv_hypercall.c | 99 ++++++++++++++++++++++++++++++++++++++++++++ x86/unittests.cfg | 5 +++ 6 files changed, 233 insertions(+) create mode 100644 x86/hyperv_hypercall.c diff --git a/config/config-x86-common.mak b/config/config-x86-common.mak index 72b95e3..11714d6 100644 --- a/config/config-x86-common.mak +++ b/config/config-x86-common.mak @@ -38,6 +38,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_hypercall.flat \ ifdef API tests-common += api/api-sample @@ -119,6 +120,9 @@ $(TEST_DIR)/hyperv_synic.elf: $(cstart.o) $(TEST_DIR)/hyperv.o \ $(TEST_DIR)/hyperv_stimer.elf: $(cstart.o) $(TEST_DIR)/hyperv.o \ $(TEST_DIR)/hyperv_stimer.o +$(TEST_DIR)/hyperv_hypercall.elf: $(cstart.o) $(TEST_DIR)/hyperv.o \ + $(TEST_DIR)/hyperv_hypercall.o + arch_clean: $(RM) $(TEST_DIR)/*.o $(TEST_DIR)/*.flat $(TEST_DIR)/*.elf \ $(TEST_DIR)/.*.d lib/x86/.*.d diff --git a/lib/x86/vm.h b/lib/x86/vm.h index 28794d7..085e29f 100644 --- a/lib/x86/vm.h +++ b/lib/x86/vm.h @@ -4,6 +4,8 @@ #include "processor.h" #define PAGE_SIZE 4096ul +#define PAGE_SHIFT 12 + #ifdef __x86_64__ #define LARGE_PAGE_SIZE (512 * PAGE_SIZE) #else diff --git a/x86/hyperv.c b/x86/hyperv.c index 824773d..1d3ab23 100644 --- a/x86/hyperv.c +++ b/x86/hyperv.c @@ -1,4 +1,5 @@ #include "hyperv.h" +#include "vm.h" static void synic_ctl(u8 ctl, u8 vcpu_id, u8 sint) { @@ -22,3 +23,97 @@ void synic_sint_destroy(int vcpu, int sint) wrmsr(HV_X64_MSR_SINT0 + sint, 0xFF|HV_SYNIC_SINT_MASKED); synic_ctl(HV_TEST_DEV_SINT_ROUTE_DESTROY, vcpu, sint); } + +static void *hv_hypercall_page; + +static inline u64 generate_guest_id(u8 d_info1, u32 kernel_version, + u16 d_info2) +{ + u64 guest_id = 0; + + guest_id = (((u64)HV_LINUX_VENDOR_ID) << 48); + guest_id |= (((u64)(d_info1)) << 48); + guest_id |= (((u64)(kernel_version)) << 16); + guest_id |= ((u64)(d_info2)); + + return guest_id; +} + +int hv_hypercall_init(void) +{ + void *page; + u64 val; + + wrmsr(HV_X64_MSR_GUEST_OS_ID, generate_guest_id(0, 263168, 0)); + page = alloc_page(); + if (!page) { + return -1; + } + + wrmsr(HV_X64_MSR_HYPERCALL, + ((virt_to_phys(page) >> PAGE_SHIFT) << + HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_SHIFT) | + HV_X64_MSR_HYPERCALL_ENABLE); + + val = rdmsr(HV_X64_MSR_HYPERCALL); + if (!(val & HV_X64_MSR_HYPERCALL_ENABLE)) { + goto err; + } + + hv_hypercall_page = page; + return 0; +err: + free_page(page); + return -1; +} + +void hv_hypercall_deinit(void) +{ + wrmsr(HV_X64_MSR_HYPERCALL, 0); + free_page(hv_hypercall_page); + wrmsr(HV_X64_MSR_GUEST_OS_ID, 0); + hv_hypercall_page = NULL; +} + +u64 hv_hypercall(u64 control, void *input, void *output) +{ + u64 input_address = (input) ? virt_to_phys(input) : 0; + u64 output_address = (output) ? virt_to_phys(output) : 0; +#ifdef __x86_64__ + u64 hv_status = 0; + + if (!hv_hypercall_page) { + return (u64)-1; + } + + __asm__ __volatile__("mov %0, %%r8" : : "r" (output_address) : "r8"); + __asm__ __volatile__("call *%3" : "=a" (hv_status) : + "c" (control), "d" (input_address), + "m" (hv_hypercall_page)); + + return hv_status; + +#else + + u32 control_hi = control >> 32; + u32 control_lo = control & 0xFFFFFFFF; + u32 hv_status_hi = 1; + u32 hv_status_lo = 1; + u32 input_address_hi = input_address >> 32; + u32 input_address_lo = input_address & 0xFFFFFFFF; + u32 output_address_hi = output_address >> 32; + u32 output_address_lo = output_address & 0xFFFFFFFF; + + if (!hv_hypercall_page) { + return (u64)-1; + } + + __asm__ __volatile__ ("call *%8" : "=d"(hv_status_hi), + "=a"(hv_status_lo) : "d" (control_hi), + "a" (control_lo), "b" (input_address_hi), + "c" (input_address_lo), "D"(output_address_hi), + "S"(output_address_lo), "m" (hv_hypercall_page)); + + return hv_status_lo | ((u64)hv_status_hi << 32); +#endif /* !x86_64 */ +} diff --git a/x86/hyperv.h b/x86/hyperv.h index faf931b..a5888bf 100644 --- a/x86/hyperv.h +++ b/x86/hyperv.h @@ -5,11 +5,30 @@ #include "processor.h" #include "io.h" +#define HV_LINUX_VENDOR_ID 0x8100 + #define HYPERV_CPUID_FEATURES 0x40000003 #define HV_X64_MSR_TIME_REF_COUNT_AVAILABLE (1 << 1) #define HV_X64_MSR_SYNIC_AVAILABLE (1 << 2) #define HV_X64_MSR_SYNTIMER_AVAILABLE (1 << 3) +/* Hypercall MSRs (HV_X64_MSR_GUEST_OS_ID and HV_X64_MSR_HYPERCALL) available*/ +#define HV_X64_MSR_HYPERCALL_AVAILABLE (1 << 5) + +/* MSR used to identify the guest OS. */ +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 + +/* MSR used to setup pages used to communicate with the hypervisor. */ +#define HV_X64_MSR_HYPERCALL 0x40000001 +#define HV_X64_MSR_HYPERCALL_ENABLE 0x00000001 +#define HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_SHIFT 12 +#define HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_MASK \ + (~((1ull << HV_X64_MSR_HYPERCALL_PAGE_ADDRESS_SHIFT) - 1)) + +/* Declare the various hypercall operations. */ +#define HV_X64_HCALL_NOTIFY_LONG_SPIN_WAIT 0x0008 +#define HV_X64_HCALL_POST_MESSAGE 0x005c +#define HV_X64_HCALL_SIGNAL_EVENT 0x005d #define HV_X64_MSR_TIME_REF_COUNT 0x40000020 @@ -180,4 +199,13 @@ void synic_sint_create(int vcpu, int sint, int vec, bool auto_eoi); void synic_sint_set(int vcpu, int sint); void synic_sint_destroy(int vcpu, int sint); +static inline bool hv_hypercall_supported(void) +{ + return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_HYPERCALL_AVAILABLE; +} + +int hv_hypercall_init(void); +void hv_hypercall_deinit(void); +u64 hv_hypercall(u64 control, void *input, void *output); + #endif diff --git a/x86/hyperv_hypercall.c b/x86/hyperv_hypercall.c new file mode 100644 index 0000000..8964bd7 --- /dev/null +++ b/x86/hyperv_hypercall.c @@ -0,0 +1,99 @@ +#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" +#include "hyperv.h" + +#define MAX_CPUS 4 + +static atomic_t g_cpus_comp_count; +static int g_cpus_count; + +static void cpu_comp(void) +{ + atomic_inc(&g_cpus_comp_count); +} + +static void hv_hypercall_test_prepare(void *ctx) +{ + write_cr3((ulong)ctx); + + cpu_comp(); +} + +static void hv_hypercall_test(void *ctx) +{ + int vcpu = smp_id(); + u64 result; + u32 codes[] = {HV_X64_HCALL_NOTIFY_LONG_SPIN_WAIT, + HV_X64_HCALL_POST_MESSAGE, HV_X64_HCALL_SIGNAL_EVENT}; + + int i; + + for (i = 0; i < ARRAY_SIZE(codes); i++) { + result = hv_hypercall(codes[i], 0, 0); + report("Hyper-V hypercall 0x%x result 0x%llx vcpu %d", true, + codes[i], result, vcpu); + } + cpu_comp(); +} + +static void on_each_cpu_async_wait(void (*func)(void *ctx), void *ctx) +{ + int i; + + atomic_set(&g_cpus_comp_count, 0); + for (i = 0; i < g_cpus_count; i++) { + on_cpu_async(i, func, ctx); + } + while (atomic_read(&g_cpus_comp_count) != g_cpus_count) { + pause(); + } +} + +static void hv_hypercall_test_all(void) +{ + int ncpus; + + setup_vm(); + smp_init(); + setup_idt(); + enable_apic(); + + if (hv_hypercall_init()) { + report("Hyper-V hypercall setup", false); + return; + } + report("Hyper-V hypercall setup", true); + + ncpus = cpu_count(); + if (ncpus > MAX_CPUS) { + ncpus = MAX_CPUS; + } + + printf("cpus = %d\n", ncpus); + g_cpus_count = ncpus; + on_each_cpu_async_wait(hv_hypercall_test_prepare, (void *)read_cr3()); + on_each_cpu_async_wait(hv_hypercall_test, NULL); + + hv_hypercall_deinit(); +} + +int main(int ac, char **av) +{ + + if (!hv_hypercall_supported()) { + report("Hyper-V hypercall is not supported", true); + goto done; + } + + hv_hypercall_test_all(); +done: + return report_summary(); +} diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 99eff26..8805091 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -188,3 +188,8 @@ extra_params = -cpu kvm64,hv_synic -device hyperv-testdev file = hyperv_stimer.flat smp = 2 extra_params = -cpu kvm64,hv_time,hv_synic,hv_stimer -device hyperv-testdev + +[hyperv_hypercall] +file = hyperv_hypercall.flat +smp = 2 +extra_params = -cpu kvm64,hv_time -- 2.4.3 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html