1/*-
2 * Copyright (c) 2009-2012 Microsoft Corp.
3 * Copyright (c) 2012 NetApp Inc.
4 * Copyright (c) 2012 Citrix Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice unmodified, this list of conditions, and the following
12 *    disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/**
30 * Implements low-level interactions with Hypver-V/Azure
31 */
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD$");
34
35#include <sys/param.h>
36#include <sys/malloc.h>
37#include <sys/pcpu.h>
38#include <sys/timetc.h>
39#include <machine/bus.h>
40#include <vm/vm.h>
41#include <vm/vm_param.h>
42#include <vm/pmap.h>
43
44
45#include "hv_vmbus_priv.h"
46
47#define HV_X64_MSR_GUEST_OS_ID		0x40000000
48
49#define HV_X64_CPUID_MIN		0x40000005
50#define HV_X64_CPUID_MAX		0x4000ffff
51#define HV_X64_MSR_TIME_REF_COUNT	0x40000020
52
53#define HV_NANOSECONDS_PER_SEC		1000000000L
54
55
56static u_int hv_get_timecount(struct timecounter *tc);
57
58static inline void do_cpuid_inline(unsigned int op, unsigned int *eax,
59	unsigned int *ebx, unsigned int *ecx, unsigned int *edx) {
60	__asm__ __volatile__("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx),
61			     "=d" (*edx) : "0" (op), "c" (ecx));
62}
63
64/**
65 * Globals
66 */
67hv_vmbus_context hv_vmbus_g_context = {
68	.syn_ic_initialized = FALSE,
69	.hypercall_page = NULL,
70	.signal_event_param = NULL,
71	.signal_event_buffer = NULL,
72};
73
74static struct timecounter hv_timecounter = {
75	hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
76};
77
78static u_int
79hv_get_timecount(struct timecounter *tc)
80{
81	u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
82	return (now);
83}
84
85/**
86 * @brief Query the cpuid for presence of windows hypervisor
87 */
88int
89hv_vmbus_query_hypervisor_presence(void)
90{
91	u_int regs[4];
92	int hyper_v_detected = 0;
93
94	/*
95	 * When Xen is detected and native Xen PV support is enabled,
96	 * ignore Xen's HyperV emulation.
97	 */
98	if (vm_guest == VM_GUEST_XEN)
99		return (0);
100
101	do_cpuid(1, regs);
102	if (regs[2] & 0x80000000) { /* if(a hypervisor is detected) */
103		/* make sure this really is Hyper-V */
104		/* we look at the CPUID info */
105		do_cpuid(HV_X64_MSR_GUEST_OS_ID, regs);
106		hyper_v_detected =
107				regs[0] >= HV_X64_CPUID_MIN &&
108				regs[0] <= HV_X64_CPUID_MAX &&
109				!memcmp("Microsoft Hv", &regs[1], 12);
110	}
111	return (hyper_v_detected);
112}
113
114/**
115 * @brief Get version of the windows hypervisor
116 */
117static int
118hv_vmbus_get_hypervisor_version(void)
119{
120	unsigned int eax;
121	unsigned int ebx;
122	unsigned int ecx;
123	unsigned int edx;
124	unsigned int maxLeaf;
125	unsigned int op;
126
127	/*
128	 * Its assumed that this is called after confirming that
129	 * Viridian is present
130	 * Query id and revision.
131	 */
132	eax = 0;
133	ebx = 0;
134	ecx = 0;
135	edx = 0;
136	op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
137	do_cpuid_inline(op, &eax, &ebx, &ecx, &edx);
138
139	maxLeaf = eax;
140	eax = 0;
141	ebx = 0;
142	ecx = 0;
143	edx = 0;
144	op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
145	do_cpuid_inline(op, &eax, &ebx, &ecx, &edx);
146
147	if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) {
148	    eax = 0;
149	    ebx = 0;
150	    ecx = 0;
151	    edx = 0;
152	    op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
153	    do_cpuid_inline(op, &eax, &ebx, &ecx, &edx);
154	}
155	return (maxLeaf);
156}
157
158/**
159 * @brief Invoke the specified hypercall
160 */
161static uint64_t
162hv_vmbus_do_hypercall(uint64_t control, void* input, void* output)
163{
164#ifdef __x86_64__
165	uint64_t hv_status = 0;
166	uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
167	uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
168	volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
169
170	__asm__ __volatile__ ("mov %0, %%r8" : : "r" (output_address): "r8");
171	__asm__ __volatile__ ("call *%3" : "=a"(hv_status):
172				"c" (control), "d" (input_address),
173				"m" (hypercall_page));
174	return (hv_status);
175#else
176	uint32_t control_high = control >> 32;
177	uint32_t control_low = control & 0xFFFFFFFF;
178	uint32_t hv_status_high = 1;
179	uint32_t hv_status_low = 1;
180	uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
181	uint32_t input_address_high = input_address >> 32;
182	uint32_t input_address_low = input_address & 0xFFFFFFFF;
183	uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
184	uint32_t output_address_high = output_address >> 32;
185	uint32_t output_address_low = output_address & 0xFFFFFFFF;
186	volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
187
188	__asm__ __volatile__ ("call *%8" : "=d"(hv_status_high),
189				"=a"(hv_status_low) : "d" (control_high),
190				"a" (control_low), "b" (input_address_high),
191				"c" (input_address_low),
192				"D"(output_address_high),
193				"S"(output_address_low), "m" (hypercall_page));
194	return (hv_status_low | ((uint64_t)hv_status_high << 32));
195#endif /* __x86_64__ */
196}
197
198/**
199 *  @brief Main initialization routine.
200 *
201 *  This routine must be called
202 *  before any other routines in here are called
203 */
204int
205hv_vmbus_init(void)
206{
207	int					max_leaf;
208	hv_vmbus_x64_msr_hypercall_contents	hypercall_msr;
209	void* 					virt_addr = 0;
210
211	memset(
212	    hv_vmbus_g_context.syn_ic_event_page,
213	    0,
214	    sizeof(hv_vmbus_handle) * MAXCPU);
215
216	memset(
217	    hv_vmbus_g_context.syn_ic_msg_page,
218	    0,
219	    sizeof(hv_vmbus_handle) * MAXCPU);
220
221	if (vm_guest != VM_GUEST_HV)
222	    goto cleanup;
223
224	max_leaf = hv_vmbus_get_hypervisor_version();
225
226	/*
227	 * Write our OS info
228	 */
229	uint64_t os_guest_info = HV_FREEBSD_GUEST_ID;
230	wrmsr(HV_X64_MSR_GUEST_OS_ID, os_guest_info);
231	hv_vmbus_g_context.guest_id = os_guest_info;
232
233	/*
234	 * See if the hypercall page is already set
235	 */
236	hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
237	virt_addr = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
238	KASSERT(virt_addr != NULL,
239	    ("Error VMBUS: malloc failed to allocate page during init!"));
240	if (virt_addr == NULL)
241	    goto cleanup;
242
243	hypercall_msr.u.enable = 1;
244	hypercall_msr.u.guest_physical_address =
245	    (hv_get_phys_addr(virt_addr) >> PAGE_SHIFT);
246	wrmsr(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64_t);
247
248	/*
249	 * Confirm that hypercall page did get set up
250	 */
251	hypercall_msr.as_uint64_t = 0;
252	hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
253
254	if (!hypercall_msr.u.enable)
255	    goto cleanup;
256
257	hv_vmbus_g_context.hypercall_page = virt_addr;
258
259	/*
260	 * Setup the global signal event param for the signal event hypercall
261	 */
262	hv_vmbus_g_context.signal_event_buffer =
263	    malloc(sizeof(hv_vmbus_input_signal_event_buffer), M_DEVBUF,
264		M_ZERO | M_NOWAIT);
265	KASSERT(hv_vmbus_g_context.signal_event_buffer != NULL,
266	    ("Error VMBUS: Failed to allocate signal_event_buffer\n"));
267	if (hv_vmbus_g_context.signal_event_buffer == NULL)
268	    goto cleanup;
269
270	hv_vmbus_g_context.signal_event_param =
271	    (hv_vmbus_input_signal_event*)
272	    (HV_ALIGN_UP((unsigned long)
273		hv_vmbus_g_context.signal_event_buffer,
274		HV_HYPERCALL_PARAM_ALIGN));
275	hv_vmbus_g_context.signal_event_param->connection_id.as_uint32_t = 0;
276	hv_vmbus_g_context.signal_event_param->connection_id.u.id =
277	    HV_VMBUS_EVENT_CONNECTION_ID;
278	hv_vmbus_g_context.signal_event_param->flag_number = 0;
279	hv_vmbus_g_context.signal_event_param->rsvd_z = 0;
280
281	tc_init(&hv_timecounter); /* register virtual timecount */
282
283	return (0);
284
285	cleanup:
286	if (virt_addr != NULL) {
287	    if (hypercall_msr.u.enable) {
288		hypercall_msr.as_uint64_t = 0;
289		wrmsr(HV_X64_MSR_HYPERCALL,
290					hypercall_msr.as_uint64_t);
291	    }
292
293	    free(virt_addr, M_DEVBUF);
294	}
295	return (ENOTSUP);
296}
297
298/**
299 * @brief Cleanup routine, called normally during driver unloading or exiting
300 */
301void
302hv_vmbus_cleanup(void)
303{
304	hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
305
306	if (hv_vmbus_g_context.signal_event_buffer != NULL) {
307	    free(hv_vmbus_g_context.signal_event_buffer, M_DEVBUF);
308	    hv_vmbus_g_context.signal_event_buffer = NULL;
309	    hv_vmbus_g_context.signal_event_param = NULL;
310	}
311
312	if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) {
313	    if (hv_vmbus_g_context.hypercall_page != NULL) {
314		hypercall_msr.as_uint64_t = 0;
315		wrmsr(HV_X64_MSR_HYPERCALL,
316					hypercall_msr.as_uint64_t);
317		free(hv_vmbus_g_context.hypercall_page, M_DEVBUF);
318		hv_vmbus_g_context.hypercall_page = NULL;
319	    }
320	}
321}
322
323/**
324 * @brief Post a message using the hypervisor message IPC.
325 * (This involves a hypercall.)
326 */
327hv_vmbus_status
328hv_vmbus_post_msg_via_msg_ipc(
329	hv_vmbus_connection_id	connection_id,
330	hv_vmbus_msg_type	message_type,
331	void*			payload,
332	size_t			payload_size)
333{
334	struct alignedinput {
335	    uint64_t alignment8;
336	    hv_vmbus_input_post_message msg;
337	};
338
339	hv_vmbus_input_post_message*	aligned_msg;
340	hv_vmbus_status 		status;
341	size_t				addr;
342
343	if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
344	    return (EMSGSIZE);
345
346	addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
347			    M_ZERO | M_NOWAIT);
348	KASSERT(addr != 0,
349	    ("Error VMBUS: malloc failed to allocate message buffer!"));
350	if (addr == 0)
351	    return (ENOMEM);
352
353	aligned_msg = (hv_vmbus_input_post_message*)
354	    (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
355
356	aligned_msg->connection_id = connection_id;
357	aligned_msg->message_type = message_type;
358	aligned_msg->payload_size = payload_size;
359	memcpy((void*) aligned_msg->payload, payload, payload_size);
360
361	status = hv_vmbus_do_hypercall(
362		    HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
363
364	free((void *) addr, M_DEVBUF);
365	return (status);
366}
367
368/**
369 * @brief Signal an event on the specified connection using the hypervisor
370 * event IPC. (This involves a hypercall.)
371 */
372hv_vmbus_status
373hv_vmbus_signal_event()
374{
375	hv_vmbus_status status;
376
377	status = hv_vmbus_do_hypercall(
378		    HV_CALL_SIGNAL_EVENT,
379		    hv_vmbus_g_context.signal_event_param,
380		    0) & 0xFFFF;
381
382	return (status);
383}
384
385/**
386 * @brief hv_vmbus_synic_init
387 */
388void
389hv_vmbus_synic_init(void *arg)
390
391{
392	int			cpu;
393	hv_vmbus_synic_simp	simp;
394	hv_vmbus_synic_siefp	siefp;
395	hv_vmbus_synic_scontrol sctrl;
396	hv_vmbus_synic_sint	shared_sint;
397	uint64_t		version;
398	hv_setup_args* 		setup_args = (hv_setup_args *)arg;
399
400	cpu = PCPU_GET(cpuid);
401
402	if (hv_vmbus_g_context.hypercall_page == NULL)
403	    return;
404
405	/*
406	 * KYS: Looks like we can only initialize on cpu0; don't we support
407	 * SMP guests?
408	 *
409	 * TODO: Need to add SMP support for FreeBSD V9
410	 */
411
412	if (cpu != 0)
413	    return;
414
415	/*
416	 * TODO: Check the version
417	 */
418	version = rdmsr(HV_X64_MSR_SVERSION);
419
420
421	hv_vmbus_g_context.syn_ic_msg_page[cpu] = setup_args->page_buffers[0];
422	hv_vmbus_g_context.syn_ic_event_page[cpu] = setup_args->page_buffers[1];
423
424	/*
425	 * Setup the Synic's message page
426	 */
427
428	simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
429	simp.u.simp_enabled = 1;
430	simp.u.base_simp_gpa = ((hv_get_phys_addr(
431	    hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT);
432
433	wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
434
435	/*
436	 * Setup the Synic's event page
437	 */
438	siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
439	siefp.u.siefp_enabled = 1;
440	siefp.u.base_siefp_gpa = ((hv_get_phys_addr(
441	    hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT);
442
443	wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
444
445	/*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
446	shared_sint.u.vector = setup_args->vector;
447	shared_sint.u.masked = FALSE;
448	shared_sint.u.auto_eoi = FALSE;
449
450	wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
451	    shared_sint.as_uint64_t);
452
453	/* Enable the global synic bit */
454	sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
455	sctrl.u.enable = 1;
456
457	wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
458
459	hv_vmbus_g_context.syn_ic_initialized = TRUE;
460
461	return;
462}
463
464/**
465 * @brief Cleanup routine for hv_vmbus_synic_init()
466 */
467void hv_vmbus_synic_cleanup(void *arg)
468{
469	hv_vmbus_synic_sint	shared_sint;
470	hv_vmbus_synic_simp	simp;
471	hv_vmbus_synic_siefp	siefp;
472	int			cpu = PCPU_GET(cpuid);
473
474	if (!hv_vmbus_g_context.syn_ic_initialized)
475	    return;
476
477	if (cpu != 0)
478	    return; /* TODO: XXXKYS: SMP? */
479
480	shared_sint.as_uint64_t = rdmsr(
481	    HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
482
483	shared_sint.u.masked = 1;
484
485	/*
486	 * Disable the interrupt
487	 */
488	wrmsr(
489	    HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
490	    shared_sint.as_uint64_t);
491
492	simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
493	simp.u.simp_enabled = 0;
494	simp.u.base_simp_gpa = 0;
495
496	wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
497
498	siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
499	siefp.u.siefp_enabled = 0;
500	siefp.u.base_siefp_gpa = 0;
501
502	wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
503}
504
505