]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - sys/dev/hyperv/vmbus/hv_hv.c
MFC 297142,297143,297176,297177,297178,297221
[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/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_WAITOK | M_ZERO);
197
198         hypercall_msr.u.enable = 1;
199         hypercall_msr.u.guest_physical_address =
200             (hv_get_phys_addr(virt_addr) >> PAGE_SHIFT);
201         wrmsr(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64_t);
202
203         /*
204          * Confirm that hypercall page did get set up
205          */
206         hypercall_msr.as_uint64_t = 0;
207         hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
208
209         if (!hypercall_msr.u.enable)
210             goto cleanup;
211
212         hv_vmbus_g_context.hypercall_page = virt_addr;
213
214         hv_et_init();
215         
216         return (0);
217
218         cleanup:
219         if (virt_addr != NULL) {
220             if (hypercall_msr.u.enable) {
221                 hypercall_msr.as_uint64_t = 0;
222                 wrmsr(HV_X64_MSR_HYPERCALL,
223                                         hypercall_msr.as_uint64_t);
224             }
225
226             free(virt_addr, M_DEVBUF);
227         }
228         return (ENOTSUP);
229 }
230
231 /**
232  * @brief Cleanup routine, called normally during driver unloading or exiting
233  */
234 void
235 hv_vmbus_cleanup(void) 
236 {
237         hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
238
239         if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) {
240             if (hv_vmbus_g_context.hypercall_page != NULL) {
241                 hypercall_msr.as_uint64_t = 0;
242                 wrmsr(HV_X64_MSR_HYPERCALL,
243                                         hypercall_msr.as_uint64_t);
244                 free(hv_vmbus_g_context.hypercall_page, M_DEVBUF);
245                 hv_vmbus_g_context.hypercall_page = NULL;
246             }
247         }
248 }
249
250 /**
251  * @brief Post a message using the hypervisor message IPC.
252  * (This involves a hypercall.)
253  */
254 hv_vmbus_status
255 hv_vmbus_post_msg_via_msg_ipc(
256         hv_vmbus_connection_id  connection_id,
257         hv_vmbus_msg_type       message_type,
258         void*                   payload,
259         size_t                  payload_size)
260 {
261         struct alignedinput {
262             uint64_t alignment8;
263             hv_vmbus_input_post_message msg;
264         };
265
266         hv_vmbus_input_post_message*    aligned_msg;
267         hv_vmbus_status                 status;
268         size_t                          addr;
269
270         if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
271             return (EMSGSIZE);
272
273         addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
274                             M_ZERO | M_NOWAIT);
275         KASSERT(addr != 0,
276             ("Error VMBUS: malloc failed to allocate message buffer!"));
277         if (addr == 0)
278             return (ENOMEM);
279
280         aligned_msg = (hv_vmbus_input_post_message*)
281             (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
282
283         aligned_msg->connection_id = connection_id;
284         aligned_msg->message_type = message_type;
285         aligned_msg->payload_size = payload_size;
286         memcpy((void*) aligned_msg->payload, payload, payload_size);
287
288         status = hv_vmbus_do_hypercall(
289                     HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
290
291         free((void *) addr, M_DEVBUF);
292         return (status);
293 }
294
295 /**
296  * @brief Signal an event on the specified connection using the hypervisor
297  * event IPC. (This involves a hypercall.)
298  */
299 hv_vmbus_status
300 hv_vmbus_signal_event(void *con_id)
301 {
302         hv_vmbus_status status;
303
304         status = hv_vmbus_do_hypercall(
305                     HV_CALL_SIGNAL_EVENT,
306                     con_id,
307                     0) & 0xFFFF;
308
309         return (status);
310 }
311
312 /**
313  * @brief hv_vmbus_synic_init
314  */
315 void
316 hv_vmbus_synic_init(void *arg)
317
318 {
319         int                     cpu;
320         uint64_t                hv_vcpu_index;
321         hv_vmbus_synic_simp     simp;
322         hv_vmbus_synic_siefp    siefp;
323         hv_vmbus_synic_scontrol sctrl;
324         hv_vmbus_synic_sint     shared_sint;
325         uint64_t                version;
326         hv_setup_args*          setup_args = (hv_setup_args *)arg;
327
328         cpu = PCPU_GET(cpuid);
329
330         if (hv_vmbus_g_context.hypercall_page == NULL)
331             return;
332
333         /*
334          * TODO: Check the version
335          */
336         version = rdmsr(HV_X64_MSR_SVERSION);
337         
338         hv_vmbus_g_context.syn_ic_msg_page[cpu] =
339             setup_args->page_buffers[2 * cpu];
340         hv_vmbus_g_context.syn_ic_event_page[cpu] =
341             setup_args->page_buffers[2 * cpu + 1];
342
343         /*
344          * Setup the Synic's message page
345          */
346
347         simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
348         simp.u.simp_enabled = 1;
349         simp.u.base_simp_gpa = ((hv_get_phys_addr(
350             hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT);
351
352         wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
353
354         /*
355          * Setup the Synic's event page
356          */
357         siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
358         siefp.u.siefp_enabled = 1;
359         siefp.u.base_siefp_gpa = ((hv_get_phys_addr(
360             hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT);
361
362         wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
363
364         /*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
365         shared_sint.as_uint64_t = 0;
366         shared_sint.u.vector = setup_args->vector;
367         shared_sint.u.masked = FALSE;
368         shared_sint.u.auto_eoi = TRUE;
369
370         wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
371             shared_sint.as_uint64_t);
372
373         wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_TIMER_SINT,
374             shared_sint.as_uint64_t);
375
376         /* Enable the global synic bit */
377         sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
378         sctrl.u.enable = 1;
379
380         wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
381
382         hv_vmbus_g_context.syn_ic_initialized = TRUE;
383
384         /*
385          * Set up the cpuid mapping from Hyper-V to FreeBSD.
386          * The array is indexed using FreeBSD cpuid.
387          */
388         hv_vcpu_index = rdmsr(HV_X64_MSR_VP_INDEX);
389         hv_vmbus_g_context.hv_vcpu_index[cpu] = (uint32_t)hv_vcpu_index;
390
391         return;
392 }
393
394 /**
395  * @brief Cleanup routine for hv_vmbus_synic_init()
396  */
397 void hv_vmbus_synic_cleanup(void *arg)
398 {
399         hv_vmbus_synic_sint     shared_sint;
400         hv_vmbus_synic_simp     simp;
401         hv_vmbus_synic_siefp    siefp;
402
403         if (!hv_vmbus_g_context.syn_ic_initialized)
404             return;
405
406         shared_sint.as_uint64_t = rdmsr(
407             HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
408
409         shared_sint.u.masked = 1;
410
411         /*
412          * Disable the interrupt 0
413          */
414         wrmsr(
415             HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
416             shared_sint.as_uint64_t);
417
418         shared_sint.as_uint64_t = rdmsr(
419             HV_X64_MSR_SINT0 + HV_VMBUS_TIMER_SINT);
420
421         shared_sint.u.masked = 1;
422
423         /*
424          * Disable the interrupt 1
425          */
426         wrmsr(
427             HV_X64_MSR_SINT0 + HV_VMBUS_TIMER_SINT,
428             shared_sint.as_uint64_t);
429         simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
430         simp.u.simp_enabled = 0;
431         simp.u.base_simp_gpa = 0;
432
433         wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
434
435         siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
436         siefp.u.siefp_enabled = 0;
437         siefp.u.base_siefp_gpa = 0;
438
439         wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
440 }
441
442 static bool
443 hyperv_identify(void)
444 {
445         u_int regs[4];
446         unsigned int maxLeaf;
447         unsigned int op;
448
449         if (vm_guest != VM_GUEST_HV)
450                 return (false);
451
452         op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
453         do_cpuid(op, regs);
454         maxLeaf = regs[0];
455         if (maxLeaf < HV_CPU_ID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS)
456                 return (false);
457
458         op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
459         do_cpuid(op, regs);
460         if (regs[0] != 0x31237648 /* HV#1 */)
461                 return (false);
462
463         op = HV_CPU_ID_FUNCTION_MS_HV_FEATURES;
464         do_cpuid(op, regs);
465         if ((regs[0] & HV_FEATURE_MSR_HYPERCALL) == 0) {
466                 /*
467                  * Hyper-V w/o Hypercall is impossible; someone
468                  * is faking Hyper-V.
469                  */
470                 return (false);
471         }
472         hyperv_features = regs[0];
473
474         op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
475         do_cpuid(op, regs);
476         printf("Hyper-V Version: %d.%d.%d [SP%d]\n",
477             regs[1] >> 16, regs[1] & 0xffff, regs[0], regs[2]);
478
479         printf("  Features: 0x%b\n", hyperv_features,
480             "\020"
481             "\001VPRUNTIME"
482             "\002TMREFCNT"
483             "\003SYNCIC"
484             "\004SYNCTM"
485             "\005APIC"
486             "\006HYERCALL"
487             "\007VPINDEX"
488             "\010RESET"
489             "\011STATS"
490             "\012REFTSC"
491             "\013IDLE"
492             "\014TMFREQ"
493             "\015DEBUG");
494
495         op = HV_CPU_ID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION;
496         do_cpuid(op, regs);
497         hyperv_recommends = regs[0];
498         if (bootverbose)
499                 printf("  Recommends: %08x %08x\n", regs[0], regs[1]);
500
501         op = HV_CPU_ID_FUNCTION_MS_HV_IMPLEMENTATION_LIMITS;
502         do_cpuid(op, regs);
503         if (bootverbose) {
504                 printf("  Limits: Vcpu:%d Lcpu:%d Int:%d\n",
505                     regs[0], regs[1], regs[2]);
506         }
507
508         if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_HARDWARE_FEATURE) {
509                 op = HV_CPU_ID_FUNCTION_MS_HV_HARDWARE_FEATURE;
510                 do_cpuid(op, regs);
511                 if (bootverbose) {
512                         printf("  HW Features: %08x AMD: %08x\n",
513                             regs[0], regs[3]);
514                 }
515         }
516
517         return (true);
518 }
519
520 static void
521 hyperv_init(void *dummy __unused)
522 {
523         if (!hyperv_identify())
524                 return;
525
526         if (hyperv_features & HV_FEATURE_MSR_TIME_REFCNT) {
527                 /* Register virtual timecount */
528                 tc_init(&hv_timecounter);
529         }
530 }
531 SYSINIT(hyperv_initialize, SI_SUB_HYPERVISOR, SI_ORDER_FIRST, hyperv_init, NULL);