#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/systimer.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <machine/intr_machdep.h>
#include <machine/smp.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>
#include <dev/virtual/hyperv/vmbus/vmbus_reg.h>
#include <dev/virtual/hyperv/vmbus/vmbus_var.h>
#include "acpi.h"
#include "acpi_if.h"
#include "pcib_if.h"
#define MSR_HV_STIMER0_CFG_SINT \
((((uint64_t)VMBUS_SINT_TIMER) << MSR_HV_STIMER_CFG_SINT_SHIFT) & \
MSR_HV_STIMER_CFG_SINT_MASK)
#define CPUID_HV_TIMER_MASK (CPUID_HV_MSR_SYNIC | \
CPUID_HV_MSR_SYNTIMER)
#define VMBUS_SINT_MESSAGE 2
#define VMBUS_SINT_TIMER 4
#define VMBUS_CONNID_MESSAGE 1
#define VMBUS_CONNID_EVENT 2
struct vmbus_msghc {
struct hypercall_postmsg_in *mh_inprm;
struct hypercall_postmsg_in mh_inprm_save;
struct hyperv_dma mh_inprm_dma;
struct vmbus_message *mh_resp;
struct vmbus_message mh_resp0;
};
struct vmbus_msghc_ctx {
struct vmbus_msghc *mhc_free;
struct lwkt_token mhc_free_token;
uint32_t mhc_flags;
struct vmbus_msghc *mhc_active;
struct lwkt_token mhc_active_token;
};
#define VMBUS_MSGHC_CTXF_DESTROY 0x0001
static int vmbus_probe(device_t);
static int vmbus_attach(device_t);
static int vmbus_detach(device_t);
static void vmbus_intr(void *);
static void vmbus_timer_intr_reload(struct cputimer_intr *,
sysclock_t);
static void vmbus_timer_intr_pcpuhand(
struct cputimer_intr *);
static void vmbus_timer_intr_restart(
struct cputimer_intr *);
static int vmbus_dma_alloc(struct vmbus_softc *);
static void vmbus_dma_free(struct vmbus_softc *);
static int vmbus_intr_setup(struct vmbus_softc *);
static void vmbus_intr_teardown(struct vmbus_softc *);
static void vmbus_synic_setup(void *);
static void vmbus_synic_teardown(void *);
static void vmbus_timer_stop(void *);
static void vmbus_timer_config(void *);
static int vmbus_init(struct vmbus_softc *);
static int vmbus_init_contact(struct vmbus_softc *,
uint32_t);
static void vmbus_timer_restart(void *);
static void vmbus_timer_msgintr(struct vmbus_pcpu_data *);
static void vmbus_chan_msgproc(struct vmbus_softc *,
const struct vmbus_message *);
static struct vmbus_msghc_ctx *vmbus_msghc_ctx_create(bus_dma_tag_t);
static void vmbus_msghc_ctx_destroy(
struct vmbus_msghc_ctx *);
static void vmbus_msghc_ctx_free(struct vmbus_msghc_ctx *);
static struct vmbus_msghc *vmbus_msghc_alloc(bus_dma_tag_t);
static void vmbus_msghc_free(struct vmbus_msghc *);
static struct vmbus_msghc *vmbus_msghc_get1(struct vmbus_msghc_ctx *,
uint32_t);
static device_method_t vmbus_methods[] = {
DEVMETHOD(device_probe, vmbus_probe),
DEVMETHOD(device_attach, vmbus_attach),
DEVMETHOD(device_detach, vmbus_detach),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD_END
};
static driver_t vmbus_driver = {
"vmbus",
vmbus_methods,
sizeof(struct vmbus_softc)
};
static devclass_t vmbus_devclass;
DRIVER_MODULE(vmbus, acpi, vmbus_driver, vmbus_devclass, NULL, NULL);
MODULE_DEPEND(vmbus, acpi, 1, 1, 1);
MODULE_VERSION(vmbus, 1);
static struct cputimer_intr vmbus_cputimer_intr = {
.freq = HYPERV_TIMER_FREQ,
.reload = vmbus_timer_intr_reload,
.enable = cputimer_intr_default_enable,
.config = cputimer_intr_default_config,
.restart = vmbus_timer_intr_restart,
.pmfixup = cputimer_intr_default_pmfixup,
.initclock = cputimer_intr_default_initclock,
.pcpuhand = vmbus_timer_intr_pcpuhand,
.next = SLIST_ENTRY_INITIALIZER,
.name = "hyperv",
.type = CPUTIMER_INTR_VMM,
.prio = CPUTIMER_INTR_PRIO_VMM,
.caps = CPUTIMER_INTR_CAP_PS,
.priv = NULL
};
static const uint32_t vmbus_version[] = {
VMBUS_VERSION_WIN8_1,
VMBUS_VERSION_WIN8,
VMBUS_VERSION_WIN7,
VMBUS_VERSION_WS2008
};
static int vmbus_timer_intr_enable = 1;
TUNABLE_INT("hw.vmbus.timer_intr.enable", &vmbus_timer_intr_enable);
static int
vmbus_probe(device_t dev)
{
char *id[] = { "VMBUS", NULL };
if (ACPI_ID_PROBE(device_get_parent(dev), dev, id) == NULL ||
device_get_unit(dev) != 0 || vmm_guest != VMM_GUEST_HYPERV ||
(hyperv_features & CPUID_HV_MSR_SYNIC) == 0)
return (ENXIO);
device_set_desc(dev, "Hyper-V vmbus");
return (0);
}
static int
vmbus_attach(device_t dev)
{
struct vmbus_softc *sc = device_get_softc(dev);
int error, cpu, use_timer;
sc->vmbus_dev = dev;
for (cpu = 0; cpu < ncpus; ++cpu) {
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, cpu);
psc->sc = sc;
psc->cpuid = cpu;
psc->timer_last = UINT64_MAX;
}
use_timer = 0;
if (device_get_unit(dev) == 0 &&
(hyperv_features & CPUID_HV_TIMER_MASK) == CPUID_HV_TIMER_MASK &&
hyperv_tc64 != NULL)
use_timer = 1;
sc->vmbus_msg_hc = vmbus_msghc_ctx_create(
bus_get_dma_tag(sc->vmbus_dev));
if (sc->vmbus_msg_hc == NULL)
return ENXIO;
error = vmbus_dma_alloc(sc);
if (error)
goto failed;
error = vmbus_intr_setup(sc);
if (error)
goto failed;
if (use_timer) {
lwkt_cpusync_simple(smp_active_mask, vmbus_timer_stop, sc);
}
lwkt_cpusync_simple(smp_active_mask, vmbus_synic_setup, sc);
sc->vmbus_flags |= VMBUS_FLAG_SYNIC;
error = vmbus_init(sc);
if (error)
goto failed;
if (use_timer) {
lwkt_cpusync_simple(smp_active_mask, vmbus_timer_config, sc);
vmbus_cputimer_intr.priv = sc;
cputimer_intr_register(&vmbus_cputimer_intr);
if (vmbus_timer_intr_enable)
cputimer_intr_select(&vmbus_cputimer_intr, 0);
}
return 0;
failed:
vmbus_detach(dev);
return error;
}
static int
vmbus_detach(device_t dev)
{
struct vmbus_softc *sc = device_get_softc(dev);
if (sc->vmbus_flags & VMBUS_FLAG_SYNIC)
lwkt_cpusync_simple(smp_active_mask, vmbus_synic_teardown, sc);
vmbus_intr_teardown(sc);
vmbus_dma_free(sc);
if (sc->vmbus_msg_hc != NULL) {
vmbus_msghc_ctx_destroy(sc->vmbus_msg_hc);
sc->vmbus_msg_hc = NULL;
}
return (0);
}
static __inline void
vmbus_msg_reset(volatile struct vmbus_message *msg)
{
msg->msg_type = HYPERV_MSGTYPE_NONE;
cpu_mfence();
if (msg->msg_flags & VMBUS_MSGFLAG_PENDING) {
wrmsr(MSR_HV_EOM, 0);
}
}
static void
vmbus_intr(void *xpsc)
{
struct vmbus_pcpu_data *psc = xpsc;
volatile struct vmbus_message *msg;
msg = psc->message + VMBUS_SINT_MESSAGE;
while (__predict_false(msg->msg_type != HYPERV_MSGTYPE_NONE)) {
if (msg->msg_type == HYPERV_MSGTYPE_CHANNEL) {
vmbus_chan_msgproc(psc->sc,
__DEVOLATILE(const struct vmbus_message *, msg));
}
vmbus_msg_reset(msg);
}
}
static __inline void
vmbus_timer_oneshot(struct vmbus_pcpu_data *psc, uint64_t current)
{
psc->timer_last = current;
wrmsr(MSR_HV_STIMER0_COUNT, current);
}
static void
vmbus_timer_intr_reload(struct cputimer_intr *cti, sysclock_t reload)
{
struct globaldata *gd = mycpu;
struct vmbus_softc *sc = cti->priv;
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, gd->gd_cpuid);
uint64_t current;
if ((ssysclock_t)reload < 0)
reload = 1;
reload = muldivu64(reload, cti->freq, sys_cputimer->freq);
current = hyperv_tc64() + reload;
if (gd->gd_timer_running) {
if (current < psc->timer_last)
vmbus_timer_oneshot(psc, current);
} else {
gd->gd_timer_running = 1;
vmbus_timer_oneshot(psc, current);
}
}
static void
vmbus_timer_intr_pcpuhand(struct cputimer_intr *cti)
{
struct vmbus_softc *sc = cti->priv;
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, mycpuid);
vmbus_timer_msgintr(psc);
}
static void
vmbus_timer_intr_restart(struct cputimer_intr *cti)
{
lwkt_send_ipiq_mask(smp_active_mask, vmbus_timer_restart, cti->priv);
}
static struct vmbus_msghc *
vmbus_msghc_alloc(bus_dma_tag_t parent_dtag)
{
struct vmbus_msghc *mh;
mh = kmalloc(sizeof(*mh), M_DEVBUF, M_WAITOK | M_ZERO);
mh->mh_inprm = hyperv_dmamem_alloc(parent_dtag,
HYPERCALL_POSTMSGIN_ALIGN, 0, HYPERCALL_POSTMSGIN_SIZE,
&mh->mh_inprm_dma, BUS_DMA_WAITOK);
if (mh->mh_inprm == NULL) {
kfree(mh, M_DEVBUF);
return NULL;
}
return mh;
}
static void
vmbus_msghc_free(struct vmbus_msghc *mh)
{
hyperv_dmamem_free(&mh->mh_inprm_dma, mh->mh_inprm);
kfree(mh, M_DEVBUF);
}
static void
vmbus_msghc_ctx_free(struct vmbus_msghc_ctx *mhc)
{
KASSERT(mhc->mhc_active == NULL, ("still have active msg hypercall"));
KASSERT(mhc->mhc_free == NULL, ("still have hypercall msg"));
lwkt_token_uninit(&mhc->mhc_free_token);
lwkt_token_uninit(&mhc->mhc_active_token);
kfree(mhc, M_DEVBUF);
}
static struct vmbus_msghc_ctx *
vmbus_msghc_ctx_create(bus_dma_tag_t parent_dtag)
{
struct vmbus_msghc_ctx *mhc;
mhc = kmalloc(sizeof(*mhc), M_DEVBUF, M_WAITOK | M_ZERO);
lwkt_token_init(&mhc->mhc_free_token, "msghcf");
lwkt_token_init(&mhc->mhc_active_token, "msghca");
mhc->mhc_free = vmbus_msghc_alloc(parent_dtag);
if (mhc->mhc_free == NULL) {
vmbus_msghc_ctx_free(mhc);
return NULL;
}
return mhc;
}
static struct vmbus_msghc *
vmbus_msghc_get1(struct vmbus_msghc_ctx *mhc, uint32_t dtor_flag)
{
struct vmbus_msghc *mh;
lwkt_gettoken(&mhc->mhc_free_token);
while ((mhc->mhc_flags & dtor_flag) == 0 && mhc->mhc_free == NULL)
tsleep(&mhc->mhc_free, 0, "gmsghc", 0);
if (mhc->mhc_flags & dtor_flag) {
mh = NULL;
} else {
mh = mhc->mhc_free;
KASSERT(mh != NULL, ("no free hypercall msg"));
KASSERT(mh->mh_resp == NULL,
("hypercall msg has pending response"));
mhc->mhc_free = NULL;
}
lwkt_reltoken(&mhc->mhc_free_token);
return mh;
}
struct vmbus_msghc *
vmbus_msghc_get(struct vmbus_softc *sc, size_t dsize)
{
struct hypercall_postmsg_in *inprm;
struct vmbus_msghc *mh;
if (dsize > HYPERCALL_POSTMSGIN_DSIZE_MAX)
return NULL;
mh = vmbus_msghc_get1(sc->vmbus_msg_hc, VMBUS_MSGHC_CTXF_DESTROY);
if (mh == NULL)
return NULL;
inprm = mh->mh_inprm;
memset(inprm, 0, HYPERCALL_POSTMSGIN_SIZE);
inprm->hc_connid = VMBUS_CONNID_MESSAGE;
inprm->hc_msgtype = HYPERV_MSGTYPE_CHANNEL;
inprm->hc_dsize = dsize;
return mh;
}
void
vmbus_msghc_put(struct vmbus_softc *sc, struct vmbus_msghc *mh)
{
struct vmbus_msghc_ctx *mhc = sc->vmbus_msg_hc;
KASSERT(mhc->mhc_active == NULL, ("msg hypercall is active"));
mh->mh_resp = NULL;
lwkt_gettoken(&mhc->mhc_free_token);
KASSERT(mhc->mhc_free == NULL, ("has free hypercall msg"));
mhc->mhc_free = mh;
lwkt_reltoken(&mhc->mhc_free_token);
wakeup(&mhc->mhc_free);
}
void *
vmbus_msghc_dataptr(struct vmbus_msghc *mh)
{
return mh->mh_inprm->hc_data;
}
static void
vmbus_msghc_ctx_destroy(struct vmbus_msghc_ctx *mhc)
{
struct vmbus_msghc *mh;
lwkt_gettoken(&mhc->mhc_free_token);
mhc->mhc_flags |= VMBUS_MSGHC_CTXF_DESTROY;
lwkt_reltoken(&mhc->mhc_free_token);
wakeup(&mhc->mhc_free);
mh = vmbus_msghc_get1(mhc, 0);
if (mh == NULL)
panic("can't get msghc");
vmbus_msghc_free(mh);
vmbus_msghc_ctx_free(mhc);
}
int
vmbus_msghc_exec_noresult(struct vmbus_msghc *mh)
{
int i, wait_ticks = 1;
memcpy(&mh->mh_inprm_save, mh->mh_inprm, HYPERCALL_POSTMSGIN_SIZE);
#define HC_RETRY_MAX 20
for (i = 0; i < HC_RETRY_MAX; ++i) {
uint64_t status;
status = hypercall_post_message(mh->mh_inprm_dma.hv_paddr);
if (status == HYPERCALL_STATUS_SUCCESS)
return 0;
tsleep(&status, 0, "hcpmsg", wait_ticks);
if (wait_ticks < hz)
wait_ticks *= 2;
memcpy(mh->mh_inprm, &mh->mh_inprm_save,
HYPERCALL_POSTMSGIN_SIZE);
}
#undef HC_RETRY_MAX
return EIO;
}
int
vmbus_msghc_exec(struct vmbus_softc *sc, struct vmbus_msghc *mh)
{
struct vmbus_msghc_ctx *mhc = sc->vmbus_msg_hc;
int error;
KASSERT(mh->mh_resp == NULL, ("hypercall msg has pending response"));
lwkt_gettoken(&mhc->mhc_active_token);
KASSERT(mhc->mhc_active == NULL, ("pending active msg hypercall"));
mhc->mhc_active = mh;
lwkt_reltoken(&mhc->mhc_active_token);
error = vmbus_msghc_exec_noresult(mh);
if (error) {
lwkt_gettoken(&mhc->mhc_active_token);
KASSERT(mhc->mhc_active == mh, ("msghc mismatch"));
mhc->mhc_active = NULL;
lwkt_reltoken(&mhc->mhc_active_token);
}
return error;
}
const struct vmbus_message *
vmbus_msghc_wait_result(struct vmbus_softc *sc, struct vmbus_msghc *mh)
{
struct vmbus_msghc_ctx *mhc = sc->vmbus_msg_hc;
lwkt_gettoken(&mhc->mhc_active_token);
KASSERT(mhc->mhc_active == mh, ("msghc mismatch"));
while (mh->mh_resp == NULL)
tsleep(&mhc->mhc_active, 0, "wmsghc", 0);
mhc->mhc_active = NULL;
lwkt_reltoken(&mhc->mhc_active_token);
return mh->mh_resp;
}
void
vmbus_msghc_wakeup(struct vmbus_softc *sc, const struct vmbus_message *msg)
{
struct vmbus_msghc_ctx *mhc = sc->vmbus_msg_hc;
struct vmbus_msghc *mh;
lwkt_gettoken(&mhc->mhc_active_token);
mh = mhc->mhc_active;
KASSERT(mh != NULL, ("no pending msg hypercall"));
memcpy(&mh->mh_resp0, msg, sizeof(mh->mh_resp0));
mh->mh_resp = &mh->mh_resp0;
lwkt_reltoken(&mhc->mhc_active_token);
wakeup(&mhc->mhc_active);
}
static int
vmbus_dma_alloc(struct vmbus_softc *sc)
{
bus_dma_tag_t parent_dtag;
uint8_t *evtflags;
int cpu;
parent_dtag = bus_get_dma_tag(sc->vmbus_dev);
for (cpu = 0; cpu < ncpus; ++cpu) {
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, cpu);
psc->message = hyperv_dmamem_alloc(parent_dtag,
PAGE_SIZE, 0, PAGE_SIZE, &psc->message_dma,
BUS_DMA_WAITOK | BUS_DMA_ZERO);
if (psc->message == NULL)
return ENOMEM;
psc->event_flags = hyperv_dmamem_alloc(parent_dtag,
PAGE_SIZE, 0, PAGE_SIZE, &psc->event_flags_dma,
BUS_DMA_WAITOK | BUS_DMA_ZERO);
if (psc->event_flags == NULL)
return ENOMEM;
}
evtflags = hyperv_dmamem_alloc(parent_dtag, PAGE_SIZE, 0,
PAGE_SIZE, &sc->vmbus_evtflags_dma, BUS_DMA_WAITOK | BUS_DMA_ZERO);
if (evtflags == NULL)
return ENOMEM;
sc->vmbus_rx_evtflags = (u_long *)evtflags;
sc->vmbus_tx_evtflags = (u_long *)(evtflags + (PAGE_SIZE / 2));
sc->vmbus_evtflags = evtflags;
sc->vmbus_mnf1 = hyperv_dmamem_alloc(parent_dtag, PAGE_SIZE, 0,
PAGE_SIZE, &sc->vmbus_mnf1_dma, BUS_DMA_WAITOK | BUS_DMA_ZERO);
if (sc->vmbus_mnf1 == NULL)
return ENOMEM;
sc->vmbus_mnf2 = hyperv_dmamem_alloc(parent_dtag, PAGE_SIZE, 0,
PAGE_SIZE, &sc->vmbus_mnf2_dma, BUS_DMA_WAITOK | BUS_DMA_ZERO);
if (sc->vmbus_mnf2 == NULL)
return ENOMEM;
return 0;
}
static void
vmbus_dma_free(struct vmbus_softc *sc)
{
int cpu;
if (sc->vmbus_evtflags != NULL) {
hyperv_dmamem_free(&sc->vmbus_evtflags_dma, sc->vmbus_evtflags);
sc->vmbus_evtflags = NULL;
sc->vmbus_rx_evtflags = NULL;
sc->vmbus_tx_evtflags = NULL;
}
if (sc->vmbus_mnf1 != NULL) {
hyperv_dmamem_free(&sc->vmbus_mnf1_dma, sc->vmbus_mnf1);
sc->vmbus_mnf1 = NULL;
}
if (sc->vmbus_mnf2 != NULL) {
hyperv_dmamem_free(&sc->vmbus_mnf2_dma, sc->vmbus_mnf2);
sc->vmbus_mnf2 = NULL;
}
for (cpu = 0; cpu < ncpus; ++cpu) {
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, cpu);
if (psc->message != NULL) {
hyperv_dmamem_free(&psc->message_dma, psc->message);
psc->message = NULL;
}
if (psc->event_flags != NULL) {
hyperv_dmamem_free(&psc->event_flags_dma,
psc->event_flags);
psc->event_flags = NULL;
}
}
}
static int
vmbus_intr_setup(struct vmbus_softc *sc)
{
device_t dev = sc->vmbus_dev;
device_t bus = device_get_parent(device_get_parent(dev));
int rid, cpu;
rid = 0;
for (cpu = 0; cpu < ncpus; ++cpu) {
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, cpu);
uint64_t msi_addr;
uint32_t msi_data;
int error;
error = PCIB_ALLOC_MSIX(bus, dev, &psc->intr_irq, cpu);
if (error) {
device_printf(dev, "alloc vector on cpu%d failed: %d\n",
cpu, error);
return ENXIO;
}
psc->intr_rid = ++rid;
psc->intr_res = BUS_ALLOC_RESOURCE(bus, dev, SYS_RES_IRQ,
&psc->intr_rid, psc->intr_irq, psc->intr_irq, 1,
RF_ACTIVE, cpu);
if (psc->intr_res == NULL) {
device_printf(dev, "alloc irq on cpu%d failed: %d\n",
cpu, error);
return ENXIO;
}
error = PCIB_MAP_MSI(bus, dev, rman_get_start(psc->intr_res),
&msi_addr, &msi_data, cpu);
if (error) {
device_printf(dev, "map irq on cpu%d failed: %d\n",
cpu, error);
return ENXIO;
}
psc->intr_vec = hyperv_msi2vector(msi_addr, msi_data);
if (bootverbose) {
device_printf(dev, "vector %d irq %d on cpu%d\n",
psc->intr_vec, psc->intr_irq, cpu);
}
ksnprintf(psc->intr_desc, sizeof(psc->intr_desc), "%s cpu%d",
device_get_nameunit(dev), cpu);
error = bus_setup_intr_descr(dev, psc->intr_res, INTR_MPSAFE,
vmbus_intr, psc, &psc->intr_hand, NULL, psc->intr_desc);
if (error) {
device_printf(dev, "setup intr on cpu%d failed: %d\n",
cpu, error);
return ENXIO;
}
}
return 0;
}
static void
vmbus_intr_teardown(struct vmbus_softc *sc)
{
device_t dev = sc->vmbus_dev;
device_t bus = device_get_parent(device_get_parent(dev));
int cpu;
for (cpu = 0; cpu < ncpus; ++cpu) {
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, cpu);
if (psc->intr_hand != NULL) {
bus_teardown_intr(dev, psc->intr_res, psc->intr_hand);
psc->intr_hand = NULL;
}
if (psc->intr_res != NULL) {
BUS_RELEASE_RESOURCE(bus, dev, SYS_RES_IRQ,
psc->intr_rid, psc->intr_res);
psc->intr_res = NULL;
}
if (psc->intr_rid != 0) {
PCIB_RELEASE_MSIX(bus, dev, psc->intr_irq, psc->cpuid);
psc->intr_rid = 0;
}
}
}
static void
vmbus_synic_setup(void *xsc)
{
struct vmbus_softc *sc = xsc;
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, mycpuid);
uint64_t val, orig;
uint32_t sint;
if (hyperv_features & CPUID_HV_MSR_VP_INDEX) {
psc->vcpuid = rdmsr(MSR_HV_VP_INDEX);
} else {
psc->vcpuid = mycpuid;
}
orig = rdmsr(MSR_HV_SIMP);
val = MSR_HV_SIMP_ENABLE | (orig & MSR_HV_SIMP_RSVD_MASK) |
((psc->message_dma.hv_paddr >> PAGE_SHIFT) << 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) |
((psc->event_flags_dma.hv_paddr >> PAGE_SHIFT) <<
MSR_HV_SIEFP_PGSHIFT);
wrmsr(MSR_HV_SIEFP, val);
sint = MSR_HV_SINT0 + VMBUS_SINT_MESSAGE;
orig = rdmsr(sint);
val = psc->intr_vec |
(orig & MSR_HV_SINT_RSVD_MASK);
wrmsr(sint, val);
sint = MSR_HV_SINT0 + VMBUS_SINT_TIMER;
orig = rdmsr(sint);
val = XTIMER_OFFSET |
(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);
}
static void
vmbus_timer_stop(void *arg __unused)
{
for (;;) {
uint64_t val;
wrmsr(MSR_HV_STIMER0_COUNT, 0);
val = rdmsr(MSR_HV_STIMER0_CONFIG);
if ((val & MSR_HV_STIMER_CFG_ENABLE) == 0)
break;
cpu_pause();
}
}
static void
vmbus_timer_config(void *arg __unused)
{
vmbus_timer_stop(arg);
wrmsr(MSR_HV_STIMER0_CONFIG,
MSR_HV_STIMER_CFG_AUTOEN | MSR_HV_STIMER0_CFG_SINT);
}
static void
vmbus_timer_msgintr(struct vmbus_pcpu_data *psc)
{
volatile struct vmbus_message *msg;
msg = psc->message + VMBUS_SINT_TIMER;
if (msg->msg_type == HYPERV_MSGTYPE_TIMER_EXPIRED)
vmbus_msg_reset(msg);
}
static void
vmbus_timer_restart(void *xsc)
{
struct vmbus_softc *sc = xsc;
struct vmbus_pcpu_data *psc = VMBUS_PCPU(sc, mycpuid);
crit_enter();
vmbus_timer_msgintr(psc);
vmbus_timer_oneshot(psc, hyperv_tc64() + 1);
crit_exit();
}
static void
vmbus_synic_teardown(void *arg __unused)
{
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
vmbus_init_contact(struct vmbus_softc *sc, uint32_t version)
{
struct vmbus_chanmsg_init_contact *req;
const struct vmbus_chanmsg_version_resp *resp;
const struct vmbus_message *msg;
struct vmbus_msghc *mh;
int error, supp = 0;
mh = vmbus_msghc_get(sc, sizeof(*req));
if (mh == NULL)
return ENXIO;
req = vmbus_msghc_dataptr(mh);
req->chm_hdr.chm_type = VMBUS_CHANMSG_TYPE_INIT_CONTACT;
req->chm_ver = version;
req->chm_evtflags = sc->vmbus_evtflags_dma.hv_paddr;
req->chm_mnf1 = sc->vmbus_mnf1_dma.hv_paddr;
req->chm_mnf2 = sc->vmbus_mnf2_dma.hv_paddr;
error = vmbus_msghc_exec(sc, mh);
if (error) {
vmbus_msghc_put(sc, mh);
return error;
}
msg = vmbus_msghc_wait_result(sc, mh);
resp = (const struct vmbus_chanmsg_version_resp *)msg->msg_data;
supp = resp->chm_supp;
vmbus_msghc_put(sc, mh);
return (supp ? 0 : EOPNOTSUPP);
}
static int
vmbus_init(struct vmbus_softc *sc)
{
int i;
for (i = 0; i < nitems(vmbus_version); ++i) {
int error;
error = vmbus_init_contact(sc, vmbus_version[i]);
if (!error) {
sc->vmbus_version = vmbus_version[i];
device_printf(sc->vmbus_dev, "version %u.%u\n",
(sc->vmbus_version >> 16),
(sc->vmbus_version & 0xffff));
return 0;
}
}
return ENXIO;
}
static void
vmbus_chan_msgproc(struct vmbus_softc *sc, const struct vmbus_message *msg)
{
const struct vmbus_chanmsg_hdr *hdr;
hdr = (const struct vmbus_chanmsg_hdr *)msg->msg_data;
if (hdr->chm_type == VMBUS_CHANMSG_TYPE_VERSION_RESP)
vmbus_msghc_wakeup(sc, msg);
}