#include <sys/cdefs.h>
#ifdef __KERNEL_RCSID
__KERNEL_RCSID(0, "$NetBSD: hyperv.c,v 1.17 2025/04/12 19:31:44 nonaka Exp $");
#endif
#ifdef __FBSDID
__FBSDID("$FreeBSD: head/sys/dev/hyperv/vmbus/hyperv.c 331757 2018-03-30 02:25:12Z emaste $");
#endif
#ifdef _KERNEL_OPT
#include "lapic.h"
#include "genfb.h"
#include "opt_ddb.h"
#include "vmbus.h"
#include "wsdisplay.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/kmem.h>
#include <sys/module.h>
#include <sys/pmf.h>
#include <sys/sysctl.h>
#include <sys/timetc.h>
#include <uvm/uvm_extern.h>
#include <machine/autoconf.h>
#include <machine/bootinfo.h>
#include <machine/cpufunc.h>
#include <machine/cputypes.h>
#include <machine/cpuvar.h>
#include <machine/cpu_counter.h>
#include <x86/apicvar.h>
#include <x86/efi.h>
#include <dev/wsfb/genfbvar.h>
#include <x86/genfb_machdep.h>
#include <x86/x86/hypervreg.h>
#include <x86/x86/hypervvar.h>
#include <dev/hyperv/vmbusvar.h>
#include <dev/hyperv/genfb_vmbusvar.h>
#ifdef DDB
#include <machine/db_machdep.h>
#include <ddb/db_sym.h>
#include <ddb/db_extern.h>
#endif
struct hyperv_softc {
device_t sc_dev;
struct sysctllog *sc_log;
};
struct hyperv_hypercall_ctx {
void *hc_addr;
paddr_t hc_paddr;
};
struct hyperv_percpu_data {
int pd_idtvec;
};
static struct hyperv_hypercall_ctx hyperv_hypercall_ctx;
static void __attribute__((naked)) __aligned(PAGE_SIZE)
hyperv_hypercall_page(void)
{
__asm__ __volatile__ (".fill %c0, 1, 0xcc" :: "i" (PAGE_SIZE));
}
static u_int hyperv_get_timecount(struct timecounter *);
static u_int hyperv_features;
static u_int hyperv_recommends;
static u_int hyperv_pm_features;
static u_int hyperv_features3;
static char hyperv_version_str[64];
static char hyperv_features_str[256];
static char hyperv_pm_features_str[256];
static char hyperv_features3_str[256];
uint32_t hyperv_vcpuid[MAXCPUS];
static struct timecounter hyperv_timecounter = {
.tc_get_timecount = hyperv_get_timecount,
.tc_counter_mask = 0xffffffff,
.tc_frequency = HYPERV_TIMER_FREQ,
.tc_name = "Hyper-V",
.tc_quality = 2000,
};
static void hyperv_proc_dummy(void *, struct cpu_info *);
struct hyperv_proc {
hyperv_proc_t func;
void *arg;
};
static struct hyperv_proc hyperv_event_proc = {
.func = hyperv_proc_dummy,
};
static struct hyperv_proc hyperv_message_proc = {
.func = hyperv_proc_dummy,
};
static int hyperv_match(device_t, cfdata_t, void *);
static void hyperv_attach(device_t, device_t, void *);
static int hyperv_detach(device_t, int);
CFATTACH_DECL_NEW(hyperv, sizeof(struct hyperv_softc),
hyperv_match, hyperv_attach, hyperv_detach, NULL);
static void hyperv_hypercall_memfree(void);
static bool hyperv_init_hypercall(void);
static int hyperv_sysctl_setup_root(struct hyperv_softc *);
static u_int
hyperv_get_timecount(struct timecounter *tc)
{
return (u_int)rdmsr(MSR_HV_TIME_REF_COUNT);
}
static uint64_t
hyperv_tc64_rdmsr(void)
{
return rdmsr(MSR_HV_TIME_REF_COUNT);
}
#ifdef __amd64__
struct hyperv_ref_tsc {
struct hyperv_reftsc *tsc_ref;
paddr_t tsc_paddr;
};
static struct hyperv_ref_tsc hyperv_ref_tsc;
static u_int hyperv_tsc_timecount(struct timecounter *);
static struct timecounter hyperv_tsc_timecounter = {
.tc_get_timecount = hyperv_tsc_timecount,
.tc_counter_mask = 0xffffffff,
.tc_frequency = HYPERV_TIMER_FREQ,
.tc_name = "Hyper-V-TSC",
.tc_quality = 3000,
};
static __inline u_int
atomic_load_acq_int(volatile u_int *p)
{
u_int r = *p;
__insn_barrier();
return r;
}
static uint64_t
hyperv_tc64_tsc(void)
{
struct hyperv_reftsc *tsc_ref = hyperv_ref_tsc.tsc_ref;
uint32_t seq;
while ((seq = atomic_load_acq_int(&tsc_ref->tsc_seq)) != 0) {
uint64_t disc, ret, tsc;
uint64_t scale = tsc_ref->tsc_scale;
int64_t ofs = tsc_ref->tsc_ofs;
tsc = cpu_counter();
__asm__ __volatile__ ("mulq %3" :
"=d" (ret), "=a" (disc) :
"a" (tsc), "r" (scale));
ret += ofs;
__insn_barrier();
if (tsc_ref->tsc_seq == seq)
return ret;
}
return rdmsr(MSR_HV_TIME_REF_COUNT);
}
static u_int
hyperv_tsc_timecount(struct timecounter *tc __unused)
{
return hyperv_tc64_tsc();
}
static bool
hyperv_tsc_tcinit(void)
{
uint64_t orig_msr, msr;
if ((hyperv_features &
(CPUID_HV_MSR_TIME_REFCNT | CPUID_HV_MSR_REFERENCE_TSC)) !=
(CPUID_HV_MSR_TIME_REFCNT | CPUID_HV_MSR_REFERENCE_TSC) ||
(cpu_feature[0] & CPUID_SSE2) == 0)
return false;
hyperv_ref_tsc.tsc_ref = (void *)uvm_km_alloc(kernel_map,
PAGE_SIZE, PAGE_SIZE, UVM_KMF_WIRED | UVM_KMF_ZERO);
if (hyperv_ref_tsc.tsc_ref == NULL) {
aprint_error("Hyper-V: reference TSC page allocation failed\n");
return false;
}
if (!pmap_extract(pmap_kernel(), (vaddr_t)hyperv_ref_tsc.tsc_ref,
&hyperv_ref_tsc.tsc_paddr)) {
aprint_error("Hyper-V: reference TSC page setup failed\n");
uvm_km_free(kernel_map, (vaddr_t)hyperv_ref_tsc.tsc_ref,
PAGE_SIZE, UVM_KMF_WIRED);
hyperv_ref_tsc.tsc_ref = NULL;
return false;
}
orig_msr = rdmsr(MSR_HV_REFERENCE_TSC);
msr = MSR_HV_REFTSC_ENABLE | (orig_msr & MSR_HV_REFTSC_RSVD_MASK) |
(atop(hyperv_ref_tsc.tsc_paddr) << MSR_HV_REFTSC_PGSHIFT);
wrmsr(MSR_HV_REFERENCE_TSC, msr);
hyperv_tc64 = hyperv_tc64_tsc;
tc_init(&hyperv_tsc_timecounter);
return true;
}
#endif
static void
delay_tc(unsigned int n)
{
struct timecounter *tc;
uint64_t end, now;
u_int last, u;
tc = timecounter;
if (tc->tc_quality <= 0) {
x86_delay(n);
return;
}
now = 0;
end = tc->tc_frequency * n / 1000000;
last = tc->tc_get_timecount(tc) & tc->tc_counter_mask;
do {
x86_pause();
u = tc->tc_get_timecount(tc) & tc->tc_counter_mask;
if (u < last)
now += tc->tc_counter_mask - last + u + 1;
else
now += u - last;
last = u;
} while (now < end);
}
static void
delay_msr(unsigned int n)
{
uint64_t end, now;
u_int last, u;
now = 0;
end = HYPERV_TIMER_FREQ * n / 1000000ULL;
last = (u_int)rdmsr(MSR_HV_TIME_REF_COUNT);
do {
x86_pause();
u = (u_int)rdmsr(MSR_HV_TIME_REF_COUNT);
if (u < last)
now += 0xffffffff - last + u + 1;
else
now += u - last;
last = u;
} while (now < end);
}
static __inline uint64_t
hyperv_hypercall_md(volatile void *hc_addr, uint64_t in_val, uint64_t in_paddr,
uint64_t out_paddr)
{
uint64_t status;
#ifdef __amd64__
__asm__ __volatile__ ("mov %0, %%r8" : : "r" (out_paddr): "r8");
__asm__ __volatile__ ("call *%3" : "=a" (status) : "c" (in_val),
"d" (in_paddr), "m" (hc_addr));
#else
uint32_t in_val_hi = in_val >> 32;
uint32_t in_val_lo = in_val & 0xFFFFFFFF;
uint32_t status_hi, status_lo;
uint32_t in_paddr_hi = in_paddr >> 32;
uint32_t in_paddr_lo = in_paddr & 0xFFFFFFFF;
uint32_t out_paddr_hi = out_paddr >> 32;
uint32_t out_paddr_lo = out_paddr & 0xFFFFFFFF;
__asm__ __volatile__ ("call *%8" : "=d" (status_hi), "=a" (status_lo) :
"d" (in_val_hi), "a" (in_val_lo),
"b" (in_paddr_hi), "c" (in_paddr_lo),
"D" (out_paddr_hi), "S" (out_paddr_lo),
"m" (hc_addr));
status = status_lo | ((uint64_t)status_hi << 32);
#endif
return status;
}
uint64_t
hyperv_hypercall(uint64_t control, paddr_t in_paddr, paddr_t out_paddr)
{
if (hyperv_hypercall_ctx.hc_addr == NULL)
return ~HYPERCALL_STATUS_SUCCESS;
return hyperv_hypercall_md(hyperv_hypercall_ctx.hc_addr, control,
in_paddr, out_paddr);
}
static bool
hyperv_probe(u_int *maxleaf, u_int *features, u_int *pm_features,
u_int *features3)
{
u_int regs[4];
if (vm_guest != VM_GUEST_HV)
return false;
x86_cpuid(CPUID_LEAF_HV_MAXLEAF, regs);
*maxleaf = regs[0];
if (*maxleaf < CPUID_LEAF_HV_LIMITS)
return false;
x86_cpuid(CPUID_LEAF_HV_INTERFACE, regs);
if (regs[0] != CPUID_HV_IFACE_HYPERV)
return false;
x86_cpuid(CPUID_LEAF_HV_FEATURES, regs);
if (!(regs[0] & CPUID_HV_MSR_HYPERCALL)) {
return false;
}
*features = regs[0];
*pm_features = regs[2];
*features3 = regs[3];
return true;
}
static bool
hyperv_identify(void)
{
char buf[256];
u_int regs[4];
u_int maxleaf;
if (!hyperv_probe(&maxleaf, &hyperv_features, &hyperv_pm_features,
&hyperv_features3))
return false;
x86_cpuid(CPUID_LEAF_HV_IDENTITY, regs);
hyperv_ver_major = regs[1] >> 16;
snprintf(hyperv_version_str, sizeof(hyperv_version_str),
"%d.%d.%d [SP%d]",
hyperv_ver_major, regs[1] & 0xffff, regs[0], regs[2]);
aprint_verbose("Hyper-V Version: %s\n", hyperv_version_str);
snprintb(hyperv_features_str, sizeof(hyperv_features_str),
"\020"
"\001VPRUNTIME"
"\002TMREFCNT"
"\003SYNIC"
"\004SYNTM"
"\005APIC"
"\006HYPERCALL"
"\007VPINDEX"
"\010RESET"
"\011STATS"
"\012REFTSC"
"\013IDLE"
"\014TMFREQ"
"\015DEBUG",
hyperv_features);
aprint_verbose(" Features=%s\n", hyperv_features_str);
snprintb(buf, sizeof(buf),
"\020"
"\005C3HPET",
(hyperv_pm_features & ~CPUPM_HV_CSTATE_MASK));
snprintf(hyperv_pm_features_str, sizeof(hyperv_pm_features_str),
"%s [C%u]", buf, CPUPM_HV_CSTATE(hyperv_pm_features));
aprint_verbose(" PM Features=%s\n", hyperv_pm_features_str);
snprintb(hyperv_features3_str, sizeof(hyperv_features3_str),
"\020"
"\001MWAIT"
"\002DEBUG"
"\003PERFMON"
"\004PCPUDPE"
"\005XMMHC"
"\006IDLE"
"\007SLEEP"
"\010NUMA"
"\011TMFREQ"
"\012SYNCMC"
"\013CRASH"
"\014DEBUGMSR"
"\015NPIEP"
"\016HVDIS",
hyperv_features3);
aprint_verbose(" Features3=%s\n", hyperv_features3_str);
x86_cpuid(CPUID_LEAF_HV_RECOMMENDS, regs);
hyperv_recommends = regs[0];
aprint_verbose(" Recommends: %08x %08x\n", regs[0], regs[1]);
x86_cpuid(CPUID_LEAF_HV_LIMITS, regs);
aprint_verbose(" Limits: Vcpu:%d Lcpu:%d Int:%d\n",
regs[0], regs[1], regs[2]);
if (maxleaf >= CPUID_LEAF_HV_HWFEATURES) {
x86_cpuid(CPUID_LEAF_HV_HWFEATURES, regs);
aprint_verbose(" HW Features: %08x, AMD: %08x\n",
regs[0], regs[3]);
}
return true;
}
void
hyperv_early_init(void)
{
u_int features, pm_features, features3;
u_int maxleaf;
int i;
if (!hyperv_probe(&maxleaf, &features, &pm_features, &features3))
return;
if (features & CPUID_HV_MSR_TIME_REFCNT)
x86_delay = delay_func = delay_msr;
if (features & CPUID_HV_MSR_VP_INDEX) {
hyperv_vcpuid[0] = rdmsr(MSR_HV_VP_INDEX);
} else {
hyperv_vcpuid[0] = 0;
}
for (i = 1; i < MAXCPUS; i++)
hyperv_vcpuid[i] = hyperv_vcpuid[0];
}
void
hyperv_init_cpu(struct cpu_info *ci)
{
u_int features, pm_features, features3;
u_int maxleaf;
if (!hyperv_probe(&maxleaf, &features, &pm_features, &features3))
return;
if (features & CPUID_HV_MSR_VP_INDEX)
hyperv_vcpuid[ci->ci_index] = rdmsr(MSR_HV_VP_INDEX);
}
uint32_t
hyperv_get_vcpuid(cpuid_t cpu)
{
if (cpu < MAXCPUS)
return hyperv_vcpuid[cpu];
return 0;
}
static bool
hyperv_init(void)
{
if (!hyperv_identify()) {
if (vm_guest == VM_GUEST_HV)
vm_guest = VM_GUEST_VM;
return false;
}
wrmsr(MSR_HV_GUEST_OS_ID, MSR_HV_GUESTID_OSTYPE_NETBSD |
(uint64_t)__NetBSD_Version__ << MSR_HV_GUESTID_VERSION_SHIFT);
if (hyperv_features & CPUID_HV_MSR_TIME_REFCNT) {
tc_init(&hyperv_timecounter);
hyperv_tc64 = hyperv_tc64_rdmsr;
#ifdef __amd64__
hyperv_tsc_tcinit();
#endif
x86_delay = delay_func = delay_tc;
}
#if NLAPIC > 0
if ((hyperv_features & CPUID_HV_MSR_TIME_FREQ) &&
(hyperv_features3 & CPUID3_HV_TIME_FREQ))
lapic_per_second = rdmsr(MSR_HV_APIC_FREQUENCY);
#endif
return hyperv_init_hypercall();
}
static bool
hyperv_is_initialized(void)
{
uint64_t msr;
if (vm_guest != VM_GUEST_HV)
return false;
if (rdmsr_safe(MSR_HV_HYPERCALL, &msr) == EFAULT)
return false;
return (msr & MSR_HV_HYPERCALL_ENABLE) ? true : false;
}
static int
hyperv_match(device_t parent, cfdata_t cf, void *aux)
{
struct cpufeature_attach_args *cfaa = aux;
struct cpu_info *ci = cfaa->ci;
if (strcmp(cfaa->name, "vm") != 0)
return 0;
if ((ci->ci_flags & (CPUF_BSP|CPUF_SP|CPUF_PRIMARY)) == 0)
return 0;
if (vm_guest != VM_GUEST_HV)
return 0;
return 1;
}
static void
hyperv_attach(device_t parent, device_t self, void *aux)
{
struct hyperv_softc *sc = device_private(self);
sc->sc_dev = self;
aprint_naive("\n");
aprint_normal(": Hyper-V\n");
if (!hyperv_is_initialized()) {
if (rdmsr(MSR_HV_GUEST_OS_ID) == 0) {
if (!hyperv_init()) {
aprint_error_dev(self, "initialize failed\n");
return;
}
}
hyperv_init_hypercall();
}
(void) pmf_device_register(self, NULL, NULL);
(void) hyperv_sysctl_setup_root(sc);
}
static int
hyperv_detach(device_t self, int flags)
{
struct hyperv_softc *sc = device_private(self);
uint64_t hc;
hc = rdmsr(MSR_HV_HYPERCALL);
wrmsr(MSR_HV_HYPERCALL, hc & MSR_HV_HYPERCALL_RSVD_MASK);
hyperv_hypercall_memfree();
if (hyperv_features & CPUID_HV_MSR_TIME_REFCNT)
tc_detach(&hyperv_timecounter);
wrmsr(MSR_HV_GUEST_OS_ID, 0);
pmf_device_deregister(self);
if (sc->sc_log != NULL) {
sysctl_teardown(&sc->sc_log);
sc->sc_log = NULL;
}
return 0;
}
void
hyperv_intr(void)
{
struct cpu_info *ci = curcpu();
(*hyperv_event_proc.func)(hyperv_event_proc.arg, ci);
(*hyperv_message_proc.func)(hyperv_message_proc.arg, ci);
}
void hyperv_hypercall_intr(struct trapframe *);
void
hyperv_hypercall_intr(struct trapframe *frame __unused)
{
struct cpu_info *ci = curcpu();
ci->ci_isources[LIR_HV]->is_evcnt.ev_count++;
hyperv_intr();
}
static void
hyperv_proc_dummy(void *arg __unused, struct cpu_info *ci __unused)
{
}
void
hyperv_set_event_proc(void (*func)(void *, struct cpu_info *), void *arg)
{
hyperv_event_proc.func = func;
hyperv_event_proc.arg = arg;
}
void
hyperv_set_message_proc(void (*func)(void *, struct cpu_info *), void *arg)
{
hyperv_message_proc.func = func;
hyperv_message_proc.arg = arg;
}
static void
hyperv_hypercall_memfree(void)
{
hyperv_hypercall_ctx.hc_addr = NULL;
}
static bool
hyperv_init_hypercall(void)
{
uint64_t hc, hc_orig;
hyperv_hypercall_ctx.hc_addr = hyperv_hypercall_page;
hyperv_hypercall_ctx.hc_paddr = vtophys((vaddr_t)hyperv_hypercall_page);
KASSERT(hyperv_hypercall_ctx.hc_paddr != 0);
hc_orig = rdmsr(MSR_HV_HYPERCALL);
hc = (atop(hyperv_hypercall_ctx.hc_paddr) << 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)) {
aprint_error("Hyper-V: Hypercall setup failed\n");
hyperv_hypercall_memfree();
vm_guest = VM_GUEST_VM;
return false;
}
return true;
}
int
hyperv_hypercall_enabled(void)
{
return hyperv_is_initialized();
}
int
hyperv_synic_supported(void)
{
return (hyperv_features & CPUID_HV_MSR_SYNIC) ? 1 : 0;
}
int
hyperv_is_gen1(void)
{
return !efi_probe();
}
void
hyperv_send_eom(void)
{
wrmsr(MSR_HV_EOM, 0);
}
void
vmbus_init_interrupts_md(struct vmbus_softc *sc, cpuid_t cpu)
{
extern void Xintr_hyperv_hypercall(void);
struct vmbus_percpu_data *pd;
struct hyperv_percpu_data *hv_pd;
struct cpu_info *ci;
struct idt_vec *iv;
int hyperv_idtvec;
cpuid_t cpu0;
cpu0 = cpu_index(&cpu_info_primary);
if (cpu == cpu0 || idt_vec_is_pcpu()) {
ci = cpu_lookup(cpu);
iv = &ci->ci_idtvec;
mutex_enter(&cpu_lock);
hyperv_idtvec = idt_vec_alloc(iv,
APIC_LEVEL(NIPL), IDT_INTR_HIGH);
mutex_exit(&cpu_lock);
KASSERT(hyperv_idtvec > 0);
idt_vec_set(iv, hyperv_idtvec, Xintr_hyperv_hypercall);
} else {
pd = &sc->sc_percpu[cpu0];
hv_pd = pd->md_cookie;
KASSERT(hv_pd != NULL && hv_pd->pd_idtvec > 0);
hyperv_idtvec = hv_pd->pd_idtvec;
}
hv_pd = kmem_zalloc(sizeof(*hv_pd), KM_SLEEP);
hv_pd->pd_idtvec = hyperv_idtvec;
pd = &sc->sc_percpu[cpu];
pd->md_cookie = (void *)hv_pd;
}
void
vmbus_deinit_interrupts_md(struct vmbus_softc *sc, cpuid_t cpu)
{
struct vmbus_percpu_data *pd;
struct hyperv_percpu_data *hv_pd;
struct cpu_info *ci;
struct idt_vec *iv;
pd = &sc->sc_percpu[cpu];
hv_pd = pd->md_cookie;
KASSERT(hv_pd != NULL);
if (cpu == cpu_index(&cpu_info_primary) ||
idt_vec_is_pcpu()) {
ci = cpu_lookup(cpu);
iv = &ci->ci_idtvec;
if (hv_pd->pd_idtvec > 0) {
idt_vec_free(iv, hv_pd->pd_idtvec);
}
}
pd->md_cookie = NULL;
kmem_free(hv_pd, sizeof(*hv_pd));
}
void
vmbus_init_synic_md(struct vmbus_softc *sc, cpuid_t cpu)
{
extern void Xintr_hyperv_hypercall(void);
struct vmbus_percpu_data *pd;
struct hyperv_percpu_data *hv_pd;
uint64_t val, orig;
uint32_t sint;
int hyperv_idtvec;
pd = &sc->sc_percpu[cpu];
hv_pd = pd->md_cookie;
hyperv_idtvec = hv_pd->pd_idtvec;
orig = rdmsr(MSR_HV_SIMP);
val = MSR_HV_SIMP_ENABLE | (orig & MSR_HV_SIMP_RSVD_MASK) |
(atop(hyperv_dma_get_paddr(&pd->simp_dma)) << MSR_HV_SIMP_PGSHIFT);
wrmsr(MSR_HV_SIMP, val);
orig = rdmsr(MSR_HV_SIEFP);
val = MSR_HV_SIEFP_ENABLE | (orig & MSR_HV_SIEFP_RSVD_MASK) |
(atop(hyperv_dma_get_paddr(&pd->siep_dma)) << MSR_HV_SIEFP_PGSHIFT);
wrmsr(MSR_HV_SIEFP, val);
sint = MSR_HV_SINT0 + VMBUS_SINT_MESSAGE;
orig = rdmsr(sint);
val = hyperv_idtvec | MSR_HV_SINT_AUTOEOI |
(orig & MSR_HV_SINT_RSVD_MASK);
wrmsr(sint, val);
sint = MSR_HV_SINT0 + VMBUS_SINT_TIMER;
orig = rdmsr(sint);
val = hyperv_idtvec | MSR_HV_SINT_AUTOEOI |
(orig & MSR_HV_SINT_RSVD_MASK);
wrmsr(sint, val);
orig = rdmsr(MSR_HV_SCONTROL);
val = MSR_HV_SCTRL_ENABLE | (orig & MSR_HV_SCTRL_RSVD_MASK);
wrmsr(MSR_HV_SCONTROL, val);
}
void
vmbus_deinit_synic_md(struct vmbus_softc *sc, cpuid_t cpu)
{
uint64_t orig;
uint32_t sint;
orig = rdmsr(MSR_HV_SCONTROL);
wrmsr(MSR_HV_SCONTROL, (orig & MSR_HV_SCTRL_RSVD_MASK));
sint = MSR_HV_SINT0 + VMBUS_SINT_MESSAGE;
orig = rdmsr(sint);
wrmsr(sint, orig | MSR_HV_SINT_MASKED);
sint = MSR_HV_SINT0 + VMBUS_SINT_TIMER;
orig = rdmsr(sint);
wrmsr(sint, orig | MSR_HV_SINT_MASKED);
orig = rdmsr(MSR_HV_SIMP);
wrmsr(MSR_HV_SIMP, (orig & MSR_HV_SIMP_RSVD_MASK));
orig = rdmsr(MSR_HV_SIEFP);
wrmsr(MSR_HV_SIEFP, (orig & MSR_HV_SIEFP_RSVD_MASK));
}
static int
hyperv_sysctl_setup(struct hyperv_softc *sc,
const struct sysctlnode *hyperv_node)
{
int error;
error = sysctl_createv(&sc->sc_log, 0, &hyperv_node, NULL,
CTLFLAG_READONLY, CTLTYPE_STRING, "version", NULL,
NULL, 0, hyperv_version_str,
0, CTL_CREATE, CTL_EOL);
if (error)
return error;
error = sysctl_createv(&sc->sc_log, 0, &hyperv_node, NULL,
CTLFLAG_READONLY, CTLTYPE_STRING, "features", NULL,
NULL, 0, hyperv_features_str,
0, CTL_CREATE, CTL_EOL);
if (error)
return error;
error = sysctl_createv(&sc->sc_log, 0, &hyperv_node, NULL,
CTLFLAG_READONLY, CTLTYPE_STRING, "pm_features", NULL,
NULL, 0, hyperv_pm_features_str,
0, CTL_CREATE, CTL_EOL);
if (error)
return error;
error = sysctl_createv(&sc->sc_log, 0, &hyperv_node, NULL,
CTLFLAG_READONLY, CTLTYPE_STRING, "features3", NULL,
NULL, 0, hyperv_features3_str,
0, CTL_CREATE, CTL_EOL);
if (error)
return error;
return 0;
}
static int
hyperv_sysctl_setup_root(struct hyperv_softc *sc)
{
const struct sysctlnode *machdep_node, *hyperv_node;
int error;
error = sysctl_createv(&sc->sc_log, 0, NULL, &machdep_node,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
if (error)
goto fail;
error = sysctl_createv(&sc->sc_log, 0, &machdep_node, &hyperv_node,
CTLFLAG_PERMANENT, CTLTYPE_NODE, "hyperv", NULL,
NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
if (error)
goto fail;
error = hyperv_sysctl_setup(sc, hyperv_node);
if (error)
goto fail;
return 0;
fail:
sysctl_teardown(&sc->sc_log);
sc->sc_log = NULL;
return error;
}
MODULE(MODULE_CLASS_DRIVER, hyperv, NULL);
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
hyperv_modcmd(modcmd_t cmd, void *aux)
{
int rv = 0;
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
rv = config_init_component(cfdriver_ioconf_hyperv,
cfattach_ioconf_hyperv, cfdata_ioconf_hyperv);
#endif
hyperv_init();
break;
case MODULE_CMD_FINI:
#ifdef _MODULE
rv = config_fini_component(cfdriver_ioconf_hyperv,
cfattach_ioconf_hyperv, cfdata_ioconf_hyperv);
#endif
break;
default:
rv = ENOTTY;
break;
}
return rv;
}
#if NVMBUS > 0
static struct genfb_pmf_callback pmf_cb;
static struct genfb_mode_callback mode_cb;
static bool
x86_genfb_setmode(struct genfb_softc *sc, int newmode)
{
return true;
}
static bool
x86_genfb_suspend(device_t dev, const pmf_qual_t *qual)
{
return true;
}
static bool
x86_genfb_resume(device_t dev, const pmf_qual_t *qual)
{
#if NGENFB > 0
struct genfb_vmbus_softc *sc = device_private(dev);
genfb_restore_palette(&sc->sc_gen);
#endif
return true;
}
static void
populate_fbinfo(device_t dev, prop_dictionary_t dict)
{
#if NWSDISPLAY > 0 && NGENFB > 0
struct rasops_info *ri = &x86_genfb_console_screen.scr_ri;
#endif
const void *fbptr = lookup_bootinfo(BTINFO_FRAMEBUFFER);
struct btinfo_framebuffer fbinfo;
if (fbptr == NULL)
return;
memcpy(&fbinfo, fbptr, sizeof(fbinfo));
if (fbinfo.physaddr != 0) {
prop_dictionary_set_uint32(dict, "width", fbinfo.width);
prop_dictionary_set_uint32(dict, "height", fbinfo.height);
prop_dictionary_set_uint8(dict, "depth", fbinfo.depth);
prop_dictionary_set_uint16(dict, "linebytes", fbinfo.stride);
prop_dictionary_set_uint64(dict, "address", fbinfo.physaddr);
#if NWSDISPLAY > 0 && NGENFB > 0
if (ri->ri_bits != NULL) {
prop_dictionary_set_uint64(dict, "virtual_address",
ri->ri_hwbits != NULL ?
(vaddr_t)ri->ri_hworigbits :
(vaddr_t)ri->ri_origbits);
}
#endif
}
#if notyet
prop_dictionary_set_bool(dict, "splash",
(fbinfo.flags & BI_FB_SPLASH) != 0);
#endif
#if 0
if (fbinfo.depth == 8) {
gfb_cb.gcc_cookie = NULL;
gfb_cb.gcc_set_mapreg = x86_genfb_set_mapreg;
prop_dictionary_set_uint64(dict, "cmap_callback",
(uint64_t)(uintptr_t)&gfb_cb);
}
#endif
if (fbinfo.physaddr != 0) {
mode_cb.gmc_setmode = x86_genfb_setmode;
prop_dictionary_set_uint64(dict, "mode_callback",
(uint64_t)(uintptr_t)&mode_cb);
}
#if NWSDISPLAY > 0 && NGENFB > 0
if (device_is_a(dev, "genfb")) {
prop_dictionary_set_bool(dict, "enable_shadowfb",
ri->ri_hwbits != NULL);
x86_genfb_set_console_dev(dev);
#ifdef DDB
db_trap_callback = x86_genfb_ddb_trap_callback;
#endif
}
#endif
}
#endif
device_t
device_hyperv_register(device_t dev, void *aux)
{
#if NVMBUS > 0
device_t parent = device_parent(dev);
if (parent && device_is_a(parent, "vmbus") && !x86_found_console) {
struct vmbus_attach_args *aa = aux;
if (memcmp(aa->aa_type, &hyperv_guid_video,
sizeof(*aa->aa_type)) == 0) {
prop_dictionary_t dict = device_properties(dev);
x86_genfb_init();
populate_fbinfo(dev, dict);
#if 1 && NWSDISPLAY > 0 && NGENFB > 0
if (device_is_a(dev, "genfb")) {
prop_dictionary_set_bool(dict, "is_console",
genfb_is_console());
} else
#endif
prop_dictionary_set_bool(dict, "is_console", true);
prop_dictionary_set_bool(dict, "clear-screen", false);
#if NWSDISPLAY > 0 && NGENFB > 0
prop_dictionary_set_uint16(dict, "cursor-row",
x86_genfb_console_screen.scr_ri.ri_crow);
#endif
pmf_cb.gpc_suspend = x86_genfb_suspend;
pmf_cb.gpc_resume = x86_genfb_resume;
prop_dictionary_set_uint64(dict, "pmf_callback",
(uint64_t)(uintptr_t)&pmf_cb);
x86_found_console = true;
return NULL;
}
}
#endif
return NULL;
}