]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - sys/dev/hyperv/vmbus/hv_hv.c
Release 6 errata notices for 10.3-RELEASE, all related to Microsoft Hyper-V.
[FreeBSD/releng/10.3.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/kernel.h>
37 #include <sys/malloc.h>
38 #include <sys/pcpu.h>
39 #include <sys/timetc.h>
40 #include <machine/bus.h>
41 #include <machine/md_var.h>
42 #include <vm/vm.h>
43 #include <vm/vm_param.h>
44 #include <vm/pmap.h>
45
46
47 #include "hv_vmbus_priv.h"
48
49 #define HV_NANOSECONDS_PER_SEC          1000000000L
50
51
52 static u_int hv_get_timecount(struct timecounter *tc);
53
54 u_int   hyperv_features;
55 u_int   hyperv_recommends;
56
57 /**
58  * Globals
59  */
60 hv_vmbus_context hv_vmbus_g_context = {
61         .syn_ic_initialized = FALSE,
62         .hypercall_page = NULL,
63 };
64
65 static struct timecounter hv_timecounter = {
66         hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
67 };
68
69 static u_int
70 hv_get_timecount(struct timecounter *tc)
71 {
72         u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
73         return (now);
74 }
75
76 /**
77  * @brief Query the cpuid for presence of windows hypervisor
78  */
79 int
80 hv_vmbus_query_hypervisor_presence(void) 
81 {
82         if (vm_guest != VM_GUEST_HV)
83                 return (0);
84
85         return (hv_high >= HV_X64_CPUID_MIN && hv_high <= HV_X64_CPUID_MAX);
86 }
87
88 /**
89  * @brief Get version of the windows hypervisor
90  */
91 static int
92 hv_vmbus_get_hypervisor_version(void) 
93 {
94         u_int regs[4];
95         unsigned int maxLeaf;
96         unsigned int op;
97
98         /*
99          * Its assumed that this is called after confirming that
100          * Viridian is present
101          * Query id and revision.
102          */
103         op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
104         do_cpuid(op, regs);
105
106         maxLeaf = regs[0];
107         op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
108         do_cpuid(op, regs);
109
110         if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) {
111             op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
112             do_cpuid(op, regs);
113         }
114         return (maxLeaf);
115 }
116
117 /**
118  * @brief Invoke the specified hypercall
119  */
120 static uint64_t
121 hv_vmbus_do_hypercall(uint64_t control, void* input, void* output)
122 {
123 #ifdef __x86_64__
124         uint64_t hv_status = 0;
125         uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
126         uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
127         volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
128
129         __asm__ __volatile__ ("mov %0, %%r8" : : "r" (output_address): "r8");
130         __asm__ __volatile__ ("call *%3" : "=a"(hv_status):
131                                 "c" (control), "d" (input_address),
132                                 "m" (hypercall_page));
133         return (hv_status);
134 #else
135         uint32_t control_high = control >> 32;
136         uint32_t control_low = control & 0xFFFFFFFF;
137         uint32_t hv_status_high = 1;
138         uint32_t hv_status_low = 1;
139         uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
140         uint32_t input_address_high = input_address >> 32;
141         uint32_t input_address_low = input_address & 0xFFFFFFFF;
142         uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
143         uint32_t output_address_high = output_address >> 32;
144         uint32_t output_address_low = output_address & 0xFFFFFFFF;
145         volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
146
147         __asm__ __volatile__ ("call *%8" : "=d"(hv_status_high),
148                                 "=a"(hv_status_low) : "d" (control_high),
149                                 "a" (control_low), "b" (input_address_high),
150                                 "c" (input_address_low),
151                                 "D"(output_address_high),
152                                 "S"(output_address_low), "m" (hypercall_page));
153         return (hv_status_low | ((uint64_t)hv_status_high << 32));
154 #endif /* __x86_64__ */
155 }
156
157 /**
158  *  @brief Main initialization routine.
159  *
160  *  This routine must be called
161  *  before any other routines in here are called
162  */
163 int
164 hv_vmbus_init(void) 
165 {
166         int                                     max_leaf;
167         hv_vmbus_x64_msr_hypercall_contents     hypercall_msr;
168         void*                                   virt_addr = 0;
169
170         memset(
171             hv_vmbus_g_context.syn_ic_event_page,
172             0,
173             sizeof(hv_vmbus_handle) * MAXCPU);
174
175         memset(
176             hv_vmbus_g_context.syn_ic_msg_page,
177             0,
178             sizeof(hv_vmbus_handle) * MAXCPU);
179
180         if (vm_guest != VM_GUEST_HV)
181             goto cleanup;
182
183         max_leaf = hv_vmbus_get_hypervisor_version();
184
185         /*
186          * Write our OS info
187          */
188         uint64_t os_guest_info = HV_FREEBSD_GUEST_ID;
189         wrmsr(HV_X64_MSR_GUEST_OS_ID, os_guest_info);
190         hv_vmbus_g_context.guest_id = os_guest_info;
191
192         /*
193          * See if the hypercall page is already set
194          */
195         hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
196         virt_addr = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
197         KASSERT(virt_addr != NULL,
198             ("Error VMBUS: malloc failed to allocate page during init!"));
199         if (virt_addr == NULL)
200             goto cleanup;
201
202         hypercall_msr.u.enable = 1;
203         hypercall_msr.u.guest_physical_address =
204             (hv_get_phys_addr(virt_addr) >> PAGE_SHIFT);
205         wrmsr(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64_t);
206
207         /*
208          * Confirm that hypercall page did get set up
209          */
210         hypercall_msr.as_uint64_t = 0;
211         hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
212
213         if (!hypercall_msr.u.enable)
214             goto cleanup;
215
216         hv_vmbus_g_context.hypercall_page = virt_addr;
217
218         hv_et_init();
219         
220         return (0);
221
222         cleanup:
223         if (virt_addr != NULL) {
224             if (hypercall_msr.u.enable) {
225                 hypercall_msr.as_uint64_t = 0;
226                 wrmsr(HV_X64_MSR_HYPERCALL,
227                                         hypercall_msr.as_uint64_t);
228             }
229
230             free(virt_addr, M_DEVBUF);
231         }
232         return (ENOTSUP);
233 }
234
235 /**
236  * @brief Cleanup routine, called normally during driver unloading or exiting
237  */
238 void
239 hv_vmbus_cleanup(void) 
240 {
241         hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
242
243         if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) {
244             if (hv_vmbus_g_context.hypercall_page != NULL) {
245                 hypercall_msr.as_uint64_t = 0;
246                 wrmsr(HV_X64_MSR_HYPERCALL,
247                                         hypercall_msr.as_uint64_t);
248                 free(hv_vmbus_g_context.hypercall_page, M_DEVBUF);
249                 hv_vmbus_g_context.hypercall_page = NULL;
250             }
251         }
252 }
253
254 /**
255  * @brief Post a message using the hypervisor message IPC.
256  * (This involves a hypercall.)
257  */
258 hv_vmbus_status
259 hv_vmbus_post_msg_via_msg_ipc(
260         hv_vmbus_connection_id  connection_id,
261         hv_vmbus_msg_type       message_type,
262         void*                   payload,
263         size_t                  payload_size)
264 {
265         struct alignedinput {
266             uint64_t alignment8;
267             hv_vmbus_input_post_message msg;
268         };
269
270         hv_vmbus_input_post_message*    aligned_msg;
271         hv_vmbus_status                 status;
272         size_t                          addr;
273
274         if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
275             return (EMSGSIZE);
276
277         addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
278                             M_ZERO | M_NOWAIT);
279         KASSERT(addr != 0,
280             ("Error VMBUS: malloc failed to allocate message buffer!"));
281         if (addr == 0)
282             return (ENOMEM);
283
284         aligned_msg = (hv_vmbus_input_post_message*)
285             (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
286
287         aligned_msg->connection_id = connection_id;
288         aligned_msg->message_type = message_type;
289         aligned_msg->payload_size = payload_size;
290         memcpy((void*) aligned_msg->payload, payload, payload_size);
291
292         status = hv_vmbus_do_hypercall(
293                     HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
294
295         free((void *) addr, M_DEVBUF);
296         return (status);
297 }
298
299 /**
300  * @brief Signal an event on the specified connection using the hypervisor
301  * event IPC. (This involves a hypercall.)
302  */
303 hv_vmbus_status
304 hv_vmbus_signal_event(void *con_id)
305 {
306         hv_vmbus_status status;
307
308         status = hv_vmbus_do_hypercall(
309                     HV_CALL_SIGNAL_EVENT,
310                     con_id,
311                     0) & 0xFFFF;
312
313         return (status);
314 }
315
316 /**
317  * @brief hv_vmbus_synic_init
318  */
319 void
320 hv_vmbus_synic_init(void *arg)
321
322 {
323         int                     cpu;
324         uint64_t                hv_vcpu_index;
325         hv_vmbus_synic_simp     simp;
326         hv_vmbus_synic_siefp    siefp;
327         hv_vmbus_synic_scontrol sctrl;
328         hv_vmbus_synic_sint     shared_sint;
329         uint64_t                version;
330         hv_setup_args*          setup_args = (hv_setup_args *)arg;
331
332         cpu = PCPU_GET(cpuid);
333
334         if (hv_vmbus_g_context.hypercall_page == NULL)
335             return;
336
337         /*
338          * TODO: Check the version
339          */
340         version = rdmsr(HV_X64_MSR_SVERSION);
341         
342         hv_vmbus_g_context.syn_ic_msg_page[cpu] =
343             setup_args->page_buffers[2 * cpu];
344         hv_vmbus_g_context.syn_ic_event_page[cpu] =
345             setup_args->page_buffers[2 * cpu + 1];
346
347         /*
348          * Setup the Synic's message page
349          */
350
351         simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
352         simp.u.simp_enabled = 1;
353         simp.u.base_simp_gpa = ((hv_get_phys_addr(
354             hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT);
355
356         wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
357
358         /*
359          * Setup the Synic's event page
360          */
361         siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
362         siefp.u.siefp_enabled = 1;
363         siefp.u.base_siefp_gpa = ((hv_get_phys_addr(
364             hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT);
365
366         wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
367
368         /*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
369         shared_sint.as_uint64_t = 0;
370         shared_sint.u.vector = setup_args->vector;
371         shared_sint.u.masked = FALSE;
372         shared_sint.u.auto_eoi = TRUE;
373
374         wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
375             shared_sint.as_uint64_t);
376
377         /* Enable the global synic bit */
378         sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
379         sctrl.u.enable = 1;
380
381         wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
382
383         hv_vmbus_g_context.syn_ic_initialized = TRUE;
384
385         /*
386          * Set up the cpuid mapping from Hyper-V to FreeBSD.
387          * The array is indexed using FreeBSD cpuid.
388          */
389         hv_vcpu_index = rdmsr(HV_X64_MSR_VP_INDEX);
390         hv_vmbus_g_context.hv_vcpu_index[cpu] = (uint32_t)hv_vcpu_index;
391
392         return;
393 }
394
395 /**
396  * @brief Cleanup routine for hv_vmbus_synic_init()
397  */
398 void hv_vmbus_synic_cleanup(void *arg)
399 {
400         hv_vmbus_synic_sint     shared_sint;
401         hv_vmbus_synic_simp     simp;
402         hv_vmbus_synic_siefp    siefp;
403
404         if (!hv_vmbus_g_context.syn_ic_initialized)
405             return;
406
407         shared_sint.as_uint64_t = rdmsr(
408             HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
409
410         shared_sint.u.masked = 1;
411
412         /*
413          * Disable the interrupt
414          */
415         wrmsr(
416             HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
417             shared_sint.as_uint64_t);
418
419         simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
420         simp.u.simp_enabled = 0;
421         simp.u.base_simp_gpa = 0;
422
423         wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
424
425         siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
426         siefp.u.siefp_enabled = 0;
427         siefp.u.base_siefp_gpa = 0;
428
429         wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
430 }
431
432 static bool
433 hyperv_identify(void)
434 {
435         u_int regs[4];
436         unsigned int maxLeaf;
437         unsigned int op;
438
439         if (vm_guest != VM_GUEST_HV)
440                 return (false);
441
442         op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
443         do_cpuid(op, regs);
444         maxLeaf = regs[0];
445         if (maxLeaf < HV_CPU_ID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS)
446                 return (false);
447
448         op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
449         do_cpuid(op, regs);
450         if (regs[0] != 0x31237648 /* HV#1 */)
451                 return (false);
452
453         op = HV_CPU_ID_FUNCTION_MS_HV_FEATURES;
454         do_cpuid(op, regs);
455         if ((regs[0] & HV_FEATURE_MSR_HYPERCALL) == 0) {
456                 /*
457                  * Hyper-V w/o Hypercall is impossible; someone
458                  * is faking Hyper-V.
459                  */
460                 return (false);
461         }
462         hyperv_features = regs[0];
463
464         op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
465         do_cpuid(op, regs);
466         printf("Hyper-V Version: %d.%d.%d [SP%d]\n",
467             regs[1] >> 16, regs[1] & 0xffff, regs[0], regs[2]);
468
469         printf("  Features: 0x%b\n", hyperv_features,
470             "\020"
471             "\001VPRUNTIME"
472             "\002TMREFCNT"
473             "\003SYNCIC"
474             "\004SYNCTM"
475             "\005APIC"
476             "\006HYERCALL"
477             "\007VPINDEX"
478             "\010RESET"
479             "\011STATS"
480             "\012REFTSC"
481             "\013IDLE"
482             "\014TMFREQ"
483             "\015DEBUG");
484
485         op = HV_CPU_ID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION;
486         do_cpuid(op, regs);
487         hyperv_recommends = regs[0];
488         if (bootverbose)
489                 printf("  Recommends: %08x %08x\n", regs[0], regs[1]);
490
491         op = HV_CPU_ID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS;
492         do_cpuid(op, regs);
493         if (bootverbose) {
494                 printf("  Limits: Vcpu:%d Lcpu:%d Int:%d\n",
495                     regs[0], regs[1], regs[2]);
496         }
497
498         if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_HARDWARE_FEATURE) {
499                 op = HV_CPU_ID_FUNCTION_MS_HV_HARDWARE_FEATURE;
500                 do_cpuid(op, regs);
501                 if (bootverbose) {
502                         printf("  HW Features: %08x AMD: %08x\n",
503                             regs[0], regs[3]);
504                 }
505         }
506
507         return (true);
508 }
509
510 static void
511 hyperv_init(void *dummy __unused)
512 {
513         if (!hyperv_identify())
514                 return;
515
516         if (hyperv_features & HV_FEATURE_MSR_TIME_REFCNT) {
517                 /* Register virtual timecount */
518                 tc_init(&hv_timecounter);
519         }
520 }
521 SYSINIT(hyperv_initialize, SI_SUB_HYPERVISOR, SI_ORDER_FIRST, hyperv_init, NULL);