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: head/sys/dev/hyperv/vmbus/hv_hv.c 256425 2013-10-13 02:41:30Z gibbs $");
|
33__FBSDID("$FreeBSD: head/sys/dev/hyperv/vmbus/hv_hv.c 282212 2015-04-29 10:12:34Z whu $"); |
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,
|
70}; 71 72static struct timecounter hv_timecounter = { 73 hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100 74}; 75 76static u_int 77hv_get_timecount(struct timecounter *tc) 78{ 79 u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT); 80 return (now); 81} 82 83/** 84 * @brief Query the cpuid for presence of windows hypervisor 85 */ 86int 87hv_vmbus_query_hypervisor_presence(void) 88{ 89 u_int regs[4]; 90 int hyper_v_detected = 0; 91 92 /* 93 * When Xen is detected and native Xen PV support is enabled, 94 * ignore Xen's HyperV emulation. 95 */ 96 if (vm_guest == VM_GUEST_XEN) 97 return (0); 98 99 do_cpuid(1, regs); 100 if (regs[2] & 0x80000000) { /* if(a hypervisor is detected) */ 101 /* make sure this really is Hyper-V */ 102 /* we look at the CPUID info */ 103 do_cpuid(HV_X64_MSR_GUEST_OS_ID, regs); 104 hyper_v_detected = 105 regs[0] >= HV_X64_CPUID_MIN && 106 regs[0] <= HV_X64_CPUID_MAX && 107 !memcmp("Microsoft Hv", ®s[1], 12); 108 } 109 return (hyper_v_detected); 110} 111 112/** 113 * @brief Get version of the windows hypervisor 114 */ 115static int 116hv_vmbus_get_hypervisor_version(void) 117{ 118 unsigned int eax; 119 unsigned int ebx; 120 unsigned int ecx; 121 unsigned int edx; 122 unsigned int maxLeaf; 123 unsigned int op; 124 125 /* 126 * Its assumed that this is called after confirming that 127 * Viridian is present 128 * Query id and revision. 129 */ 130 eax = 0; 131 ebx = 0; 132 ecx = 0; 133 edx = 0; 134 op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION; 135 do_cpuid_inline(op, &eax, &ebx, &ecx, &edx); 136 137 maxLeaf = eax; 138 eax = 0; 139 ebx = 0; 140 ecx = 0; 141 edx = 0; 142 op = HV_CPU_ID_FUNCTION_HV_INTERFACE; 143 do_cpuid_inline(op, &eax, &ebx, &ecx, &edx); 144 145 if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) { 146 eax = 0; 147 ebx = 0; 148 ecx = 0; 149 edx = 0; 150 op = HV_CPU_ID_FUNCTION_MS_HV_VERSION; 151 do_cpuid_inline(op, &eax, &ebx, &ecx, &edx); 152 } 153 return (maxLeaf); 154} 155 156/** 157 * @brief Invoke the specified hypercall 158 */ 159static uint64_t 160hv_vmbus_do_hypercall(uint64_t control, void* input, void* output) 161{ 162#ifdef __x86_64__ 163 uint64_t hv_status = 0; 164 uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0; 165 uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0; 166 volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page; 167 168 __asm__ __volatile__ ("mov %0, %%r8" : : "r" (output_address): "r8"); 169 __asm__ __volatile__ ("call *%3" : "=a"(hv_status): 170 "c" (control), "d" (input_address), 171 "m" (hypercall_page)); 172 return (hv_status); 173#else 174 uint32_t control_high = control >> 32; 175 uint32_t control_low = control & 0xFFFFFFFF; 176 uint32_t hv_status_high = 1; 177 uint32_t hv_status_low = 1; 178 uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0; 179 uint32_t input_address_high = input_address >> 32; 180 uint32_t input_address_low = input_address & 0xFFFFFFFF; 181 uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0; 182 uint32_t output_address_high = output_address >> 32; 183 uint32_t output_address_low = output_address & 0xFFFFFFFF; 184 volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page; 185 186 __asm__ __volatile__ ("call *%8" : "=d"(hv_status_high), 187 "=a"(hv_status_low) : "d" (control_high), 188 "a" (control_low), "b" (input_address_high), 189 "c" (input_address_low), 190 "D"(output_address_high), 191 "S"(output_address_low), "m" (hypercall_page)); 192 return (hv_status_low | ((uint64_t)hv_status_high << 32)); 193#endif /* __x86_64__ */ 194} 195 196/** 197 * @brief Main initialization routine. 198 * 199 * This routine must be called 200 * before any other routines in here are called 201 */ 202int 203hv_vmbus_init(void) 204{ 205 int max_leaf; 206 hv_vmbus_x64_msr_hypercall_contents hypercall_msr; 207 void* virt_addr = 0; 208 209 memset( 210 hv_vmbus_g_context.syn_ic_event_page, 211 0, 212 sizeof(hv_vmbus_handle) * MAXCPU); 213 214 memset( 215 hv_vmbus_g_context.syn_ic_msg_page, 216 0, 217 sizeof(hv_vmbus_handle) * MAXCPU); 218 219 if (vm_guest != VM_GUEST_HV) 220 goto cleanup; 221 222 max_leaf = hv_vmbus_get_hypervisor_version(); 223 224 /* 225 * Write our OS info 226 */ 227 uint64_t os_guest_info = HV_FREEBSD_GUEST_ID; 228 wrmsr(HV_X64_MSR_GUEST_OS_ID, os_guest_info); 229 hv_vmbus_g_context.guest_id = os_guest_info; 230 231 /* 232 * See if the hypercall page is already set 233 */ 234 hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL); 235 virt_addr = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO); 236 KASSERT(virt_addr != NULL, 237 ("Error VMBUS: malloc failed to allocate page during init!")); 238 if (virt_addr == NULL) 239 goto cleanup; 240 241 hypercall_msr.u.enable = 1; 242 hypercall_msr.u.guest_physical_address = 243 (hv_get_phys_addr(virt_addr) >> PAGE_SHIFT); 244 wrmsr(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64_t); 245 246 /* 247 * Confirm that hypercall page did get set up 248 */ 249 hypercall_msr.as_uint64_t = 0; 250 hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL); 251 252 if (!hypercall_msr.u.enable) 253 goto cleanup; 254 255 hv_vmbus_g_context.hypercall_page = virt_addr; 256
|
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
|
257 tc_init(&hv_timecounter); /* register virtual timecount */ 258 259 return (0); 260 261 cleanup: 262 if (virt_addr != NULL) { 263 if (hypercall_msr.u.enable) { 264 hypercall_msr.as_uint64_t = 0; 265 wrmsr(HV_X64_MSR_HYPERCALL, 266 hypercall_msr.as_uint64_t); 267 } 268 269 free(virt_addr, M_DEVBUF); 270 } 271 return (ENOTSUP); 272} 273 274/** 275 * @brief Cleanup routine, called normally during driver unloading or exiting 276 */ 277void 278hv_vmbus_cleanup(void) 279{ 280 hv_vmbus_x64_msr_hypercall_contents hypercall_msr; 281
|
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
|
282 if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) { 283 if (hv_vmbus_g_context.hypercall_page != NULL) { 284 hypercall_msr.as_uint64_t = 0; 285 wrmsr(HV_X64_MSR_HYPERCALL, 286 hypercall_msr.as_uint64_t); 287 free(hv_vmbus_g_context.hypercall_page, M_DEVBUF); 288 hv_vmbus_g_context.hypercall_page = NULL; 289 } 290 } 291} 292 293/** 294 * @brief Post a message using the hypervisor message IPC. 295 * (This involves a hypercall.) 296 */ 297hv_vmbus_status 298hv_vmbus_post_msg_via_msg_ipc( 299 hv_vmbus_connection_id connection_id, 300 hv_vmbus_msg_type message_type, 301 void* payload, 302 size_t payload_size) 303{ 304 struct alignedinput { 305 uint64_t alignment8; 306 hv_vmbus_input_post_message msg; 307 }; 308 309 hv_vmbus_input_post_message* aligned_msg; 310 hv_vmbus_status status; 311 size_t addr; 312 313 if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT) 314 return (EMSGSIZE); 315 316 addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF, 317 M_ZERO | M_NOWAIT); 318 KASSERT(addr != 0, 319 ("Error VMBUS: malloc failed to allocate message buffer!")); 320 if (addr == 0) 321 return (ENOMEM); 322 323 aligned_msg = (hv_vmbus_input_post_message*) 324 (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN)); 325 326 aligned_msg->connection_id = connection_id; 327 aligned_msg->message_type = message_type; 328 aligned_msg->payload_size = payload_size; 329 memcpy((void*) aligned_msg->payload, payload, payload_size); 330 331 status = hv_vmbus_do_hypercall( 332 HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF; 333 334 free((void *) addr, M_DEVBUF); 335 return (status); 336} 337 338/** 339 * @brief Signal an event on the specified connection using the hypervisor 340 * event IPC. (This involves a hypercall.) 341 */ 342hv_vmbus_status
|
373hv_vmbus_signal_event()
|
343hv_vmbus_signal_event(void *con_id) |
344{ 345 hv_vmbus_status status; 346 347 status = hv_vmbus_do_hypercall( 348 HV_CALL_SIGNAL_EVENT,
|
379 hv_vmbus_g_context.signal_event_param,
|
349 con_id, |
350 0) & 0xFFFF; 351 352 return (status); 353} 354 355/** 356 * @brief hv_vmbus_synic_init 357 */ 358void 359hv_vmbus_synic_init(void *arg) 360 361{ 362 int cpu;
|
363 uint64_t hv_vcpu_index; |
364 hv_vmbus_synic_simp simp; 365 hv_vmbus_synic_siefp siefp; 366 hv_vmbus_synic_scontrol sctrl; 367 hv_vmbus_synic_sint shared_sint; 368 uint64_t version; 369 hv_setup_args* setup_args = (hv_setup_args *)arg; 370 371 cpu = PCPU_GET(cpuid); 372 373 if (hv_vmbus_g_context.hypercall_page == NULL) 374 return; 375 376 /*
|
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 /*
|
377 * TODO: Check the version 378 */ 379 version = rdmsr(HV_X64_MSR_SVERSION);
|
419
|
380
|
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];
|
381 hv_vmbus_g_context.syn_ic_msg_page[cpu] = 382 setup_args->page_buffers[2 * cpu]; 383 hv_vmbus_g_context.syn_ic_event_page[cpu] = 384 setup_args->page_buffers[2 * cpu + 1]; |
385 386 /* 387 * Setup the Synic's message page 388 */ 389 390 simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP); 391 simp.u.simp_enabled = 1; 392 simp.u.base_simp_gpa = ((hv_get_phys_addr( 393 hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT); 394 395 wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t); 396 397 /* 398 * Setup the Synic's event page 399 */ 400 siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP); 401 siefp.u.siefp_enabled = 1; 402 siefp.u.base_siefp_gpa = ((hv_get_phys_addr( 403 hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT); 404 405 wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t); 406 407 /*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
|
408 shared_sint.as_uint64_t = 0; |
409 shared_sint.u.vector = setup_args->vector; 410 shared_sint.u.masked = FALSE;
|
448 shared_sint.u.auto_eoi = FALSE;
|
411 shared_sint.u.auto_eoi = TRUE; |
412 413 wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT, 414 shared_sint.as_uint64_t); 415 416 /* Enable the global synic bit */ 417 sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL); 418 sctrl.u.enable = 1; 419 420 wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t); 421 422 hv_vmbus_g_context.syn_ic_initialized = TRUE; 423
|
424 /* 425 * Set up the cpuid mapping from Hyper-V to FreeBSD. 426 * The array is indexed using FreeBSD cpuid. 427 */ 428 hv_vcpu_index = rdmsr(HV_X64_MSR_VP_INDEX); 429 hv_vmbus_g_context.hv_vcpu_index[cpu] = (uint32_t)hv_vcpu_index; 430 |
431 return; 432} 433 434/** 435 * @brief Cleanup routine for hv_vmbus_synic_init() 436 */ 437void hv_vmbus_synic_cleanup(void *arg) 438{ 439 hv_vmbus_synic_sint shared_sint; 440 hv_vmbus_synic_simp simp; 441 hv_vmbus_synic_siefp siefp;
|
472 int cpu = PCPU_GET(cpuid);
|
442 443 if (!hv_vmbus_g_context.syn_ic_initialized) 444 return; 445
|
477 if (cpu != 0)
478 return; /* TODO: XXXKYS: SMP? */
479
|
446 shared_sint.as_uint64_t = rdmsr( 447 HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT); 448 449 shared_sint.u.masked = 1; 450 451 /* 452 * Disable the interrupt 453 */ 454 wrmsr( 455 HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT, 456 shared_sint.as_uint64_t); 457 458 simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP); 459 simp.u.simp_enabled = 0; 460 simp.u.base_simp_gpa = 0; 461 462 wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t); 463 464 siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP); 465 siefp.u.siefp_enabled = 0; 466 siefp.u.base_siefp_gpa = 0; 467 468 wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t); 469} 470
|