hyperv_machdep.c revision 311223
1/*- 2 * Copyright (c) 2016 Microsoft Corp. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice unmodified, this list of conditions, and the following 10 * disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: stable/10/sys/dev/hyperv/vmbus/amd64/hyperv_machdep.c 311223 2017-01-04 01:58:38Z sephe $"); 29 30#include <sys/param.h> 31#include <sys/conf.h> 32#include <sys/fcntl.h> 33#include <sys/kernel.h> 34#include <sys/systm.h> 35#include <sys/timetc.h> 36 37#include <machine/cpufunc.h> 38#include <machine/cputypes.h> 39#include <machine/md_var.h> 40#include <machine/specialreg.h> 41 42#include <vm/vm.h> 43 44#include <dev/hyperv/include/hyperv.h> 45#include <dev/hyperv/include/hyperv_busdma.h> 46#include <dev/hyperv/vmbus/hyperv_machdep.h> 47#include <dev/hyperv/vmbus/hyperv_reg.h> 48#include <dev/hyperv/vmbus/hyperv_var.h> 49 50struct hyperv_reftsc_ctx { 51 struct hyperv_reftsc *tsc_ref; 52 struct hyperv_dma tsc_ref_dma; 53}; 54 55static d_open_t hyperv_tsc_open; 56static d_mmap_t hyperv_tsc_mmap; 57 58static struct timecounter hyperv_tsc_timecounter = { 59 .tc_get_timecount = NULL, /* based on CPU vendor. */ 60 .tc_poll_pps = NULL, 61 .tc_counter_mask = 0xffffffff, 62 .tc_frequency = HYPERV_TIMER_FREQ, 63 .tc_name = "Hyper-V-TSC", 64 .tc_quality = 3000, 65 .tc_flags = 0, 66 .tc_priv = NULL 67}; 68 69static struct cdevsw hyperv_tsc_cdevsw = { 70 .d_version = D_VERSION, 71 .d_open = hyperv_tsc_open, 72 .d_mmap = hyperv_tsc_mmap, 73 .d_name = HYPERV_REFTSC_DEVNAME 74}; 75 76static struct hyperv_reftsc_ctx hyperv_ref_tsc; 77 78uint64_t 79hypercall_md(volatile void *hc_addr, uint64_t in_val, 80 uint64_t in_paddr, uint64_t out_paddr) 81{ 82 uint64_t status; 83 84 __asm__ __volatile__ ("mov %0, %%r8" : : "r" (out_paddr): "r8"); 85 __asm__ __volatile__ ("call *%3" : "=a" (status) : 86 "c" (in_val), "d" (in_paddr), "m" (hc_addr)); 87 return (status); 88} 89 90static int 91hyperv_tsc_open(struct cdev *dev __unused, int oflags, int devtype __unused, 92 struct thread *td __unused) 93{ 94 95 if (oflags & FWRITE) 96 return (EPERM); 97 return (0); 98} 99 100static int 101hyperv_tsc_mmap(struct cdev *dev __unused, vm_ooffset_t offset, 102 vm_paddr_t *paddr, int nprot __unused, vm_memattr_t *memattr __unused) 103{ 104 105 KASSERT(hyperv_ref_tsc.tsc_ref != NULL, ("reftsc has not been setup")); 106 107 /* 108 * NOTE: 109 * 'nprot' does not contain information interested to us; 110 * WR-open is blocked by d_open. 111 */ 112 113 if (offset != 0) 114 return (EOPNOTSUPP); 115 116 *paddr = hyperv_ref_tsc.tsc_ref_dma.hv_paddr; 117 return (0); 118} 119 120#define HYPERV_TSC_TIMECOUNT(fence) \ 121static u_int \ 122hyperv_tsc_timecount_##fence(struct timecounter *tc) \ 123{ \ 124 struct hyperv_reftsc *tsc_ref = hyperv_ref_tsc.tsc_ref; \ 125 uint32_t seq; \ 126 \ 127 while ((seq = tsc_ref->tsc_seq) != 0) { \ 128 uint64_t disc, ret, tsc, scale; \ 129 int64_t ofs; \ 130 \ 131 __compiler_membar(); \ 132 scale = tsc_ref->tsc_scale; \ 133 ofs = tsc_ref->tsc_ofs; \ 134 \ 135 fence(); \ 136 tsc = rdtsc(); \ 137 \ 138 /* ret = ((tsc * scale) >> 64) + ofs */ \ 139 __asm__ __volatile__ ("mulq %3" : \ 140 "=d" (ret), "=a" (disc) : \ 141 "a" (tsc), "r" (scale)); \ 142 ret += ofs; \ 143 \ 144 __compiler_membar(); \ 145 if (tsc_ref->tsc_seq == seq) \ 146 return (ret); \ 147 \ 148 /* Sequence changed; re-sync. */ \ 149 } \ 150 /* Fallback to the generic timecounter, i.e. rdmsr. */ \ 151 return (rdmsr(MSR_HV_TIME_REF_COUNT)); \ 152} \ 153struct __hack 154 155HYPERV_TSC_TIMECOUNT(lfence); 156HYPERV_TSC_TIMECOUNT(mfence); 157 158static void 159hyperv_tsc_tcinit(void *dummy __unused) 160{ 161 uint64_t val, orig; 162 163 if ((hyperv_features & 164 (CPUID_HV_MSR_TIME_REFCNT | CPUID_HV_MSR_REFERENCE_TSC)) != 165 (CPUID_HV_MSR_TIME_REFCNT | CPUID_HV_MSR_REFERENCE_TSC) || 166 (cpu_feature & CPUID_SSE2) == 0) /* SSE2 for mfence/lfence */ 167 return; 168 169 switch (cpu_vendor_id) { 170 case CPU_VENDOR_AMD: 171 hyperv_tsc_timecounter.tc_get_timecount = 172 hyperv_tsc_timecount_mfence; 173 break; 174 175 case CPU_VENDOR_INTEL: 176 hyperv_tsc_timecounter.tc_get_timecount = 177 hyperv_tsc_timecount_lfence; 178 break; 179 180 default: 181 /* Unsupport CPU vendors. */ 182 return; 183 } 184 185 hyperv_ref_tsc.tsc_ref = hyperv_dmamem_alloc(NULL, PAGE_SIZE, 0, 186 sizeof(struct hyperv_reftsc), &hyperv_ref_tsc.tsc_ref_dma, 187 BUS_DMA_WAITOK | BUS_DMA_ZERO); 188 if (hyperv_ref_tsc.tsc_ref == NULL) { 189 printf("hyperv: reftsc page allocation failed\n"); 190 return; 191 } 192 193 orig = rdmsr(MSR_HV_REFERENCE_TSC); 194 val = MSR_HV_REFTSC_ENABLE | (orig & MSR_HV_REFTSC_RSVD_MASK) | 195 ((hyperv_ref_tsc.tsc_ref_dma.hv_paddr >> PAGE_SHIFT) << 196 MSR_HV_REFTSC_PGSHIFT); 197 wrmsr(MSR_HV_REFERENCE_TSC, val); 198 199 /* Register "enlightened" timecounter. */ 200 tc_init(&hyperv_tsc_timecounter); 201 202 /* Add device for mmap(2). */ 203 make_dev(&hyperv_tsc_cdevsw, 0, UID_ROOT, GID_WHEEL, 0444, 204 HYPERV_REFTSC_DEVNAME); 205} 206SYSINIT(hyperv_tsc_init, SI_SUB_DRIVERS, SI_ORDER_FIRST, hyperv_tsc_tcinit, 207 NULL); 208