#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systimer.h>
#include <sys/systm.h>
#include <machine/cpufunc.h>
#include <dev/virtual/hyperv/hyperv_busdma.h>
#include <dev/virtual/hyperv/hyperv_machdep.h>
#include <dev/virtual/hyperv/hyperv_reg.h>
#include <dev/virtual/hyperv/hyperv_var.h>
#define HYPERV_DRAGONFLY_BUILD 0ULL
#define HYPERV_DRAGONFLY_VERSION ((uint64_t)__DragonFly_version)
#define HYPERV_DRAGONFLY_OSID 0ULL
#define MSR_HV_GUESTID_BUILD_DRAGONFLY \
(HYPERV_DRAGONFLY_BUILD & MSR_HV_GUESTID_BUILD_MASK)
#define MSR_HV_GUESTID_VERSION_DRAGONFLY \
((HYPERV_DRAGONFLY_VERSION << MSR_HV_GUESTID_VERSION_SHIFT) & \
MSR_HV_GUESTID_VERSION_MASK)
#define MSR_HV_GUESTID_OSID_DRAGONFLY \
((HYPERV_DRAGONFLY_OSID << MSR_HV_GUESTID_OSID_SHIFT) & \
MSR_HV_GUESTID_OSID_MASK)
#define MSR_HV_GUESTID_DRAGONFLY \
(MSR_HV_GUESTID_BUILD_DRAGONFLY | \
MSR_HV_GUESTID_VERSION_DRAGONFLY | \
MSR_HV_GUESTID_OSID_DRAGONFLY | \
MSR_HV_GUESTID_OSTYPE_FREEBSD)
struct hypercall_ctx {
void *hc_addr;
struct hyperv_dma hc_dma;
};
static void hyperv_cputimer_construct(struct cputimer *,
sysclock_t);
static sysclock_t hyperv_cputimer_count(void);
static boolean_t hyperv_identify(void);
static int hypercall_create(void);
static void hypercall_destroy(void);
static void hypercall_memfree(void);
static uint64_t hyperv_tc64_rdmsr(void);
u_int hyperv_features;
static u_int hyperv_recommends;
static u_int hyperv_pm_features;
static u_int hyperv_features3;
hyperv_tc64_t hyperv_tc64;
static struct cputimer hyperv_cputimer = {
.next = SLIST_ENTRY_INITIALIZER,
.name = "Hyper-V",
.pri = CPUTIMER_PRI_VMM,
.type = CPUTIMER_VMM,
.count = hyperv_cputimer_count,
.fromhz = cputimer_default_fromhz,
.fromus = cputimer_default_fromus,
.construct = hyperv_cputimer_construct,
.destruct = cputimer_default_destruct,
.freq = HYPERV_TIMER_FREQ
};
static struct cpucounter hyperv_cpucounter = {
.freq = HYPERV_TIMER_FREQ,
.count = hyperv_tc64_rdmsr,
.flags = CPUCOUNTER_FLAG_MPSYNC,
.prio = CPUCOUNTER_PRIO_VMM,
.type = CPUCOUNTER_VMM
};
static struct hypercall_ctx hypercall_context;
uint64_t
hypercall_post_message(bus_addr_t msg_paddr)
{
return hypercall_md(hypercall_context.hc_addr,
HYPERCALL_POST_MESSAGE, msg_paddr, 0);
}
static void
hyperv_cputimer_construct(struct cputimer *timer, sysclock_t oldclock)
{
timer->base = 0;
timer->base = oldclock - timer->count();
}
static sysclock_t
hyperv_cputimer_count(void)
{
uint64_t val;
val = rdmsr(MSR_HV_TIME_REF_COUNT);
return (val + hyperv_cputimer.base);
}
static void
hypercall_memfree(void)
{
hyperv_dmamem_free(&hypercall_context.hc_dma,
hypercall_context.hc_addr);
hypercall_context.hc_addr = NULL;
}
static int
hypercall_create(void)
{
uint64_t hc, hc_orig;
hypercall_context.hc_addr = hyperv_dmamem_alloc(NULL, PAGE_SIZE, 0,
PAGE_SIZE, &hypercall_context.hc_dma, BUS_DMA_WAITOK);
if (hypercall_context.hc_addr == NULL) {
kprintf("hyperv: Hypercall page allocation failed\n");
return ENOMEM;
}
hc_orig = rdmsr(MSR_HV_HYPERCALL);
hc = ((hypercall_context.hc_dma.hv_paddr >> PAGE_SHIFT) <<
MSR_HV_HYPERCALL_PGSHIFT) |
(hc_orig & MSR_HV_HYPERCALL_RSVD_MASK) |
MSR_HV_HYPERCALL_ENABLE;
wrmsr(MSR_HV_HYPERCALL, hc);
hc = rdmsr(MSR_HV_HYPERCALL);
if ((hc & MSR_HV_HYPERCALL_ENABLE) == 0) {
kprintf("hyperv: Hypercall setup failed\n");
hypercall_memfree();
return EIO;
}
if (bootverbose)
kprintf("hyperv: Hypercall created\n");
return 0;
}
static void
hypercall_destroy(void)
{
uint64_t hc;
if (hypercall_context.hc_addr == NULL)
return;
hc = rdmsr(MSR_HV_HYPERCALL);
wrmsr(MSR_HV_HYPERCALL, (hc & MSR_HV_HYPERCALL_RSVD_MASK));
hypercall_memfree();
if (bootverbose)
kprintf("hyperv: Hypercall destroyed\n");
}
static uint64_t
hyperv_tc64_rdmsr(void)
{
return (rdmsr(MSR_HV_TIME_REF_COUNT));
}
static boolean_t
hyperv_identify(void)
{
u_int regs[4];
unsigned int maxleaf;
if (vmm_guest != VMM_GUEST_HYPERV)
return (FALSE);
do_cpuid(CPUID_LEAF_HV_MAXLEAF, regs);
maxleaf = regs[0];
if (maxleaf < CPUID_LEAF_HV_LIMITS)
return (FALSE);
do_cpuid(CPUID_LEAF_HV_INTERFACE, regs);
if (regs[0] != CPUID_HV_IFACE_HYPERV)
return (FALSE);
do_cpuid(CPUID_LEAF_HV_FEATURES, regs);
if ((regs[0] & CPUID_HV_MSR_HYPERCALL) == 0) {
return (FALSE);
}
hyperv_features = regs[0];
hyperv_pm_features = regs[2];
hyperv_features3 = regs[3];
do_cpuid(CPUID_LEAF_HV_IDENTITY, regs);
kprintf("Hyper-V Version: %d.%d.%d [SP%d]\n",
regs[1] >> 16, regs[1] & 0xffff, regs[0], regs[2]);
kprintf(" Features=0x%pb%i\n",
"\020"
"\001VPRUNTIME"
"\002TMREFCNT"
"\003SYNIC"
"\004SYNTM"
"\005APIC"
"\006HYPERCALL"
"\007VPINDEX"
"\010RESET"
"\011STATS"
"\012REFTSC"
"\013IDLE"
"\014TMFREQ"
"\015DEBUG",
hyperv_features);
kprintf(" PM Features=0x%pb%i [C%u]\n",
"\020"
"\005C3HPET",
(hyperv_pm_features & ~CPUPM_HV_CSTATE_MASK),
CPUPM_HV_CSTATE(hyperv_pm_features));
kprintf(" Features3=0x%pb%i\n",
"\020"
"\001MWAIT"
"\002DEBUG"
"\003PERFMON"
"\004PCPUDPE"
"\005XMMHC"
"\006IDLE"
"\007SLEEP"
"\010NUMA"
"\011TMFREQ"
"\012SYNCMC"
"\013CRASH"
"\014DEBUGMSR"
"\015NPIEP"
"\016HVDIS",
hyperv_features3);
do_cpuid(CPUID_LEAF_HV_RECOMMENDS, regs);
hyperv_recommends = regs[0];
if (bootverbose)
kprintf(" Recommends: %08x %08x\n", regs[0], regs[1]);
do_cpuid(CPUID_LEAF_HV_LIMITS, regs);
if (bootverbose) {
kprintf(" Limits: Vcpu:%d Lcpu:%d Int:%d\n",
regs[0], regs[1], regs[2]);
}
if (maxleaf >= CPUID_LEAF_HV_HWFEATURES) {
do_cpuid(CPUID_LEAF_HV_HWFEATURES, regs);
if (bootverbose) {
kprintf(" HW Features: %08x, AMD: %08x\n",
regs[0], regs[3]);
}
}
return (TRUE);
}
static void
hyperv_init(void *dummy __unused)
{
int error;
if (!hyperv_identify()) {
if (vmm_guest == VMM_GUEST_HYPERV)
vmm_guest = VMM_GUEST_UNKNOWN;
return;
}
wrmsr(MSR_HV_GUEST_OS_ID, MSR_HV_GUESTID_DRAGONFLY);
if (hyperv_features & CPUID_HV_MSR_TIME_REFCNT) {
cputimer_register(&hyperv_cputimer);
cputimer_select(&hyperv_cputimer, 0);
cpucounter_register(&hyperv_cpucounter);
hyperv_tc64 = hyperv_tc64_rdmsr;
}
error = hypercall_create();
if (error) {
vmm_guest = VMM_GUEST_UNKNOWN;
}
hyperv_md_init();
}
SYSINIT(hyperv_initialize, SI_SUB_PRE_DRIVERS, SI_ORDER_FIRST,
hyperv_init, NULL);
static void
hyperv_uninit(void *dummy __unused)
{
hyperv_md_uninit();
if (hyperv_features & CPUID_HV_MSR_TIME_REFCNT) {
cputimer_deregister(&hyperv_cputimer);
}
hypercall_destroy();
}
SYSUNINIT(hyperv_uninitialize, SI_SUB_PRE_DRIVERS, SI_ORDER_FIRST,
hyperv_uninit, NULL);