2 * Copyright (c) 2009-2012 Microsoft Corp.
3 * Copyright (c) 2012 NetApp Inc.
4 * Copyright (c) 2012 Citrix Inc.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice unmodified, this list of conditions, and the following
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.
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.
30 * Implements low-level interactions with Hypver-V/Azure
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
35 #include <sys/param.h>
36 #include <sys/malloc.h>
38 #include <sys/timetc.h>
39 #include <machine/bus.h>
41 #include <vm/vm_param.h>
45 #include "hv_vmbus_priv.h"
47 #define HV_X64_MSR_GUEST_OS_ID 0x40000000
49 #define HV_X64_CPUID_MIN 0x40000005
50 #define HV_X64_CPUID_MAX 0x4000ffff
51 #define HV_X64_MSR_TIME_REF_COUNT 0x40000020
53 #define HV_NANOSECONDS_PER_SEC 1000000000L
56 static u_int hv_get_timecount(struct timecounter *tc);
58 static 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));
67 hv_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,
74 static struct timecounter hv_timecounter = {
75 hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
79 hv_get_timecount(struct timecounter *tc)
81 u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
86 * @brief Query the cpuid for presence of windows hypervisor
89 hv_vmbus_query_hypervisor_presence(void)
92 int hyper_v_detected = 0;
95 * When Xen is detected and native Xen PV support is enabled,
96 * ignore Xen's HyperV emulation.
98 if (vm_guest == VM_GUEST_XEN)
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);
107 regs[0] >= HV_X64_CPUID_MIN &&
108 regs[0] <= HV_X64_CPUID_MAX &&
109 !memcmp("Microsoft Hv", ®s[1], 12);
111 return (hyper_v_detected);
115 * @brief Get version of the windows hypervisor
118 hv_vmbus_get_hypervisor_version(void)
124 unsigned int maxLeaf;
128 * Its assumed that this is called after confirming that
129 * Viridian is present
130 * Query id and revision.
136 op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
137 do_cpuid_inline(op, &eax, &ebx, &ecx, &edx);
144 op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
145 do_cpuid_inline(op, &eax, &ebx, &ecx, &edx);
147 if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) {
152 op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
153 do_cpuid_inline(op, &eax, &ebx, &ecx, &edx);
159 * @brief Invoke the specified hypercall
162 hv_vmbus_do_hypercall(uint64_t control, void* input, void* output)
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;
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));
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;
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__ */
199 * @brief Main initialization routine.
201 * This routine must be called
202 * before any other routines in here are called
208 hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
212 hv_vmbus_g_context.syn_ic_event_page,
214 sizeof(hv_vmbus_handle) * MAXCPU);
217 hv_vmbus_g_context.syn_ic_msg_page,
219 sizeof(hv_vmbus_handle) * MAXCPU);
221 if (vm_guest != VM_GUEST_HV)
224 max_leaf = hv_vmbus_get_hypervisor_version();
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;
234 * See if the hypercall page is already set
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)
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);
249 * Confirm that hypercall page did get set up
251 hypercall_msr.as_uint64_t = 0;
252 hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
254 if (!hypercall_msr.u.enable)
257 hv_vmbus_g_context.hypercall_page = virt_addr;
260 * Setup the global signal event param for the signal event hypercall
262 hv_vmbus_g_context.signal_event_buffer =
263 malloc(sizeof(hv_vmbus_input_signal_event_buffer), M_DEVBUF,
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)
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;
281 tc_init(&hv_timecounter); /* register virtual timecount */
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);
293 free(virt_addr, M_DEVBUF);
299 * @brief Cleanup routine, called normally during driver unloading or exiting
302 hv_vmbus_cleanup(void)
304 hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
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;
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;
324 * @brief Post a message using the hypervisor message IPC.
325 * (This involves a hypercall.)
328 hv_vmbus_post_msg_via_msg_ipc(
329 hv_vmbus_connection_id connection_id,
330 hv_vmbus_msg_type message_type,
334 struct alignedinput {
336 hv_vmbus_input_post_message msg;
339 hv_vmbus_input_post_message* aligned_msg;
340 hv_vmbus_status status;
343 if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
346 addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
349 ("Error VMBUS: malloc failed to allocate message buffer!"));
353 aligned_msg = (hv_vmbus_input_post_message*)
354 (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
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);
361 status = hv_vmbus_do_hypercall(
362 HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
364 free((void *) addr, M_DEVBUF);
369 * @brief Signal an event on the specified connection using the hypervisor
370 * event IPC. (This involves a hypercall.)
373 hv_vmbus_signal_event()
375 hv_vmbus_status status;
377 status = hv_vmbus_do_hypercall(
378 HV_CALL_SIGNAL_EVENT,
379 hv_vmbus_g_context.signal_event_param,
386 * @brief hv_vmbus_synic_init
389 hv_vmbus_synic_init(void *arg)
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;
398 hv_setup_args* setup_args = (hv_setup_args *)arg;
400 cpu = PCPU_GET(cpuid);
402 if (hv_vmbus_g_context.hypercall_page == NULL)
406 * KYS: Looks like we can only initialize on cpu0; don't we support
409 * TODO: Need to add SMP support for FreeBSD V9
416 * TODO: Check the version
418 version = rdmsr(HV_X64_MSR_SVERSION);
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];
425 * Setup the Synic's message page
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);
433 wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
436 * Setup the Synic's event page
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);
443 wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
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;
450 wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
451 shared_sint.as_uint64_t);
453 /* Enable the global synic bit */
454 sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
457 wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
459 hv_vmbus_g_context.syn_ic_initialized = TRUE;
465 * @brief Cleanup routine for hv_vmbus_synic_init()
467 void hv_vmbus_synic_cleanup(void *arg)
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);
474 if (!hv_vmbus_g_context.syn_ic_initialized)
478 return; /* TODO: XXXKYS: SMP? */
480 shared_sint.as_uint64_t = rdmsr(
481 HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
483 shared_sint.u.masked = 1;
486 * Disable the interrupt
489 HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
490 shared_sint.as_uint64_t);
492 simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
493 simp.u.simp_enabled = 0;
494 simp.u.base_simp_gpa = 0;
496 wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
498 siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
499 siefp.u.siefp_enabled = 0;
500 siefp.u.base_siefp_gpa = 0;
502 wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);