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