Deleted Added
sdiff udiff text old ( 256425 ) new ( 282212 )
full compact
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 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};
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", &regs[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
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
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
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,
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 /*
377 * TODO: Check the version
378 */
379 version = rdmsr(HV_X64_MSR_SVERSION);
380
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;
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;
442
443 if (!hv_vmbus_g_context.syn_ic_initialized)
444 return;
445
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