]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - sys/dev/hyperv/vmbus/hv_hv.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[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/malloc.h>
37 #include <sys/pcpu.h>
38 #include <sys/timetc.h>
39 #include <machine/bus.h>
40 #include <machine/md_var.h>
41 #include <vm/vm.h>
42 #include <vm/vm_param.h>
43 #include <vm/pmap.h>
44
45
46 #include "hv_vmbus_priv.h"
47
48 #define HV_NANOSECONDS_PER_SEC          1000000000L
49
50
51 static u_int hv_get_timecount(struct timecounter *tc);
52
53 /**
54  * Globals
55  */
56 hv_vmbus_context hv_vmbus_g_context = {
57         .syn_ic_initialized = FALSE,
58         .hypercall_page = NULL,
59 };
60
61 static struct timecounter hv_timecounter = {
62         hv_get_timecount, 0, ~0u, HV_NANOSECONDS_PER_SEC/100, "Hyper-V", HV_NANOSECONDS_PER_SEC/100
63 };
64
65 static u_int
66 hv_get_timecount(struct timecounter *tc)
67 {
68         u_int now = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
69         return (now);
70 }
71
72 /**
73  * @brief Query the cpuid for presence of windows hypervisor
74  */
75 int
76 hv_vmbus_query_hypervisor_presence(void) 
77 {
78         if (vm_guest != VM_GUEST_HV)
79                 return (0);
80
81         return (hv_high >= HV_X64_CPUID_MIN && hv_high <= HV_X64_CPUID_MAX);
82 }
83
84 /**
85  * @brief Get version of the windows hypervisor
86  */
87 static int
88 hv_vmbus_get_hypervisor_version(void) 
89 {
90         u_int regs[4];
91         unsigned int maxLeaf;
92         unsigned int op;
93
94         /*
95          * Its assumed that this is called after confirming that
96          * Viridian is present
97          * Query id and revision.
98          */
99         op = HV_CPU_ID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION;
100         do_cpuid(op, regs);
101
102         maxLeaf = regs[0];
103         op = HV_CPU_ID_FUNCTION_HV_INTERFACE;
104         do_cpuid(op, regs);
105
106         if (maxLeaf >= HV_CPU_ID_FUNCTION_MS_HV_VERSION) {
107             op = HV_CPU_ID_FUNCTION_MS_HV_VERSION;
108             do_cpuid(op, regs);
109         }
110         return (maxLeaf);
111 }
112
113 /**
114  * @brief Invoke the specified hypercall
115  */
116 static uint64_t
117 hv_vmbus_do_hypercall(uint64_t control, void* input, void* output)
118 {
119 #ifdef __x86_64__
120         uint64_t hv_status = 0;
121         uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
122         uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
123         volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
124
125         __asm__ __volatile__ ("mov %0, %%r8" : : "r" (output_address): "r8");
126         __asm__ __volatile__ ("call *%3" : "=a"(hv_status):
127                                 "c" (control), "d" (input_address),
128                                 "m" (hypercall_page));
129         return (hv_status);
130 #else
131         uint32_t control_high = control >> 32;
132         uint32_t control_low = control & 0xFFFFFFFF;
133         uint32_t hv_status_high = 1;
134         uint32_t hv_status_low = 1;
135         uint64_t input_address = (input) ? hv_get_phys_addr(input) : 0;
136         uint32_t input_address_high = input_address >> 32;
137         uint32_t input_address_low = input_address & 0xFFFFFFFF;
138         uint64_t output_address = (output) ? hv_get_phys_addr(output) : 0;
139         uint32_t output_address_high = output_address >> 32;
140         uint32_t output_address_low = output_address & 0xFFFFFFFF;
141         volatile void* hypercall_page = hv_vmbus_g_context.hypercall_page;
142
143         __asm__ __volatile__ ("call *%8" : "=d"(hv_status_high),
144                                 "=a"(hv_status_low) : "d" (control_high),
145                                 "a" (control_low), "b" (input_address_high),
146                                 "c" (input_address_low),
147                                 "D"(output_address_high),
148                                 "S"(output_address_low), "m" (hypercall_page));
149         return (hv_status_low | ((uint64_t)hv_status_high << 32));
150 #endif /* __x86_64__ */
151 }
152
153 /**
154  *  @brief Main initialization routine.
155  *
156  *  This routine must be called
157  *  before any other routines in here are called
158  */
159 int
160 hv_vmbus_init(void) 
161 {
162         int                                     max_leaf;
163         hv_vmbus_x64_msr_hypercall_contents     hypercall_msr;
164         void*                                   virt_addr = 0;
165
166         memset(
167             hv_vmbus_g_context.syn_ic_event_page,
168             0,
169             sizeof(hv_vmbus_handle) * MAXCPU);
170
171         memset(
172             hv_vmbus_g_context.syn_ic_msg_page,
173             0,
174             sizeof(hv_vmbus_handle) * MAXCPU);
175
176         if (vm_guest != VM_GUEST_HV)
177             goto cleanup;
178
179         max_leaf = hv_vmbus_get_hypervisor_version();
180
181         /*
182          * Write our OS info
183          */
184         uint64_t os_guest_info = HV_FREEBSD_GUEST_ID;
185         wrmsr(HV_X64_MSR_GUEST_OS_ID, os_guest_info);
186         hv_vmbus_g_context.guest_id = os_guest_info;
187
188         /*
189          * See if the hypercall page is already set
190          */
191         hypercall_msr.as_uint64_t = rdmsr(HV_X64_MSR_HYPERCALL);
192         virt_addr = malloc(PAGE_SIZE, M_DEVBUF, M_NOWAIT | M_ZERO);
193         KASSERT(virt_addr != NULL,
194             ("Error VMBUS: malloc failed to allocate page during init!"));
195         if (virt_addr == NULL)
196             goto cleanup;
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         tc_init(&hv_timecounter); /* register virtual timecount */
215
216         hv_et_init();
217         
218         return (0);
219
220         cleanup:
221         if (virt_addr != NULL) {
222             if (hypercall_msr.u.enable) {
223                 hypercall_msr.as_uint64_t = 0;
224                 wrmsr(HV_X64_MSR_HYPERCALL,
225                                         hypercall_msr.as_uint64_t);
226             }
227
228             free(virt_addr, M_DEVBUF);
229         }
230         return (ENOTSUP);
231 }
232
233 /**
234  * @brief Cleanup routine, called normally during driver unloading or exiting
235  */
236 void
237 hv_vmbus_cleanup(void) 
238 {
239         hv_vmbus_x64_msr_hypercall_contents hypercall_msr;
240
241         if (hv_vmbus_g_context.guest_id == HV_FREEBSD_GUEST_ID) {
242             if (hv_vmbus_g_context.hypercall_page != NULL) {
243                 hypercall_msr.as_uint64_t = 0;
244                 wrmsr(HV_X64_MSR_HYPERCALL,
245                                         hypercall_msr.as_uint64_t);
246                 free(hv_vmbus_g_context.hypercall_page, M_DEVBUF);
247                 hv_vmbus_g_context.hypercall_page = NULL;
248             }
249         }
250 }
251
252 /**
253  * @brief Post a message using the hypervisor message IPC.
254  * (This involves a hypercall.)
255  */
256 hv_vmbus_status
257 hv_vmbus_post_msg_via_msg_ipc(
258         hv_vmbus_connection_id  connection_id,
259         hv_vmbus_msg_type       message_type,
260         void*                   payload,
261         size_t                  payload_size)
262 {
263         struct alignedinput {
264             uint64_t alignment8;
265             hv_vmbus_input_post_message msg;
266         };
267
268         hv_vmbus_input_post_message*    aligned_msg;
269         hv_vmbus_status                 status;
270         size_t                          addr;
271
272         if (payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT)
273             return (EMSGSIZE);
274
275         addr = (size_t) malloc(sizeof(struct alignedinput), M_DEVBUF,
276                             M_ZERO | M_NOWAIT);
277         KASSERT(addr != 0,
278             ("Error VMBUS: malloc failed to allocate message buffer!"));
279         if (addr == 0)
280             return (ENOMEM);
281
282         aligned_msg = (hv_vmbus_input_post_message*)
283             (HV_ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN));
284
285         aligned_msg->connection_id = connection_id;
286         aligned_msg->message_type = message_type;
287         aligned_msg->payload_size = payload_size;
288         memcpy((void*) aligned_msg->payload, payload, payload_size);
289
290         status = hv_vmbus_do_hypercall(
291                     HV_CALL_POST_MESSAGE, aligned_msg, 0) & 0xFFFF;
292
293         free((void *) addr, M_DEVBUF);
294         return (status);
295 }
296
297 /**
298  * @brief Signal an event on the specified connection using the hypervisor
299  * event IPC. (This involves a hypercall.)
300  */
301 hv_vmbus_status
302 hv_vmbus_signal_event(void *con_id)
303 {
304         hv_vmbus_status status;
305
306         status = hv_vmbus_do_hypercall(
307                     HV_CALL_SIGNAL_EVENT,
308                     con_id,
309                     0) & 0xFFFF;
310
311         return (status);
312 }
313
314 /**
315  * @brief hv_vmbus_synic_init
316  */
317 void
318 hv_vmbus_synic_init(void *arg)
319
320 {
321         int                     cpu;
322         uint64_t                hv_vcpu_index;
323         hv_vmbus_synic_simp     simp;
324         hv_vmbus_synic_siefp    siefp;
325         hv_vmbus_synic_scontrol sctrl;
326         hv_vmbus_synic_sint     shared_sint;
327         uint64_t                version;
328         hv_setup_args*          setup_args = (hv_setup_args *)arg;
329
330         cpu = PCPU_GET(cpuid);
331
332         if (hv_vmbus_g_context.hypercall_page == NULL)
333             return;
334
335         /*
336          * TODO: Check the version
337          */
338         version = rdmsr(HV_X64_MSR_SVERSION);
339         
340         hv_vmbus_g_context.syn_ic_msg_page[cpu] =
341             setup_args->page_buffers[2 * cpu];
342         hv_vmbus_g_context.syn_ic_event_page[cpu] =
343             setup_args->page_buffers[2 * cpu + 1];
344
345         /*
346          * Setup the Synic's message page
347          */
348
349         simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
350         simp.u.simp_enabled = 1;
351         simp.u.base_simp_gpa = ((hv_get_phys_addr(
352             hv_vmbus_g_context.syn_ic_msg_page[cpu])) >> PAGE_SHIFT);
353
354         wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
355
356         /*
357          * Setup the Synic's event page
358          */
359         siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
360         siefp.u.siefp_enabled = 1;
361         siefp.u.base_siefp_gpa = ((hv_get_phys_addr(
362             hv_vmbus_g_context.syn_ic_event_page[cpu])) >> PAGE_SHIFT);
363
364         wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
365
366         /*HV_SHARED_SINT_IDT_VECTOR + 0x20; */
367         shared_sint.as_uint64_t = 0;
368         shared_sint.u.vector = setup_args->vector;
369         shared_sint.u.masked = FALSE;
370         shared_sint.u.auto_eoi = TRUE;
371
372         wrmsr(HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
373             shared_sint.as_uint64_t);
374
375         /* Enable the global synic bit */
376         sctrl.as_uint64_t = rdmsr(HV_X64_MSR_SCONTROL);
377         sctrl.u.enable = 1;
378
379         wrmsr(HV_X64_MSR_SCONTROL, sctrl.as_uint64_t);
380
381         hv_vmbus_g_context.syn_ic_initialized = TRUE;
382
383         /*
384          * Set up the cpuid mapping from Hyper-V to FreeBSD.
385          * The array is indexed using FreeBSD cpuid.
386          */
387         hv_vcpu_index = rdmsr(HV_X64_MSR_VP_INDEX);
388         hv_vmbus_g_context.hv_vcpu_index[cpu] = (uint32_t)hv_vcpu_index;
389
390         return;
391 }
392
393 /**
394  * @brief Cleanup routine for hv_vmbus_synic_init()
395  */
396 void hv_vmbus_synic_cleanup(void *arg)
397 {
398         hv_vmbus_synic_sint     shared_sint;
399         hv_vmbus_synic_simp     simp;
400         hv_vmbus_synic_siefp    siefp;
401
402         if (!hv_vmbus_g_context.syn_ic_initialized)
403             return;
404
405         shared_sint.as_uint64_t = rdmsr(
406             HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT);
407
408         shared_sint.u.masked = 1;
409
410         /*
411          * Disable the interrupt
412          */
413         wrmsr(
414             HV_X64_MSR_SINT0 + HV_VMBUS_MESSAGE_SINT,
415             shared_sint.as_uint64_t);
416
417         simp.as_uint64_t = rdmsr(HV_X64_MSR_SIMP);
418         simp.u.simp_enabled = 0;
419         simp.u.base_simp_gpa = 0;
420
421         wrmsr(HV_X64_MSR_SIMP, simp.as_uint64_t);
422
423         siefp.as_uint64_t = rdmsr(HV_X64_MSR_SIEFP);
424         siefp.u.siefp_enabled = 0;
425         siefp.u.base_siefp_gpa = 0;
426
427         wrmsr(HV_X64_MSR_SIEFP, siefp.as_uint64_t);
428 }
429