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>
40 #include <machine/md_var.h>
42 #include <vm/vm_param.h>
46 #include "hv_vmbus_priv.h"
48 #define HV_NANOSECONDS_PER_SEC 1000000000L
51 static u_int hv_get_timecount(struct timecounter *tc);
56 hv_vmbus_context hv_vmbus_g_context = {
57 .syn_ic_initialized = FALSE,
58 .hypercall_page = NULL,
61 static struct timecounter hv_timecounter = {
62 hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
66 hv_get_timecount(struct timecounter *tc)
68 u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
73 * @brief Query the cpuid for presence of windows hypervisor
76 hv_vmbus_query_hypervisor_presence(void)
78 if (vm_guest != VM_GUEST_HV)
81 return (hv_high >= HV_X64_CPUID_MIN && hv_high <= HV_X64_CPUID_MAX);
85 * @brief Get version of the windows hypervisor
88 hv_vmbus_get_hypervisor_version(void)
95 * Its assumed that this is called after confirming that
97 * Query id and revision.
99 op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
103 op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
106 if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) {
107 op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
114 * @brief Invoke the specified hypercall
117 hv_vmbus_do_hypercall(uint64_t control, void* input, void* output)
120 uint64_t hv_status = 0;
121 uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
122 uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
123 volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
125 __asm__ __volatile__ ("mov %0, %%r8" : : "r" (output_address): "r8");
126 __asm__ __volatile__ ("call *%3" : "=a"(hv_status):
127 "c" (control), "d" (input_address),
128 "m" (hypercall_page));
131 uint32_t control_high = control >> 32;
132 uint32_t control_low = control & 0xFFFFFFFF;
133 uint32_t hv_status_high = 1;
134 uint32_t hv_status_low = 1;
135 uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
136 uint32_t input_address_high = input_address >> 32;
137 uint32_t input_address_low = input_address & 0xFFFFFFFF;
138 uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
139 uint32_t output_address_high = output_address >> 32;
140 uint32_t output_address_low = output_address & 0xFFFFFFFF;
141 volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
143 __asm__ __volatile__ ("call *%8" : "=d"(hv_status_high),
144 "=a"(hv_status_low) : "d" (control_high),
145 "a" (control_low), "b" (input_address_high),
146 "c" (input_address_low),
147 "D"(output_address_high),
148 "S"(output_address_low), "m" (hypercall_page));
149 return (hv_status_low | ((uint64_t)hv_status_high << 32));
150 #endif /* __x86_64__ */
154 * @brief Main initialization routine.
156 * This routine must be called
157 * before any other routines in here are called
163 hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
167 hv_vmbus_g_context.syn_ic_event_page,
169 sizeof(hv_vmbus_handle) * MAXCPU);
172 hv_vmbus_g_context.syn_ic_msg_page,
174 sizeof(hv_vmbus_handle) * MAXCPU);
176 if (vm_guest != VM_GUEST_HV)
179 max_leaf = hv_vmbus_get_hypervisor_version();
184 uint64_t os_guest_info = HV_FREEBSD_GUEST_ID;
185 wrmsr(HV_X64_MSR_GUEST_OS_ID, os_guest_info);
186 hv_vmbus_g_context.guest_id = os_guest_info;
189 * See if the hypercall page is already set
191 hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
192 virt_addr = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
193 KASSERT(virt_addr != NULL,
194 ("Error VMBUS: malloc failed to allocate page during init!"));
195 if (virt_addr == NULL)
198 hypercall_msr.u.enable = 1;
199 hypercall_msr.u.guest_physical_address =
200 (hv_get_phys_addr(virt_addr) >> PAGE_SHIFT);
201 wrmsr(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64_t);
204 * Confirm that hypercall page did get set up
206 hypercall_msr.as_uint64_t = 0;
207 hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
209 if (!hypercall_msr.u.enable)
212 hv_vmbus_g_context.hypercall_page = virt_addr;
214 tc_init(&hv_timecounter); /* register virtual timecount */
221 if (virt_addr != NULL) {
222 if (hypercall_msr.u.enable) {
223 hypercall_msr.as_uint64_t = 0;
224 wrmsr(HV_X64_MSR_HYPERCALL,
225 hypercall_msr.as_uint64_t);
228 free(virt_addr, M_DEVBUF);
234 * @brief Cleanup routine, called normally during driver unloading or exiting
237 hv_vmbus_cleanup(void)
239 hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
241 if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) {
242 if (hv_vmbus_g_context.hypercall_page != NULL) {
243 hypercall_msr.as_uint64_t = 0;
244 wrmsr(HV_X64_MSR_HYPERCALL,
245 hypercall_msr.as_uint64_t);
246 free(hv_vmbus_g_context.hypercall_page, M_DEVBUF);
247 hv_vmbus_g_context.hypercall_page = NULL;
253 * @brief Post a message using the hypervisor message IPC.
254 * (This involves a hypercall.)
257 hv_vmbus_post_msg_via_msg_ipc(
258 hv_vmbus_connection_id connection_id,
259 hv_vmbus_msg_type message_type,
263 struct alignedinput {
265 hv_vmbus_input_post_message msg;
268 hv_vmbus_input_post_message* aligned_msg;
269 hv_vmbus_status status;
272 if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
275 addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
278 ("Error VMBUS: malloc failed to allocate message buffer!"));
282 aligned_msg = (hv_vmbus_input_post_message*)
283 (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
285 aligned_msg->connection_id = connection_id;
286 aligned_msg->message_type = message_type;
287 aligned_msg->payload_size = payload_size;
288 memcpy((void*) aligned_msg->payload, payload, payload_size);
290 status = hv_vmbus_do_hypercall(
291 HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
293 free((void *) addr, M_DEVBUF);
298 * @brief Signal an event on the specified connection using the hypervisor
299 * event IPC. (This involves a hypercall.)
302 hv_vmbus_signal_event(void *con_id)
304 hv_vmbus_status status;
306 status = hv_vmbus_do_hypercall(
307 HV_CALL_SIGNAL_EVENT,
315 * @brief hv_vmbus_synic_init
318 hv_vmbus_synic_init(void *arg)
322 uint64_t hv_vcpu_index;
323 hv_vmbus_synic_simp simp;
324 hv_vmbus_synic_siefp siefp;
325 hv_vmbus_synic_scontrol sctrl;
326 hv_vmbus_synic_sint shared_sint;
328 hv_setup_args* setup_args = (hv_setup_args *)arg;
330 cpu = PCPU_GET(cpuid);
332 if (hv_vmbus_g_context.hypercall_page == NULL)
336 * TODO: Check the version
338 version = rdmsr(HV_X64_MSR_SVERSION);
340 hv_vmbus_g_context.syn_ic_msg_page[cpu] =
341 setup_args->page_buffers[2 * cpu];
342 hv_vmbus_g_context.syn_ic_event_page[cpu] =
343 setup_args->page_buffers[2 * cpu + 1];
346 * Setup the Synic's message page
349 simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
350 simp.u.simp_enabled = 1;
351 simp.u.base_simp_gpa = ((hv_get_phys_addr(
352 hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT);
354 wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
357 * Setup the Synic's event page
359 siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
360 siefp.u.siefp_enabled = 1;
361 siefp.u.base_siefp_gpa = ((hv_get_phys_addr(
362 hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT);
364 wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
366 /*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
367 shared_sint.as_uint64_t = 0;
368 shared_sint.u.vector = setup_args->vector;
369 shared_sint.u.masked = FALSE;
370 shared_sint.u.auto_eoi = TRUE;
372 wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
373 shared_sint.as_uint64_t);
375 /* Enable the global synic bit */
376 sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
379 wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
381 hv_vmbus_g_context.syn_ic_initialized = TRUE;
384 * Set up the cpuid mapping from Hyper-V to FreeBSD.
385 * The array is indexed using FreeBSD cpuid.
387 hv_vcpu_index = rdmsr(HV_X64_MSR_VP_INDEX);
388 hv_vmbus_g_context.hv_vcpu_index[cpu] = (uint32_t)hv_vcpu_index;
394 * @brief Cleanup routine for hv_vmbus_synic_init()
396 void hv_vmbus_synic_cleanup(void *arg)
398 hv_vmbus_synic_sint shared_sint;
399 hv_vmbus_synic_simp simp;
400 hv_vmbus_synic_siefp siefp;
402 if (!hv_vmbus_g_context.syn_ic_initialized)
405 shared_sint.as_uint64_t = rdmsr(
406 HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
408 shared_sint.u.masked = 1;
411 * Disable the interrupt
414 HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
415 shared_sint.as_uint64_t);
417 simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
418 simp.u.simp_enabled = 0;
419 simp.u.base_simp_gpa = 0;
421 wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
423 siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
424 siefp.u.siefp_enabled = 0;
425 siefp.u.base_siefp_gpa = 0;
427 wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);