diff --git a/sys/amd64/vmm/intel/vmx.c b/sys/amd64/vmm/intel/vmx.c --- a/sys/amd64/vmm/intel/vmx.c +++ b/sys/amd64/vmm/intel/vmx.c @@ -292,6 +292,9 @@ SDT_PROBE_DEFINE3(vmm, vmx, exit, vminsn, "struct vmx *", "int", "struct vm_exit *"); +SDT_PROBE_DEFINE3(vmm, vmx, exit, vmcall, + "struct vmx *", "int", "struct vm_exit *"); + SDT_PROBE_DEFINE4(vmm, vmx, exit, unknown, "struct vmx *", "int", "struct vm_exit *", "uint32_t"); @@ -2765,7 +2768,6 @@ vmexit->inst_length = 0; handled = HANDLED; break; - case EXIT_REASON_VMCALL: case EXIT_REASON_VMCLEAR: case EXIT_REASON_VMLAUNCH: case EXIT_REASON_VMPTRLD: @@ -2778,6 +2780,11 @@ SDT_PROBE3(vmm, vmx, exit, vminsn, vmx, vcpu, vmexit); vmexit->exitcode = VM_EXITCODE_VMINSN; break; + case EXIT_REASON_VMCALL: + SDT_PROBE3(vmm, vmx, exit, vmcall, vmx, vcpu, vmexit); + vmexit->exitcode = 0; + handled = HANDLED; + break; default: SDT_PROBE4(vmm, vmx, exit, unknown, vmx, vcpu, vmexit, reason); diff --git a/sys/amd64/vmm/intel/vmx_msr.c b/sys/amd64/vmm/intel/vmx_msr.c --- a/sys/amd64/vmm/intel/vmx_msr.c +++ b/sys/amd64/vmm/intel/vmx_msr.c @@ -409,6 +409,20 @@ wrmsr(MSR_TSC_AUX, host_aux); } +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 +#define HV_X64_MSR_HYPERCALL 0x40000001 + +/* MSR used to provide vcpu index */ +#define HV_REGISTER_VP_INDEX 0x40000002 +#define HV_X64_MSR_TIME_REF_COUNT 0x40000020 +#define HV_X64_MSR_REFERENCE_TSC 0x40000021 +#define HV_X64_MSR_TSC_FREQUENCY 0x40000022 + +/* MSR used to retrieve the local APIC timer frequency */ +#define HV_X64_MSR_APIC_FREQUENCY 0x40000023 + +#define APIC_BUS_FREQUENCY (1000000000ULL) + int vmx_rdmsr(struct vmx *vmx, int vcpuid, u_int num, uint64_t *val, bool *retu) { @@ -419,6 +433,21 @@ error = 0; switch (num) { + case HV_X64_MSR_TIME_REF_COUNT: { + /* Hyper-V reports time in 100ns units (10 MHz) */ + uint64_t tsc_per_100ns = (tsc_freq / 10000000UL) ? : 1; + *val = (rdtsc() / tsc_per_100ns); + break; + } + case HV_X64_MSR_APIC_FREQUENCY: + *val = APIC_BUS_FREQUENCY; + break; + case HV_REGISTER_VP_INDEX: + *val = vcpuid; + break; + case HV_X64_MSR_TSC_FREQUENCY: + *val = tsc_freq; + break; case MSR_MCG_CAP: case MSR_MCG_STATUS: *val = 0; diff --git a/sys/amd64/vmm/x86.c b/sys/amd64/vmm/x86.c --- a/sys/amd64/vmm/x86.c +++ b/sys/amd64/vmm/x86.c @@ -53,7 +53,7 @@ static SYSCTL_NODE(_hw_vmm, OID_AUTO, topology, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, NULL); -#define CPUID_VM_HIGH 0x40000000 +#define CPUID_VM_HIGH 0x40000005 static const char bhyve_id[12] = "bhyve bhyve "; @@ -115,8 +115,9 @@ if (func > cpu_exthigh) func = cpu_exthigh; } else if (func >= 0x40000000) { - if (func > CPUID_VM_HIGH) + if (func > CPUID_VM_HIGH) { func = CPUID_VM_HIGH; + } } else if (func > cpu_high) { func = cpu_high; } @@ -605,14 +606,64 @@ regs[2] = 0; regs[3] = 0; break; - - case 0x40000000: +#define HYPERV_CPUID_VENDOR_AND_MAX_FUNCTIONS 0x40000000 + case HYPERV_CPUID_VENDOR_AND_MAX_FUNCTIONS: regs[0] = CPUID_VM_HIGH; bcopy(bhyve_id, ®s[1], 4); bcopy(bhyve_id + 4, ®s[2], 4); bcopy(bhyve_id + 8, ®s[3], 4); break; +#define HYPERV_CPUID_INTERFACE 0x40000001 + case HYPERV_CPUID_INTERFACE: + /* "Hv#1" signature */ +#define HYPERV_CPUID_SIGNATURE_EAX 0x31237648 + regs[0] = HYPERV_CPUID_SIGNATURE_EAX; + regs[1] = regs[2] = regs[3] = 0; + break; + +#define HYPERV_CPUID_VERSION 0x40000002 + case HYPERV_CPUID_VERSION: + regs[0] = regs[1] = regs[2] = regs[3] = 0; + regs[0] = 0x00003839; + regs[1] = 0x000A0000; + break; + +#define HYPERV_CPUID_FEATURES 0x40000003 +#define HV_MSR_TIME_REF_COUNT_AVAILABLE (1U << 1) +#define HV_MSR_HYPERCALL_AVAILABLE (1U << 5) +#define HV_MSR_VP_INDEX_AVAILABLE (1U << 6) +#define HV_ACCESS_FREQUENCY_MSRS (1U << 11) +#define HV_MSR_REFERENCE_TSC_AVAILABLE (1U << 9) +#define HV_FEATURE_FREQUENCY_MSRS_AVAILABLE (1 << 8) + case HYPERV_CPUID_FEATURES: + regs[0] = regs[1] = regs[2] = regs[3] = 0; + regs[0] |= HV_MSR_TIME_REF_COUNT_AVAILABLE; + regs[0] |= HV_MSR_HYPERCALL_AVAILABLE; + regs[0] |= HV_MSR_VP_INDEX_AVAILABLE; + regs[0] |= HV_MSR_REFERENCE_TSC_AVAILABLE; + regs[0] |= HV_ACCESS_FREQUENCY_MSRS; + regs[3] |= HV_FEATURE_FREQUENCY_MSRS_AVAILABLE; + break; + +#define HYPERV_CPUID_ENLIGHTMENT_INFO 0x40000004 +#define HYPERV_CPUID_IMPLEMENT_LIMITS 0x40000005 +#define HYPERV_RELAXED_TIMING_RECOMMENDED (1UL << 5) + case HYPERV_CPUID_ENLIGHTMENT_INFO: + regs[0] = HYPERV_RELAXED_TIMING_RECOMMENDED; + regs[1] = 0xffffffff; /* spinlock retry: never notify hypervisor */ + regs[2] = regs[3] = 0; + break; + case HYPERV_CPUID_IMPLEMENT_LIMITS: + /* Maximum number of virtual processors */ + regs[0] = VM_MAXCPU; + + /* Maximum number of logical processors */ + regs[1] = VM_MAXCPU; + + regs[2] = regs[3] = 0; + break; + default: default_leaf: /* diff --git a/usr.sbin/bhyve/xmsr.c b/usr.sbin/bhyve/xmsr.c --- a/usr.sbin/bhyve/xmsr.c +++ b/usr.sbin/bhyve/xmsr.c @@ -36,21 +36,83 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include #include "debug.h" #include "xmsr.h" static int cpu_vendor_intel, cpu_vendor_amd, cpu_vendor_hygon; +static uint64_t tsc_ref; +static uint64_t hypercall; +static uint64_t guest_os_id; +static uint64_t freq_khz; + +struct ms_hyperv_tsc_page { + volatile uint32_t tsc_sequence; + uint32_t reserved1; + volatile uint64_t tsc_scale; + volatile int64_t tsc_offset; +} __packed; + +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 +#define HV_X64_MSR_HYPERCALL 0x40000001 +#define HV_X64_MSR_REFERENCE_TSC 0x40000021 + +#define HV_X64_MSR_HYPERCALL_ENABLE 0x0001 int emulate_wrmsr(struct vmctx *ctx, int vcpu, uint32_t num, uint64_t val) { + /* Hyper-V MSR-s */ + switch (num) { + case HV_X64_MSR_HYPERCALL: { + void *base; + uint64_t gpa = val & (~((1ULL << 12) - 1)); + + if (guest_os_id == 0) + return (0); + + if (!(val & HV_X64_MSR_HYPERCALL_ENABLE)) { + hypercall = val; + return (0); + } + + base = vm_map_gpa(ctx, gpa, PAGE_SIZE); + assert(base != NULL); + + if (cpu_vendor_intel) { + const uint8_t hypercall_intel[] = { 0x0F, 0x01, 0xC1, 0xC3 }; /* VMCALL */ + + memcpy(base, hypercall_intel, sizeof (hypercall_intel)); + } else if (cpu_vendor_amd || cpu_vendor_hygon) { + const uint8_t hypercall_amd[] = { 0x0F, 0x01, 0xD9, 0xC3 }; /* VMMCALL */; + + memcpy(base, hypercall_amd, sizeof (hypercall_amd)); + } else { + return (-1); + } + return (0); + } + case HV_X64_MSR_GUEST_OS_ID: + guest_os_id = val; + if (guest_os_id == 0) { + hypercall &= ~HV_X64_MSR_HYPERCALL_ENABLE; + } + return (0); + default: + break; + } + if (cpu_vendor_intel) { switch (num) { @@ -61,6 +123,31 @@ return (0); case MSR_BIOS_SIGN: return (0); + case HV_X64_MSR_REFERENCE_TSC: + { + if (val & 0x1) { + struct ms_hyperv_tsc_page *tsp; + uint64_t gpa; + uint32_t seq; + + gpa = val & (~((1ULL << 12) - 1)); + tsp = vm_map_gpa(ctx, gpa, PAGE_SIZE); + assert(tsp != NULL); + + seq = tsp->tsc_sequence; + tsp->tsc_sequence = 0; + atomic_thread_fence_rel(); + tsp->tsc_scale = ((10000LL << 32) / freq_khz) << 32; + tsp->tsc_offset = 0; + atomic_thread_fence_rel(); + seq++; + if (seq == 0xffffffff || seq == 0) + seq = 1; + tsp->tsc_sequence = seq; + } + tsc_ref = val; + return (0); + } default: break; } @@ -107,6 +194,21 @@ { int error = 0; + /* common case of Hyper-V MSR-s */ + switch (num) { + case HV_X64_MSR_HYPERCALL: + *val = hypercall; + return (0); + case HV_X64_MSR_GUEST_OS_ID: + *val = guest_os_id = *val; + return (0); + case HV_X64_MSR_REFERENCE_TSC: + *val = tsc_ref; + return (0); + default: + break; + } + if (cpu_vendor_intel) { switch (num) { case MSR_BIOS_SIGN: @@ -241,5 +343,18 @@ EPRINTLN("Unknown cpu vendor \"%s\"", cpu_vendor); error = -1; } + + if (error == 0) { + size_t len = sizeof (freq_khz); + + error = sysctlbyname("machdep.tsc_freq", &freq_khz, &len, NULL, 0); + if (error == 0) { + freq_khz /= 1000; + if (freq_khz == 0) { + warnx("CPU freq is too small"); + error = -1; + } + } + } return (error); }