]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - sys/dev/hyperv/vmbus/hv_hv.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.git] / sys / dev / hyperv / vmbus / hv_hv.c
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
56 static u_int hv_get_timecount(struct timecounter *tc);
57
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));
62 }
63
64 /**
65  * Globals
66  */
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,
72 };
73
74 static struct timecounter hv_timecounter = {
75         hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
76 };
77
78 static u_int
79 hv_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  */
88 int
89 hv_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", &regs[1], 12);
110         }
111         return (hyper_v_detected);
112 }
113
114 /**
115  * @brief Get version of the windows hypervisor
116  */
117 static int
118 hv_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  */
161 static uint64_t
162 hv_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  */
204 int
205 hv_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 (!hv_vmbus_query_hypervisor_presence())
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  */
301 void
302 hv_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  */
327 hv_vmbus_status
328 hv_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  */
372 hv_vmbus_status
373 hv_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  */
388 void
389 hv_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  */
467 void 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