1250199Sgrehan/*-
2250199Sgrehan * Copyright (c) 2009-2012 Microsoft Corp.
3250199Sgrehan * Copyright (c) 2012 NetApp Inc.
4250199Sgrehan * Copyright (c) 2012 Citrix Inc.
5250199Sgrehan * All rights reserved.
6250199Sgrehan *
7250199Sgrehan * Redistribution and use in source and binary forms, with or without
8250199Sgrehan * modification, are permitted provided that the following conditions
9250199Sgrehan * are met:
10250199Sgrehan * 1. Redistributions of source code must retain the above copyright
11250199Sgrehan *    notice unmodified, this list of conditions, and the following
12250199Sgrehan *    disclaimer.
13250199Sgrehan * 2. Redistributions in binary form must reproduce the above copyright
14250199Sgrehan *    notice, this list of conditions and the following disclaimer in the
15250199Sgrehan *    documentation and/or other materials provided with the distribution.
16250199Sgrehan *
17250199Sgrehan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18250199Sgrehan * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19250199Sgrehan * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20250199Sgrehan * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21250199Sgrehan * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22250199Sgrehan * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23250199Sgrehan * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24250199Sgrehan * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25250199Sgrehan * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26250199Sgrehan * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27250199Sgrehan */
28250199Sgrehan
29250199Sgrehan/**
30250199Sgrehan * Implements low-level interactions with Hypver-V/Azure
31250199Sgrehan */
32256071Sgibbs#include <sys/cdefs.h>
33256071Sgibbs__FBSDID("$FreeBSD: releng/10.3/sys/dev/hyperv/vmbus/hv_hv.c 303984 2016-08-12 04:01:16Z glebius $");
34250199Sgrehan
35250199Sgrehan#include <sys/param.h>
36303984Sglebius#include <sys/kernel.h>
37250199Sgrehan#include <sys/malloc.h>
38250199Sgrehan#include <sys/pcpu.h>
39250199Sgrehan#include <sys/timetc.h>
40250199Sgrehan#include <machine/bus.h>
41295789Ssephe#include <machine/md_var.h>
42250199Sgrehan#include <vm/vm.h>
43250199Sgrehan#include <vm/vm_param.h>
44250199Sgrehan#include <vm/pmap.h>
45250199Sgrehan
46250199Sgrehan
47250199Sgrehan#include "hv_vmbus_priv.h"
48250199Sgrehan
49250199Sgrehan#define HV_NANOSECONDS_PER_SEC		1000000000L
50250199Sgrehan
51250199Sgrehan
52250199Sgrehanstatic u_int hv_get_timecount(struct timecounter *tc);
53250199Sgrehan
54303984Sglebiusu_int	hyperv_features;
55303984Sglebiusu_int	hyperv_recommends;
56303984Sglebius
57250199Sgrehan/**
58250199Sgrehan * Globals
59250199Sgrehan */
60250199Sgrehanhv_vmbus_context hv_vmbus_g_context = {
61250199Sgrehan	.syn_ic_initialized = FALSE,
62250199Sgrehan	.hypercall_page = NULL,
63250199Sgrehan};
64250199Sgrehan
65250199Sgrehanstatic struct timecounter hv_timecounter = {
66250199Sgrehan	hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
67250199Sgrehan};
68250199Sgrehan
69250199Sgrehanstatic u_int
70250199Sgrehanhv_get_timecount(struct timecounter *tc)
71250199Sgrehan{
72255414Sgrehan	u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
73250199Sgrehan	return (now);
74250199Sgrehan}
75250199Sgrehan
76250199Sgrehan/**
77250199Sgrehan * @brief Query the cpuid for presence of windows hypervisor
78250199Sgrehan */
79250199Sgrehanint
80250199Sgrehanhv_vmbus_query_hypervisor_presence(void)
81250199Sgrehan{
82295789Ssephe	if (vm_guest != VM_GUEST_HV)
83256071Sgibbs		return (0);
84256071Sgibbs
85295789Ssephe	return (hv_high >= HV_X64_CPUID_MIN && hv_high <= HV_X64_CPUID_MAX);
86250199Sgrehan}
87250199Sgrehan
88250199Sgrehan/**
89250199Sgrehan * @brief Get version of the windows hypervisor
90250199Sgrehan */
91250199Sgrehanstatic int
92250199Sgrehanhv_vmbus_get_hypervisor_version(void)
93250199Sgrehan{
94295789Ssephe	u_int regs[4];
95250199Sgrehan	unsigned int maxLeaf;
96250199Sgrehan	unsigned int op;
97250199Sgrehan
98250199Sgrehan	/*
99250199Sgrehan	 * Its assumed that this is called after confirming that
100250199Sgrehan	 * Viridian is present
101250199Sgrehan	 * Query id and revision.
102250199Sgrehan	 */
103250199Sgrehan	op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
104295789Ssephe	do_cpuid(op, regs);
105250199Sgrehan
106295789Ssephe	maxLeaf = regs[0];
107250199Sgrehan	op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
108295789Ssephe	do_cpuid(op, regs);
109250199Sgrehan
110250199Sgrehan	if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) {
111250199Sgrehan	    op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
112295789Ssephe	    do_cpuid(op, regs);
113250199Sgrehan	}
114250199Sgrehan	return (maxLeaf);
115250199Sgrehan}
116250199Sgrehan
117250199Sgrehan/**
118250199Sgrehan * @brief Invoke the specified hypercall
119250199Sgrehan */
120250199Sgrehanstatic uint64_t
121250199Sgrehanhv_vmbus_do_hypercall(uint64_t control, void* input, void* output)
122250199Sgrehan{
123250199Sgrehan#ifdef __x86_64__
124250199Sgrehan	uint64_t hv_status = 0;
125250199Sgrehan	uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
126250199Sgrehan	uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
127250199Sgrehan	volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
128250199Sgrehan
129250199Sgrehan	__asm__ __volatile__ ("mov %0, %%r8" : : "r" (output_address): "r8");
130250199Sgrehan	__asm__ __volatile__ ("call *%3" : "=a"(hv_status):
131250199Sgrehan				"c" (control), "d" (input_address),
132250199Sgrehan				"m" (hypercall_page));
133250199Sgrehan	return (hv_status);
134250199Sgrehan#else
135250199Sgrehan	uint32_t control_high = control >> 32;
136250199Sgrehan	uint32_t control_low = control & 0xFFFFFFFF;
137250199Sgrehan	uint32_t hv_status_high = 1;
138250199Sgrehan	uint32_t hv_status_low = 1;
139250199Sgrehan	uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
140250199Sgrehan	uint32_t input_address_high = input_address >> 32;
141250199Sgrehan	uint32_t input_address_low = input_address & 0xFFFFFFFF;
142250199Sgrehan	uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
143250199Sgrehan	uint32_t output_address_high = output_address >> 32;
144250199Sgrehan	uint32_t output_address_low = output_address & 0xFFFFFFFF;
145250199Sgrehan	volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
146250199Sgrehan
147250199Sgrehan	__asm__ __volatile__ ("call *%8" : "=d"(hv_status_high),
148250199Sgrehan				"=a"(hv_status_low) : "d" (control_high),
149250199Sgrehan				"a" (control_low), "b" (input_address_high),
150250199Sgrehan				"c" (input_address_low),
151250199Sgrehan				"D"(output_address_high),
152250199Sgrehan				"S"(output_address_low), "m" (hypercall_page));
153250199Sgrehan	return (hv_status_low | ((uint64_t)hv_status_high << 32));
154250199Sgrehan#endif /* __x86_64__ */
155250199Sgrehan}
156250199Sgrehan
157250199Sgrehan/**
158250199Sgrehan *  @brief Main initialization routine.
159250199Sgrehan *
160250199Sgrehan *  This routine must be called
161250199Sgrehan *  before any other routines in here are called
162250199Sgrehan */
163250199Sgrehanint
164250199Sgrehanhv_vmbus_init(void)
165250199Sgrehan{
166250199Sgrehan	int					max_leaf;
167250199Sgrehan	hv_vmbus_x64_msr_hypercall_contents	hypercall_msr;
168250199Sgrehan	void* 					virt_addr = 0;
169250199Sgrehan
170250199Sgrehan	memset(
171250199Sgrehan	    hv_vmbus_g_context.syn_ic_event_page,
172250199Sgrehan	    0,
173250199Sgrehan	    sizeof(hv_vmbus_handle) * MAXCPU);
174250199Sgrehan
175250199Sgrehan	memset(
176250199Sgrehan	    hv_vmbus_g_context.syn_ic_msg_page,
177250199Sgrehan	    0,
178250199Sgrehan	    sizeof(hv_vmbus_handle) * MAXCPU);
179250199Sgrehan
180256758Sgibbs	if (vm_guest != VM_GUEST_HV)
181250199Sgrehan	    goto cleanup;
182250199Sgrehan
183250199Sgrehan	max_leaf = hv_vmbus_get_hypervisor_version();
184250199Sgrehan
185250199Sgrehan	/*
186250199Sgrehan	 * Write our OS info
187250199Sgrehan	 */
188250199Sgrehan	uint64_t os_guest_info = HV_FREEBSD_GUEST_ID;
189255414Sgrehan	wrmsr(HV_X64_MSR_GUEST_OS_ID, os_guest_info);
190250199Sgrehan	hv_vmbus_g_context.guest_id = os_guest_info;
191250199Sgrehan
192250199Sgrehan	/*
193250199Sgrehan	 * See if the hypercall page is already set
194250199Sgrehan	 */
195255414Sgrehan	hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
196250199Sgrehan	virt_addr = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
197250199Sgrehan	KASSERT(virt_addr != NULL,
198250199Sgrehan	    ("Error VMBUS: malloc failed to allocate page during init!"));
199250199Sgrehan	if (virt_addr == NULL)
200250199Sgrehan	    goto cleanup;
201250199Sgrehan
202256276Sdim	hypercall_msr.u.enable = 1;
203256276Sdim	hypercall_msr.u.guest_physical_address =
204250199Sgrehan	    (hv_get_phys_addr(virt_addr) >> PAGE_SHIFT);
205255414Sgrehan	wrmsr(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64_t);
206250199Sgrehan
207250199Sgrehan	/*
208250199Sgrehan	 * Confirm that hypercall page did get set up
209250199Sgrehan	 */
210250199Sgrehan	hypercall_msr.as_uint64_t = 0;
211255414Sgrehan	hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
212250199Sgrehan
213256276Sdim	if (!hypercall_msr.u.enable)
214250199Sgrehan	    goto cleanup;
215250199Sgrehan
216250199Sgrehan	hv_vmbus_g_context.hypercall_page = virt_addr;
217250199Sgrehan
218295789Ssephe	hv_et_init();
219250199Sgrehan
220250199Sgrehan	return (0);
221250199Sgrehan
222250199Sgrehan	cleanup:
223250199Sgrehan	if (virt_addr != NULL) {
224256276Sdim	    if (hypercall_msr.u.enable) {
225250199Sgrehan		hypercall_msr.as_uint64_t = 0;
226255414Sgrehan		wrmsr(HV_X64_MSR_HYPERCALL,
227250199Sgrehan					hypercall_msr.as_uint64_t);
228250199Sgrehan	    }
229250199Sgrehan
230250199Sgrehan	    free(virt_addr, M_DEVBUF);
231250199Sgrehan	}
232250199Sgrehan	return (ENOTSUP);
233250199Sgrehan}
234250199Sgrehan
235250199Sgrehan/**
236250199Sgrehan * @brief Cleanup routine, called normally during driver unloading or exiting
237250199Sgrehan */
238250199Sgrehanvoid
239250199Sgrehanhv_vmbus_cleanup(void)
240250199Sgrehan{
241250199Sgrehan	hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
242250199Sgrehan
243250199Sgrehan	if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) {
244250199Sgrehan	    if (hv_vmbus_g_context.hypercall_page != NULL) {
245250199Sgrehan		hypercall_msr.as_uint64_t = 0;
246255414Sgrehan		wrmsr(HV_X64_MSR_HYPERCALL,
247250199Sgrehan					hypercall_msr.as_uint64_t);
248250199Sgrehan		free(hv_vmbus_g_context.hypercall_page, M_DEVBUF);
249250199Sgrehan		hv_vmbus_g_context.hypercall_page = NULL;
250250199Sgrehan	    }
251250199Sgrehan	}
252250199Sgrehan}
253250199Sgrehan
254250199Sgrehan/**
255250199Sgrehan * @brief Post a message using the hypervisor message IPC.
256250199Sgrehan * (This involves a hypercall.)
257250199Sgrehan */
258250199Sgrehanhv_vmbus_status
259250199Sgrehanhv_vmbus_post_msg_via_msg_ipc(
260250199Sgrehan	hv_vmbus_connection_id	connection_id,
261250199Sgrehan	hv_vmbus_msg_type	message_type,
262250199Sgrehan	void*			payload,
263250199Sgrehan	size_t			payload_size)
264250199Sgrehan{
265250199Sgrehan	struct alignedinput {
266250199Sgrehan	    uint64_t alignment8;
267250199Sgrehan	    hv_vmbus_input_post_message msg;
268250199Sgrehan	};
269250199Sgrehan
270250199Sgrehan	hv_vmbus_input_post_message*	aligned_msg;
271250199Sgrehan	hv_vmbus_status 		status;
272250199Sgrehan	size_t				addr;
273250199Sgrehan
274250199Sgrehan	if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
275250199Sgrehan	    return (EMSGSIZE);
276250199Sgrehan
277250199Sgrehan	addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
278250199Sgrehan			    M_ZERO | M_NOWAIT);
279250199Sgrehan	KASSERT(addr != 0,
280250199Sgrehan	    ("Error VMBUS: malloc failed to allocate message buffer!"));
281250199Sgrehan	if (addr == 0)
282250199Sgrehan	    return (ENOMEM);
283250199Sgrehan
284250199Sgrehan	aligned_msg = (hv_vmbus_input_post_message*)
285250199Sgrehan	    (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
286250199Sgrehan
287250199Sgrehan	aligned_msg->connection_id = connection_id;
288250199Sgrehan	aligned_msg->message_type = message_type;
289250199Sgrehan	aligned_msg->payload_size = payload_size;
290250199Sgrehan	memcpy((void*) aligned_msg->payload, payload, payload_size);
291250199Sgrehan
292250199Sgrehan	status = hv_vmbus_do_hypercall(
293250199Sgrehan		    HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
294250199Sgrehan
295250199Sgrehan	free((void *) addr, M_DEVBUF);
296250199Sgrehan	return (status);
297250199Sgrehan}
298250199Sgrehan
299250199Sgrehan/**
300250199Sgrehan * @brief Signal an event on the specified connection using the hypervisor
301250199Sgrehan * event IPC. (This involves a hypercall.)
302250199Sgrehan */
303250199Sgrehanhv_vmbus_status
304283280Swhuhv_vmbus_signal_event(void *con_id)
305250199Sgrehan{
306250199Sgrehan	hv_vmbus_status status;
307250199Sgrehan
308250199Sgrehan	status = hv_vmbus_do_hypercall(
309250199Sgrehan		    HV_CALL_SIGNAL_EVENT,
310283280Swhu		    con_id,
311250199Sgrehan		    0) & 0xFFFF;
312250199Sgrehan
313250199Sgrehan	return (status);
314250199Sgrehan}
315250199Sgrehan
316250199Sgrehan/**
317250199Sgrehan * @brief hv_vmbus_synic_init
318250199Sgrehan */
319250199Sgrehanvoid
320255414Sgrehanhv_vmbus_synic_init(void *arg)
321250199Sgrehan
322250199Sgrehan{
323250199Sgrehan	int			cpu;
324283280Swhu	uint64_t		hv_vcpu_index;
325250199Sgrehan	hv_vmbus_synic_simp	simp;
326250199Sgrehan	hv_vmbus_synic_siefp	siefp;
327250199Sgrehan	hv_vmbus_synic_scontrol sctrl;
328250199Sgrehan	hv_vmbus_synic_sint	shared_sint;
329250199Sgrehan	uint64_t		version;
330255414Sgrehan	hv_setup_args* 		setup_args = (hv_setup_args *)arg;
331250199Sgrehan
332250199Sgrehan	cpu = PCPU_GET(cpuid);
333250199Sgrehan
334250199Sgrehan	if (hv_vmbus_g_context.hypercall_page == NULL)
335250199Sgrehan	    return;
336250199Sgrehan
337250199Sgrehan	/*
338250199Sgrehan	 * TODO: Check the version
339250199Sgrehan	 */
340255414Sgrehan	version = rdmsr(HV_X64_MSR_SVERSION);
341255414Sgrehan
342283280Swhu	hv_vmbus_g_context.syn_ic_msg_page[cpu] =
343283280Swhu	    setup_args->page_buffers[2 * cpu];
344283280Swhu	hv_vmbus_g_context.syn_ic_event_page[cpu] =
345283280Swhu	    setup_args->page_buffers[2 * cpu + 1];
346250199Sgrehan
347250199Sgrehan	/*
348250199Sgrehan	 * Setup the Synic's message page
349250199Sgrehan	 */
350250199Sgrehan
351255414Sgrehan	simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
352256276Sdim	simp.u.simp_enabled = 1;
353256276Sdim	simp.u.base_simp_gpa = ((hv_get_phys_addr(
354250199Sgrehan	    hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT);
355250199Sgrehan
356255414Sgrehan	wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
357250199Sgrehan
358250199Sgrehan	/*
359250199Sgrehan	 * Setup the Synic's event page
360250199Sgrehan	 */
361255414Sgrehan	siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
362256276Sdim	siefp.u.siefp_enabled = 1;
363256276Sdim	siefp.u.base_siefp_gpa = ((hv_get_phys_addr(
364250199Sgrehan	    hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT);
365250199Sgrehan
366255414Sgrehan	wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
367250199Sgrehan
368255414Sgrehan	/*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
369283280Swhu	shared_sint.as_uint64_t = 0;
370256276Sdim	shared_sint.u.vector = setup_args->vector;
371256276Sdim	shared_sint.u.masked = FALSE;
372283280Swhu	shared_sint.u.auto_eoi = TRUE;
373250199Sgrehan
374255414Sgrehan	wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
375250199Sgrehan	    shared_sint.as_uint64_t);
376250199Sgrehan
377250199Sgrehan	/* Enable the global synic bit */
378255414Sgrehan	sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
379256276Sdim	sctrl.u.enable = 1;
380250199Sgrehan
381255414Sgrehan	wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
382250199Sgrehan
383250199Sgrehan	hv_vmbus_g_context.syn_ic_initialized = TRUE;
384250199Sgrehan
385283280Swhu	/*
386283280Swhu	 * Set up the cpuid mapping from Hyper-V to FreeBSD.
387283280Swhu	 * The array is indexed using FreeBSD cpuid.
388283280Swhu	 */
389283280Swhu	hv_vcpu_index = rdmsr(HV_X64_MSR_VP_INDEX);
390283280Swhu	hv_vmbus_g_context.hv_vcpu_index[cpu] = (uint32_t)hv_vcpu_index;
391283280Swhu
392250199Sgrehan	return;
393250199Sgrehan}
394250199Sgrehan
395250199Sgrehan/**
396250199Sgrehan * @brief Cleanup routine for hv_vmbus_synic_init()
397250199Sgrehan */
398250199Sgrehanvoid hv_vmbus_synic_cleanup(void *arg)
399250199Sgrehan{
400250199Sgrehan	hv_vmbus_synic_sint	shared_sint;
401250199Sgrehan	hv_vmbus_synic_simp	simp;
402250199Sgrehan	hv_vmbus_synic_siefp	siefp;
403250199Sgrehan
404250199Sgrehan	if (!hv_vmbus_g_context.syn_ic_initialized)
405250199Sgrehan	    return;
406250199Sgrehan
407255414Sgrehan	shared_sint.as_uint64_t = rdmsr(
408250199Sgrehan	    HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
409250199Sgrehan
410256276Sdim	shared_sint.u.masked = 1;
411250199Sgrehan
412250199Sgrehan	/*
413250199Sgrehan	 * Disable the interrupt
414250199Sgrehan	 */
415255414Sgrehan	wrmsr(
416250199Sgrehan	    HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
417250199Sgrehan	    shared_sint.as_uint64_t);
418250199Sgrehan
419255414Sgrehan	simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
420256276Sdim	simp.u.simp_enabled = 0;
421256276Sdim	simp.u.base_simp_gpa = 0;
422250199Sgrehan
423255414Sgrehan	wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
424250199Sgrehan
425255414Sgrehan	siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
426256276Sdim	siefp.u.siefp_enabled = 0;
427256276Sdim	siefp.u.base_siefp_gpa = 0;
428250199Sgrehan
429255414Sgrehan	wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
430250199Sgrehan}
431250199Sgrehan
432303984Sglebiusstatic bool
433303984Sglebiushyperv_identify(void)
434303984Sglebius{
435303984Sglebius	u_int regs[4];
436303984Sglebius	unsigned int maxLeaf;
437303984Sglebius	unsigned int op;
438303984Sglebius
439303984Sglebius	if (vm_guest != VM_GUEST_HV)
440303984Sglebius		return (false);
441303984Sglebius
442303984Sglebius	op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
443303984Sglebius	do_cpuid(op, regs);
444303984Sglebius	maxLeaf = regs[0];
445303984Sglebius	if (maxLeaf < HV_CPU_ID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS)
446303984Sglebius		return (false);
447303984Sglebius
448303984Sglebius	op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
449303984Sglebius	do_cpuid(op, regs);
450303984Sglebius	if (regs[0] != 0x31237648 /* HV#1 */)
451303984Sglebius		return (false);
452303984Sglebius
453303984Sglebius	op = HV_CPU_ID_FUNCTION_MS_HV_FEATURES;
454303984Sglebius	do_cpuid(op, regs);
455303984Sglebius	if ((regs[0] & HV_FEATURE_MSR_HYPERCALL) == 0) {
456303984Sglebius		/*
457303984Sglebius		 * Hyper-V w/o Hypercall is impossible; someone
458303984Sglebius		 * is faking Hyper-V.
459303984Sglebius		 */
460303984Sglebius		return (false);
461303984Sglebius	}
462303984Sglebius	hyperv_features = regs[0];
463303984Sglebius
464303984Sglebius	op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
465303984Sglebius	do_cpuid(op, regs);
466303984Sglebius	printf("Hyper-V Version: %d.%d.%d [SP%d]\n",
467303984Sglebius	    regs[1] >> 16, regs[1] & 0xffff, regs[0], regs[2]);
468303984Sglebius
469303984Sglebius	printf("  Features: 0x%b\n", hyperv_features,
470303984Sglebius	    "\020"
471303984Sglebius	    "\001VPRUNTIME"
472303984Sglebius	    "\002TMREFCNT"
473303984Sglebius	    "\003SYNCIC"
474303984Sglebius	    "\004SYNCTM"
475303984Sglebius	    "\005APIC"
476303984Sglebius	    "\006HYERCALL"
477303984Sglebius	    "\007VPINDEX"
478303984Sglebius	    "\010RESET"
479303984Sglebius	    "\011STATS"
480303984Sglebius	    "\012REFTSC"
481303984Sglebius	    "\013IDLE"
482303984Sglebius	    "\014TMFREQ"
483303984Sglebius	    "\015DEBUG");
484303984Sglebius
485303984Sglebius	op = HV_CPU_ID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION;
486303984Sglebius	do_cpuid(op, regs);
487303984Sglebius	hyperv_recommends = regs[0];
488303984Sglebius	if (bootverbose)
489303984Sglebius		printf("  Recommends: %08x %08x\n", regs[0], regs[1]);
490303984Sglebius
491303984Sglebius	op = HV_CPU_ID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS;
492303984Sglebius	do_cpuid(op, regs);
493303984Sglebius	if (bootverbose) {
494303984Sglebius		printf("  Limits: Vcpu:%d Lcpu:%d Int:%d\n",
495303984Sglebius		    regs[0], regs[1], regs[2]);
496303984Sglebius	}
497303984Sglebius
498303984Sglebius	if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_HARDWARE_FEATURE) {
499303984Sglebius		op = HV_CPU_ID_FUNCTION_MS_HV_HARDWARE_FEATURE;
500303984Sglebius		do_cpuid(op, regs);
501303984Sglebius		if (bootverbose) {
502303984Sglebius			printf("  HW Features: %08x AMD: %08x\n",
503303984Sglebius			    regs[0], regs[3]);
504303984Sglebius		}
505303984Sglebius	}
506303984Sglebius
507303984Sglebius	return (true);
508303984Sglebius}
509303984Sglebius
510303984Sglebiusstatic void
511303984Sglebiushyperv_init(void *dummy __unused)
512303984Sglebius{
513303984Sglebius	if (!hyperv_identify())
514303984Sglebius		return;
515303984Sglebius
516303984Sglebius	if (hyperv_features & HV_FEATURE_MSR_TIME_REFCNT) {
517303984Sglebius		/* Register virtual timecount */
518303984Sglebius		tc_init(&hv_timecounter);
519303984Sglebius	}
520303984Sglebius}
521303984SglebiusSYSINIT(hyperv_initialize, SI_SUB_HYPERVISOR, SI_ORDER_FIRST, hyperv_init, NULL);
522