#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <machine/clock.h>
#include <machine/cpufunc.h>
#include <machine/md_var.h>
#include <machine/specialreg.h>
#include <machine/vmm.h>
#include <sys/vmm_kernel.h>
#include "vmx.h"
#include "vmx_msr.h"
static bool
vmx_ctl_allows_one_setting(uint64_t msr_val, int bitpos)
{
return ((msr_val & (1UL << (bitpos + 32))) != 0);
}
static bool
vmx_ctl_allows_zero_setting(uint64_t msr_val, int bitpos)
{
return ((msr_val & (1UL << bitpos)) == 0);
}
int
vmx_set_ctlreg(int ctl_reg, int true_ctl_reg, uint32_t ones_mask,
uint32_t zeros_mask, uint32_t *retval)
{
int i;
uint64_t val, trueval;
bool true_ctls_avail, one_allowed, zero_allowed;
if ((ones_mask ^ zeros_mask) != (ones_mask | zeros_mask))
return (EINVAL);
true_ctls_avail = (rdmsr(MSR_VMX_BASIC) & (1UL << 55)) != 0;
val = rdmsr(ctl_reg);
if (true_ctls_avail)
trueval = rdmsr(true_ctl_reg);
else
trueval = val;
for (i = 0; i < 32; i++) {
one_allowed = vmx_ctl_allows_one_setting(trueval, i);
zero_allowed = vmx_ctl_allows_zero_setting(trueval, i);
KASSERT(one_allowed || zero_allowed,
("invalid zero/one setting for bit %d of ctl 0x%0x, "
"truectl 0x%0x\n", i, ctl_reg, true_ctl_reg));
if (zero_allowed && !one_allowed) {
if (ones_mask & (1 << i))
return (EINVAL);
*retval &= ~(1 << i);
} else if (one_allowed && !zero_allowed) {
if (zeros_mask & (1 << i))
return (EINVAL);
*retval |= 1 << i;
} else {
if (zeros_mask & (1 << i)) {
*retval &= ~(1 << i);
} else if (ones_mask & (1 << i)) {
*retval |= 1 << i;
} else if (!true_ctls_avail) {
*retval &= ~(1 << i);
} else if (vmx_ctl_allows_zero_setting(val, i)) {
*retval &= ~(1 << i);
} else if (vmx_ctl_allows_one_setting(val, i)) {
*retval |= 1 << i;
} else {
panic("vmx_set_ctlreg: unable to determine "
"correct value of ctl bit %d for msr "
"0x%0x and true msr 0x%0x", i, ctl_reg,
true_ctl_reg);
}
}
}
return (0);
}
void
vmx_msr_bitmap_initialize(struct vmx *vmx)
{
for (uint_t i = 0; i < VM_MAXCPU; i++) {
uint8_t *bitmap;
bitmap = kmem_alloc(PAGESIZE, KM_SLEEP);
VERIFY3U((uintptr_t)bitmap & PAGEOFFSET, ==, 0);
memset(bitmap, 0xff, PAGESIZE);
vmx->msr_bitmap[i] = bitmap;
}
}
void
vmx_msr_bitmap_destroy(struct vmx *vmx)
{
for (uint_t i = 0; i < VM_MAXCPU; i++) {
VERIFY3P(vmx->msr_bitmap[i], !=, NULL);
kmem_free(vmx->msr_bitmap[i], PAGESIZE);
vmx->msr_bitmap[i] = NULL;
}
}
void
vmx_msr_bitmap_change_access(struct vmx *vmx, int vcpuid, uint_t msr, int acc)
{
uint8_t *bitmap = vmx->msr_bitmap[vcpuid];
int byte, bit;
if (msr <= 0x00001FFF) {
byte = msr / 8;
} else if (msr >= 0xC0000000 && msr <= 0xC0001FFF) {
byte = 1024 + (msr - 0xC0000000) / 8;
} else {
panic("Invalid MSR for bitmap: %x", msr);
}
bit = msr & 0x7;
if (acc & MSR_BITMAP_ACCESS_READ) {
bitmap[byte] &= ~(1 << bit);
} else {
bitmap[byte] |= 1 << bit;
}
byte += 2048;
if (acc & MSR_BITMAP_ACCESS_WRITE) {
bitmap[byte] &= ~(1 << bit);
} else {
bitmap[byte] |= 1 << bit;
}
}
static uint64_t misc_enable;
static uint64_t platform_info;
static uint64_t turbo_ratio_limit;
static bool
nehalem_cpu(void)
{
uint_t family, model;
family = CPUID_TO_FAMILY(cpu_id);
model = CPUID_TO_MODEL(cpu_id);
if (family == 0x6) {
switch (model) {
case 0x1A:
case 0x1E:
case 0x1F:
case 0x2E:
return (true);
default:
break;
}
}
return (false);
}
static bool
westmere_cpu(void)
{
uint_t family, model;
family = CPUID_TO_FAMILY(cpu_id);
model = CPUID_TO_MODEL(cpu_id);
if (family == 0x6) {
switch (model) {
case 0x25:
case 0x2C:
return (true);
default:
break;
}
}
return (false);
}
static bool
pat_valid(uint64_t val)
{
int i, pa;
for (i = 0; i < 8; i++) {
pa = (val >> (i * 8)) & 0xff;
if (pa == 2 || pa == 3 || pa >= 8)
return (false);
}
return (true);
}
void
vmx_msr_init(void)
{
uint64_t bus_freq, ratio;
int i;
misc_enable = rdmsr(MSR_IA32_MISC_ENABLE);
misc_enable |= (1 << 12) | (1 << 11);
misc_enable &= ~((1 << 18) | (1 << 16));
if (nehalem_cpu() || westmere_cpu())
bus_freq = 133330000;
else
bus_freq = 100000000;
ratio = (tsc_freq / bus_freq) & 0xff;
platform_info = (ratio << 8) | (ratio << 40);
for (i = 0; i < 8; i++)
turbo_ratio_limit = (turbo_ratio_limit << 8) | ratio;
}
void
vmx_msr_guest_init(struct vmx *vmx, int vcpuid)
{
uint64_t *guest_msrs = vmx->guest_msrs[vcpuid];
guest_msr_rw(vmx, vcpuid, MSR_GSBASE);
guest_msr_rw(vmx, vcpuid, MSR_FSBASE);
guest_msr_rw(vmx, vcpuid, MSR_SYSENTER_CS_MSR);
guest_msr_rw(vmx, vcpuid, MSR_SYSENTER_ESP_MSR);
guest_msr_rw(vmx, vcpuid, MSR_SYSENTER_EIP_MSR);
guest_msr_rw(vmx, vcpuid, MSR_EFER);
guest_msr_ro(vmx, vcpuid, MSR_TSC);
guest_msr_rw(vmx, vcpuid, MSR_LSTAR);
guest_msr_rw(vmx, vcpuid, MSR_CSTAR);
guest_msr_rw(vmx, vcpuid, MSR_STAR);
guest_msr_rw(vmx, vcpuid, MSR_SF_MASK);
guest_msr_rw(vmx, vcpuid, MSR_KGSBASE);
guest_msrs[IDX_MSR_PAT] = PAT_VALUE(0, PAT_WRITE_BACK) |
PAT_VALUE(1, PAT_WRITE_THROUGH) |
PAT_VALUE(2, PAT_UNCACHED) |
PAT_VALUE(3, PAT_UNCACHEABLE) |
PAT_VALUE(4, PAT_WRITE_BACK) |
PAT_VALUE(5, PAT_WRITE_THROUGH) |
PAT_VALUE(6, PAT_UNCACHED) |
PAT_VALUE(7, PAT_UNCACHEABLE);
}
void
vmx_msr_guest_enter(struct vmx *vmx, int vcpuid)
{
uint64_t *guest_msrs = vmx->guest_msrs[vcpuid];
uint64_t *host_msrs = vmx->host_msrs[vcpuid];
host_msrs[IDX_MSR_LSTAR] = rdmsr(MSR_LSTAR);
host_msrs[IDX_MSR_CSTAR] = rdmsr(MSR_CSTAR);
host_msrs[IDX_MSR_STAR] = rdmsr(MSR_STAR);
host_msrs[IDX_MSR_SF_MASK] = rdmsr(MSR_SF_MASK);
wrmsr(MSR_LSTAR, guest_msrs[IDX_MSR_LSTAR]);
wrmsr(MSR_CSTAR, guest_msrs[IDX_MSR_CSTAR]);
wrmsr(MSR_STAR, guest_msrs[IDX_MSR_STAR]);
wrmsr(MSR_SF_MASK, guest_msrs[IDX_MSR_SF_MASK]);
wrmsr(MSR_KGSBASE, guest_msrs[IDX_MSR_KGSBASE]);
}
void
vmx_msr_guest_exit(struct vmx *vmx, int vcpuid)
{
uint64_t *guest_msrs = vmx->guest_msrs[vcpuid];
uint64_t *host_msrs = vmx->host_msrs[vcpuid];
guest_msrs[IDX_MSR_LSTAR] = rdmsr(MSR_LSTAR);
guest_msrs[IDX_MSR_CSTAR] = rdmsr(MSR_CSTAR);
guest_msrs[IDX_MSR_STAR] = rdmsr(MSR_STAR);
guest_msrs[IDX_MSR_SF_MASK] = rdmsr(MSR_SF_MASK);
guest_msrs[IDX_MSR_KGSBASE] = rdmsr(MSR_KGSBASE);
wrmsr(MSR_LSTAR, host_msrs[IDX_MSR_LSTAR]);
wrmsr(MSR_CSTAR, host_msrs[IDX_MSR_CSTAR]);
wrmsr(MSR_STAR, host_msrs[IDX_MSR_STAR]);
wrmsr(MSR_SF_MASK, host_msrs[IDX_MSR_SF_MASK]);
}
vm_msr_result_t
vmx_rdmsr(struct vmx *vmx, int vcpuid, uint32_t num, uint64_t *val)
{
const uint64_t *guest_msrs = vmx->guest_msrs[vcpuid];
switch (num) {
case MSR_IA32_FEATURE_CONTROL:
*val = IA32_FEATURE_CONTROL_LOCK;
break;
case MSR_IA32_MISC_ENABLE:
*val = misc_enable;
break;
case MSR_PLATFORM_INFO:
*val = platform_info;
break;
case MSR_TURBO_RATIO_LIMIT:
case MSR_TURBO_RATIO_LIMIT1:
*val = turbo_ratio_limit;
break;
case MSR_PAT:
*val = guest_msrs[IDX_MSR_PAT];
break;
default:
return (VMR_UNHANLDED);
}
return (VMR_OK);
}
vm_msr_result_t
vmx_wrmsr(struct vmx *vmx, int vcpuid, uint32_t num, uint64_t val)
{
uint64_t *guest_msrs = vmx->guest_msrs[vcpuid];
uint64_t changed;
switch (num) {
case MSR_IA32_MISC_ENABLE:
changed = val ^ misc_enable;
changed &= ~(1UL << 34);
if (changed) {
return (VMR_UNHANLDED);
}
break;
case MSR_PAT:
if (!pat_valid(val)) {
return (VMR_GP);
}
guest_msrs[IDX_MSR_PAT] = val;
break;
default:
return (VMR_UNHANLDED);
}
return (VMR_OK);
}