xref: /freebsd/sys/dev/hyperv/vmbus/amd64/hyperv_machdep.c (revision de69dfbbbc46b23ca5c379d3673d09ca41f330c2)
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$");
29 
30 #include <sys/param.h>
31 #include <sys/kernel.h>
32 #include <sys/systm.h>
33 #include <sys/timetc.h>
34 
35 #include <machine/cpufunc.h>
36 #include <machine/cputypes.h>
37 #include <machine/md_var.h>
38 
39 #include <dev/hyperv/include/hyperv.h>
40 #include <dev/hyperv/include/hyperv_busdma.h>
41 #include <dev/hyperv/vmbus/hyperv_machdep.h>
42 #include <dev/hyperv/vmbus/hyperv_reg.h>
43 #include <dev/hyperv/vmbus/hyperv_var.h>
44 
45 struct hyperv_reftsc_ctx {
46 	struct hyperv_reftsc	*tsc_ref;
47 	struct hyperv_dma	tsc_ref_dma;
48 };
49 
50 static struct timecounter	hyperv_tsc_timecounter = {
51 	.tc_get_timecount	= NULL,	/* based on CPU vendor. */
52 	.tc_poll_pps		= NULL,
53 	.tc_counter_mask	= 0xffffffff,
54 	.tc_frequency		= HYPERV_TIMER_FREQ,
55 	.tc_name		= "Hyper-V-TSC",
56 	.tc_quality		= 3000,
57 	.tc_flags		= 0,
58 	.tc_priv		= NULL
59 };
60 
61 static struct hyperv_reftsc_ctx	hyperv_ref_tsc;
62 
63 uint64_t
64 hypercall_md(volatile void *hc_addr, uint64_t in_val,
65     uint64_t in_paddr, uint64_t out_paddr)
66 {
67 	uint64_t status;
68 
69 	__asm__ __volatile__ ("mov %0, %%r8" : : "r" (out_paddr): "r8");
70 	__asm__ __volatile__ ("call *%3" : "=a" (status) :
71 	    "c" (in_val), "d" (in_paddr), "m" (hc_addr));
72 	return (status);
73 }
74 
75 #define HYPERV_TSC_TIMECOUNT(fence)					\
76 static u_int								\
77 hyperv_tsc_timecount_##fence(struct timecounter *tc)			\
78 {									\
79 	struct hyperv_reftsc *tsc_ref = hyperv_ref_tsc.tsc_ref;		\
80 	uint32_t seq;							\
81 									\
82 	while ((seq = atomic_load_acq_int(&tsc_ref->tsc_seq)) != 0) {	\
83 		uint64_t disc, ret, tsc;				\
84 		uint64_t scale = tsc_ref->tsc_scale;			\
85 		int64_t ofs = tsc_ref->tsc_ofs;				\
86 									\
87 		fence();						\
88 		tsc = rdtsc();						\
89 									\
90 		/* ret = ((tsc * scale) >> 64) + ofs */			\
91 		__asm__ __volatile__ ("mulq %3" :			\
92 		    "=d" (ret), "=a" (disc) :				\
93 		    "a" (tsc), "r" (scale));				\
94 		ret += ofs;						\
95 									\
96 		atomic_thread_fence_acq();				\
97 		if (tsc_ref->tsc_seq == seq)				\
98 			return (ret);					\
99 									\
100 		/* Sequence changed; re-sync. */			\
101 	}								\
102 	/* Fallback to the generic timecounter, i.e. rdmsr. */		\
103 	return (rdmsr(MSR_HV_TIME_REF_COUNT));				\
104 }									\
105 struct __hack
106 
107 HYPERV_TSC_TIMECOUNT(lfence);
108 HYPERV_TSC_TIMECOUNT(mfence);
109 
110 static void
111 hyperv_tsc_tcinit(void *dummy __unused)
112 {
113 	uint64_t val, orig;
114 
115 	if ((hyperv_features &
116 	     (CPUID_HV_MSR_TIME_REFCNT | CPUID_HV_MSR_REFERENCE_TSC)) !=
117 	    (CPUID_HV_MSR_TIME_REFCNT | CPUID_HV_MSR_REFERENCE_TSC) ||
118 	    (cpu_feature & CPUID_SSE2) == 0)	/* SSE2 for mfence/lfence */
119 		return;
120 
121 	switch (cpu_vendor_id) {
122 	case CPU_VENDOR_AMD:
123 		hyperv_tsc_timecounter.tc_get_timecount =
124 		    hyperv_tsc_timecount_mfence;
125 		break;
126 
127 	case CPU_VENDOR_INTEL:
128 		hyperv_tsc_timecounter.tc_get_timecount =
129 		    hyperv_tsc_timecount_lfence;
130 		break;
131 
132 	default:
133 		/* Unsupport CPU vendors. */
134 		return;
135 	}
136 
137 	hyperv_ref_tsc.tsc_ref = hyperv_dmamem_alloc(NULL, PAGE_SIZE, 0,
138 	    sizeof(struct hyperv_reftsc), &hyperv_ref_tsc.tsc_ref_dma,
139 	    BUS_DMA_WAITOK | BUS_DMA_ZERO);
140 	if (hyperv_ref_tsc.tsc_ref == NULL) {
141 		printf("hyperv: reftsc page allocation failed\n");
142 		return;
143 	}
144 
145 	orig = rdmsr(MSR_HV_REFERENCE_TSC);
146 	val = MSR_HV_REFTSC_ENABLE | (orig & MSR_HV_REFTSC_RSVD_MASK) |
147 	    ((hyperv_ref_tsc.tsc_ref_dma.hv_paddr >> PAGE_SHIFT) <<
148 	     MSR_HV_REFTSC_PGSHIFT);
149 	wrmsr(MSR_HV_REFERENCE_TSC, val);
150 
151 	/* Register "enlightened" timecounter. */
152 	tc_init(&hyperv_tsc_timecounter);
153 }
154 SYSINIT(hyperv_tsc_init, SI_SUB_DRIVERS, SI_ORDER_FIRST, hyperv_tsc_tcinit,
155     NULL);
156