root/sys/dev/virtual/hyperv/x86_64/hyperv_machdep.c
/*-
 * Copyright (c) 2016 Microsoft Corp.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/systimer.h>
#include <sys/systm.h>

#include <machine/cpufunc.h>
#include <machine/specialreg.h>
#include <machine/msi_machdep.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>

struct hyperv_reftsc_ctx {
        struct hyperv_reftsc    *tsc_ref;
        struct hyperv_dma       tsc_ref_dma;
};

static void             hyperv_tsc_cputimer_construct(struct cputimer *,
                            sysclock_t);
static sysclock_t       hyperv_tsc_cputimer_count_mfence(void);
static sysclock_t       hyperv_tsc_cputimer_count_lfence(void);

static struct hyperv_reftsc_ctx hyperv_ref_tsc;
static hyperv_tc64_t    hyperv_tc64_saved;

static struct cputimer  hyperv_tsc_cputimer = {
        .next           = SLIST_ENTRY_INITIALIZER,
        .name           = "Hyper-V-TSC",
        .pri            = CPUTIMER_PRI_VMM_HI,
        .type           = CPUTIMER_VMM1,
        .count          = NULL, /* determined later */
        .fromhz         = cputimer_default_fromhz,
        .fromus         = cputimer_default_fromus,
        .construct      = hyperv_tsc_cputimer_construct,
        .destruct       = cputimer_default_destruct,
        .freq           = HYPERV_TIMER_FREQ
};

static struct cpucounter hyperv_tsc_cpucounter = {
        .freq           = HYPERV_TIMER_FREQ,
        .count          = NULL, /* determined later */
        .flags          = CPUCOUNTER_FLAG_MPSYNC,
        .prio           = CPUCOUNTER_PRIO_VMM_HI,
        .type           = CPUCOUNTER_VMM1
};

uint64_t
hypercall_md(volatile void *hc_addr, uint64_t in_val,
    uint64_t in_paddr, uint64_t out_paddr)
{
        uint64_t status;

        __asm__ __volatile__ ("mov %0, %%r8" : : "r" (out_paddr): "r8");
        __asm__ __volatile__ ("call *%3" : "=a" (status) :
            "c" (in_val), "d" (in_paddr), "m" (hc_addr));
        return (status);
}

int
hyperv_msi2vector(uint64_t msi_addr __unused, uint32_t msi_data)
{
        return (msi_data & MSI_X86_DATA_INTVEC);
}

#define HYPERV_TSC(fence)                                       \
static uint64_t                                                 \
hyperv_tsc_##fence(void)                                        \
{                                                               \
        struct hyperv_reftsc *tsc_ref = hyperv_ref_tsc.tsc_ref; \
        uint32_t seq;                                           \
                                                                \
        while ((seq = tsc_ref->tsc_seq) != 0) {                 \
                uint64_t disc, ret, tsc;                        \
                uint64_t scale;                                 \
                int64_t ofs;                                    \
                                                                \
                cpu_ccfence();                                  \
                scale = tsc_ref->tsc_scale;                     \
                ofs = tsc_ref->tsc_ofs;                         \
                                                                \
                cpu_##fence();                                  \
                tsc = rdtsc();                                  \
                                                                \
                /* ret = ((tsc * scale) >> 64) + ofs */         \
                __asm__ __volatile__ ("mulq %3" :               \
                    "=d" (ret), "=a" (disc) :                   \
                    "a" (tsc), "r" (scale));                    \
                ret += ofs;                                     \
                                                                \
                cpu_ccfence();                                  \
                if (tsc_ref->tsc_seq == seq)                    \
                        return (ret);                           \
                                                                \
                /* Sequence changed; re-sync. */                \
        }                                                       \
        /* Fallback to the generic rdmsr. */                    \
        return (rdmsr(MSR_HV_TIME_REF_COUNT));                  \
}                                                               \
struct __hack

HYPERV_TSC(lfence);
HYPERV_TSC(mfence);

static sysclock_t
hyperv_tsc_cputimer_count_lfence(void)
{
        uint64_t val;

        val = hyperv_tsc_lfence();
        return (val + hyperv_tsc_cputimer.base);
}

static sysclock_t
hyperv_tsc_cputimer_count_mfence(void)
{
        uint64_t val;

        val = hyperv_tsc_mfence();
        return (val + hyperv_tsc_cputimer.base);
}

static void
hyperv_tsc_cputimer_construct(struct cputimer *timer, sysclock_t oldclock)
{
        timer->base = 0;
        timer->base = oldclock - timer->count();
}

void
hyperv_md_init(void)
{
        hyperv_tc64_t tc64 = NULL;
        uint64_t val, orig;

        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 & CPUID_SSE2) == 0)    /* SSE2 for mfence/lfence */
                return;

        switch (cpu_vendor_id) {
        case CPU_VENDOR_AMD:
                hyperv_tsc_cputimer.count = hyperv_tsc_cputimer_count_mfence;
                tc64 = hyperv_tsc_mfence;
                break;

        case CPU_VENDOR_INTEL:
                hyperv_tsc_cputimer.count = hyperv_tsc_cputimer_count_lfence;
                tc64 = hyperv_tsc_lfence;
                break;

        default:
                /* Unsupport CPU vendors. */
                return;
        }
        KASSERT(tc64 != NULL, ("tc64 is not set"));
        hyperv_tsc_cpucounter.count = tc64;

        hyperv_ref_tsc.tsc_ref = hyperv_dmamem_alloc(NULL, PAGE_SIZE, 0,
            sizeof(struct hyperv_reftsc), &hyperv_ref_tsc.tsc_ref_dma,
            BUS_DMA_WAITOK | BUS_DMA_ZERO);
        if (hyperv_ref_tsc.tsc_ref == NULL) {
                kprintf("hyperv: reftsc page allocation failed\n");
                return;
        }

        orig = rdmsr(MSR_HV_REFERENCE_TSC);
        val = MSR_HV_REFTSC_ENABLE | (orig & MSR_HV_REFTSC_RSVD_MASK) |
            ((hyperv_ref_tsc.tsc_ref_dma.hv_paddr >> PAGE_SHIFT) <<
             MSR_HV_REFTSC_PGSHIFT);
        wrmsr(MSR_HV_REFERENCE_TSC, val);

        /* Register Hyper-V reference TSC cputimers. */
        cputimer_register(&hyperv_tsc_cputimer);
        cputimer_select(&hyperv_tsc_cputimer, 0);
        cpucounter_register(&hyperv_tsc_cpucounter);
        hyperv_tc64_saved = hyperv_tc64;
        hyperv_tc64 = tc64;
}

void
hyperv_md_uninit(void)
{
        if (hyperv_ref_tsc.tsc_ref != NULL) {
                uint64_t val;

                /* Deregister Hyper-V reference TSC systimer. */
                cputimer_deregister(&hyperv_tsc_cputimer);
                /* Revert tc64 change. */
                hyperv_tc64 = hyperv_tc64_saved;

                val = rdmsr(MSR_HV_REFERENCE_TSC);
                wrmsr(MSR_HV_REFERENCE_TSC, val & MSR_HV_REFTSC_RSVD_MASK);

                hyperv_dmamem_free(&hyperv_ref_tsc.tsc_ref_dma,
                    hyperv_ref_tsc.tsc_ref);
                hyperv_ref_tsc.tsc_ref = NULL;
        }
}