]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - sys/dev/hyperv/vmbus/hv_hv.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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 };
71
72 static struct timecounter hv_timecounter = {
73         hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
74 };
75
76 static u_int
77 hv_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  */
86 int
87 hv_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  */
115 static int
116 hv_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  */
159 static uint64_t
160 hv_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  */
202 int
203 hv_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  */
277 void
278 hv_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  */
297 hv_vmbus_status
298 hv_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  */
342 hv_vmbus_status
343 hv_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  */
358 void
359 hv_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  */
437 void 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