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", ®s[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