#include <pexpert/pexpert.h>
#include <i386/cpuid.h>
#include <i386/cpu_data.h>
#include <i386/mp.h>
#include <i386/proc_reg.h>
#include <i386/vmx.h>
#include <i386/vmx/vmx_asm.h>
#include <i386/vmx/vmx_shims.h>
#include <i386/vmx/vmx_cpu.h>
#include <mach/mach_host.h>
#define VMX_KPRINTF(x...)
int vmx_use_count = 0;
boolean_t vmx_exclusive = FALSE;
decl_simple_lock_data(static,vmx_use_count_lock)
static inline boolean_t
vmx_is_available(void)
{
return (0 != (cpuid_features() & CPUID_FEATURE_VMX));
}
static inline boolean_t
vmxon_is_enabled(void)
{
return (vmx_is_available() &&
(rdmsr64(MSR_IA32_FEATURE_CONTROL) & MSR_IA32_FEATCTL_VMXON));
}
static inline boolean_t
vmx_is_cr0_valid(vmx_specs_t *specs)
{
uintptr_t cr0 = get_cr0();
return (0 == ((~cr0 & specs->cr0_fixed_0)|(cr0 & ~specs->cr0_fixed_1)));
}
static inline boolean_t
vmx_is_cr4_valid(vmx_specs_t *specs)
{
uintptr_t cr4 = get_cr4();
return (0 == ((~cr4 & specs->cr4_fixed_0)|(cr4 & ~specs->cr4_fixed_1)));
}
static void
vmx_init(void)
{
uint64_t msr_image;
if (!vmx_is_available())
return;
msr_image = rdmsr64(MSR_IA32_FEATURE_CONTROL);
if (0 == ((msr_image & MSR_IA32_FEATCTL_LOCK)))
wrmsr64(MSR_IA32_FEATURE_CONTROL,
(msr_image |
MSR_IA32_FEATCTL_VMXON |
MSR_IA32_FEATCTL_LOCK));
}
void
vmx_get_specs()
{
vmx_specs_t *specs = ¤t_cpu_datap()->cpu_vmx.specs;
uint64_t msr_image;
simple_lock_init(&vmx_use_count_lock, 0);
vmx_init();
if (specs->initialized)
return;
else
specs->initialized = TRUE;
specs->vmx_present = vmx_is_available() && vmxon_is_enabled();
if (!specs->vmx_present)
return;
#define bitfield(x,f) ((x >> f##_BIT) & f##_MASK)
msr_image = rdmsr64(MSR_IA32_VMX_BASIC);
specs->vmcs_id = (uint32_t)(msr_image & VMX_VCR_VMCS_REV_ID);
specs->vmcs_mem_type = bitfield(msr_image, VMX_VCR_VMCS_MEM_TYPE) != 0;
specs->vmcs_size = bitfield(msr_image, VMX_VCR_VMCS_SIZE);
msr_image = rdmsr64(MSR_IA32_VMXPINBASED_CTLS);
specs->pin_exctls_0 = (uint32_t)(msr_image & 0xFFFFFFFF);
specs->pin_exctls_1 = (uint32_t)(msr_image >> 32);
msr_image = rdmsr64(MSR_IA32_PROCBASED_CTLS);
specs->proc_exctls_0 = (uint32_t)(msr_image & 0xFFFFFFFF);
specs->proc_exctls_1 = (uint32_t)(msr_image >> 32);
msr_image = rdmsr64(MSR_IA32_VMX_EXIT_CTLS);
specs->exit_ctls_0 = (uint32_t)(msr_image & 0xFFFFFFFF);
specs->exit_ctls_1 = (uint32_t)(msr_image >> 32);
msr_image = rdmsr64(MSR_IA32_VMX_ENTRY_CTLS);
specs->enter_ctls_0 = (uint32_t)(msr_image & 0xFFFFFFFF);
specs->enter_ctls_0 = (uint32_t)(msr_image >> 32);
msr_image = rdmsr64(MSR_IA32_VMX_MISC);
specs->act_halt = bitfield(msr_image, VMX_VCR_ACT_HLT) != 0;
specs->act_shutdown = bitfield(msr_image, VMX_VCR_ACT_SHUTDOWN) != 0;
specs->act_SIPI = bitfield(msr_image, VMX_VCR_ACT_SIPI) != 0;
specs->act_CSTATE = bitfield(msr_image, VMX_VCR_ACT_CSTATE) != 0;
specs->cr3_targs = bitfield(msr_image, VMX_VCR_CR3_TARGS);
specs->max_msrs = (uint32_t)(512 * (1 + bitfield(msr_image, VMX_VCR_MAX_MSRS)));
specs->mseg_id = (uint32_t)bitfield(msr_image, VMX_VCR_MSEG_ID);
specs->cr0_fixed_0 = (uint32_t)rdmsr64(MSR_IA32_VMX_CR0_FIXED0) & 0xFFFFFFFF;
specs->cr0_fixed_1 = (uint32_t)rdmsr64(MSR_IA32_VMX_CR0_FIXED1) & 0xFFFFFFFF;
specs->cr4_fixed_0 = (uint32_t)rdmsr64(MSR_IA32_VMX_CR4_FIXED0) & 0xFFFFFFFF;
specs->cr4_fixed_1 = (uint32_t)rdmsr64(MSR_IA32_VMX_CR4_FIXED1) & 0xFFFFFFFF;
}
static void
vmx_on(void *arg __unused)
{
vmx_cpu_t *cpu = ¤t_cpu_datap()->cpu_vmx;
addr64_t vmxon_region_paddr;
int result;
vmx_init();
assert(cpu->specs.vmx_present);
if (NULL == cpu->vmxon_region)
panic("vmx_on: VMXON region not allocated");
vmxon_region_paddr = vmx_paddr(cpu->vmxon_region);
set_cr4(get_cr4() | CR4_VMXE);
assert(vmx_is_cr0_valid(&cpu->specs));
assert(vmx_is_cr4_valid(&cpu->specs));
result = __vmxon(vmxon_region_paddr);
if (result != VMX_SUCCEED) {
panic("vmx_on: unexpected return %d from __vmxon()", result);
}
}
static void
vmx_off(void *arg __unused)
{
int result;
result = __vmxoff();
if (result != VMX_SUCCEED) {
panic("vmx_off: unexpected return %d from __vmxoff()", result);
}
set_cr4(get_cr4() & ~CR4_VMXE);
}
static void
vmx_allocate_vmxon_regions(void)
{
unsigned int i;
for (i=0; i<real_ncpus; i++) {
vmx_cpu_t *cpu = &cpu_datap(i)->cpu_vmx;
cpu->vmxon_region = vmx_pcalloc();
if (NULL == cpu->vmxon_region)
panic("vmx_allocate_vmxon_regions: unable to allocate VMXON region");
*(uint32_t*)(cpu->vmxon_region) = cpu->specs.vmcs_id;
}
}
static void
vmx_free_vmxon_regions(void)
{
unsigned int i;
for (i=0; i<real_ncpus; i++) {
vmx_cpu_t *cpu = &cpu_datap(i)->cpu_vmx;
vmx_pfree(cpu->vmxon_region);
cpu->vmxon_region = NULL;
}
}
static boolean_t
vmx_globally_available(void)
{
unsigned int i;
boolean_t available = TRUE;
for (i=0; i<real_ncpus; i++) {
vmx_cpu_t *cpu = &cpu_datap(i)->cpu_vmx;
if (!cpu->specs.vmx_present)
available = FALSE;
}
VMX_KPRINTF("VMX available: %d\n", available);
return available;
}
int
host_vmxon(boolean_t exclusive)
{
int error;
boolean_t do_it = FALSE;
if (!vmx_globally_available())
return VMX_UNSUPPORTED;
simple_lock(&vmx_use_count_lock);
if (vmx_exclusive) {
error = VMX_INUSE;
} else {
vmx_use_count++;
if (vmx_use_count == 1)
do_it = TRUE;
vmx_exclusive = exclusive;
VMX_KPRINTF("VMX use count: %d\n", vmx_use_count);
error = VMX_OK;
}
simple_unlock(&vmx_use_count_lock);
if (do_it) {
vmx_allocate_vmxon_regions();
mp_rendezvous(NULL, vmx_on, NULL, NULL);
}
return error;
}
void
host_vmxoff()
{
boolean_t do_it = FALSE;
simple_lock(&vmx_use_count_lock);
if (vmx_use_count) {
vmx_use_count--;
vmx_exclusive = FALSE;
if (!vmx_use_count)
do_it = TRUE;
}
simple_unlock(&vmx_use_count_lock);
if (do_it) {
mp_rendezvous(NULL, vmx_off, NULL, NULL);
vmx_free_vmxon_regions();
}
VMX_KPRINTF("VMX use count: %d\n", vmx_use_count);
}
void
vmx_suspend()
{
VMX_KPRINTF("vmx_suspend\n");
if (vmx_use_count)
vmx_off(NULL);
}
void
vmx_resume()
{
VMX_KPRINTF("vmx_resume\n");
vmx_init();
if (vmx_use_count)
vmx_on(NULL);
}