]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - sys/dev/hyperv/vmbus/hv_connection.c
MFC 295919,295958,295964
[FreeBSD/stable/10.git] / sys / dev / hyperv / vmbus / hv_connection.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 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 #include <sys/param.h>
33 #include <sys/kernel.h>
34 #include <sys/malloc.h>
35 #include <sys/systm.h>
36 #include <sys/lock.h>
37 #include <sys/mutex.h>
38 #include <machine/bus.h>
39 #include <vm/vm.h>
40 #include <vm/vm_param.h>
41 #include <vm/pmap.h>
42
43 #include "hv_vmbus_priv.h"
44
45 /*
46  * Globals
47  */
48 hv_vmbus_connection hv_vmbus_g_connection =
49         { .connect_state = HV_DISCONNECTED,
50           .next_gpadl_handle = 0xE1E10, };
51
52 uint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008;
53
54 static uint32_t
55 hv_vmbus_get_next_version(uint32_t current_ver)
56 {
57         switch (current_ver) {
58         case (HV_VMBUS_VERSION_WIN7):
59                 return(HV_VMBUS_VERSION_WS2008);
60
61         case (HV_VMBUS_VERSION_WIN8):
62                 return(HV_VMBUS_VERSION_WIN7);
63
64         case (HV_VMBUS_VERSION_WIN8_1):
65                 return(HV_VMBUS_VERSION_WIN8);
66
67         case (HV_VMBUS_VERSION_WS2008):
68         default:
69                 return(HV_VMBUS_VERSION_INVALID);
70         }
71 }
72
73 /**
74  * Negotiate the highest supported hypervisor version.
75  */
76 static int
77 hv_vmbus_negotiate_version(hv_vmbus_channel_msg_info *msg_info,
78         uint32_t version)
79 {
80         int                                     ret = 0;
81         hv_vmbus_channel_initiate_contact       *msg;
82
83         sema_init(&msg_info->wait_sema, 0, "Msg Info Sema");
84         msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg;
85
86         msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT;
87         msg->vmbus_version_requested = version;
88
89         msg->interrupt_page = hv_get_phys_addr(
90                 hv_vmbus_g_connection.interrupt_page);
91
92         msg->monitor_page_1 = hv_get_phys_addr(
93                 hv_vmbus_g_connection.monitor_page_1);
94
95         msg->monitor_page_2 = hv_get_phys_addr(
96                 hv_vmbus_g_connection.monitor_page_2);
97
98         /**
99          * Add to list before we send the request since we may receive the
100          * response before returning from this routine
101          */
102         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
103
104         TAILQ_INSERT_TAIL(
105                 &hv_vmbus_g_connection.channel_msg_anchor,
106                 msg_info,
107                 msg_list_entry);
108
109         mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
110
111         ret = hv_vmbus_post_message(
112                 msg,
113                 sizeof(hv_vmbus_channel_initiate_contact));
114
115         if (ret != 0) {
116                 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
117                 TAILQ_REMOVE(
118                         &hv_vmbus_g_connection.channel_msg_anchor,
119                         msg_info,
120                         msg_list_entry);
121                 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
122                 return (ret);
123         }
124
125         /**
126          * Wait for the connection response
127          */
128         ret = sema_timedwait(&msg_info->wait_sema, 5 * hz); /* KYS 5 seconds */
129
130         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
131         TAILQ_REMOVE(
132                 &hv_vmbus_g_connection.channel_msg_anchor,
133                 msg_info,
134                 msg_list_entry);
135         mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
136
137         /**
138          * Check if successful
139          */
140         if (msg_info->response.version_response.version_supported) {
141                 hv_vmbus_g_connection.connect_state = HV_CONNECTED;
142         } else {
143                 ret = ECONNREFUSED;
144         }
145
146         return (ret);
147 }
148
149 /**
150  * Send a connect request on the partition service connection
151  */
152 int
153 hv_vmbus_connect(void) {
154         int                                     ret = 0;
155         uint32_t                                version;
156         hv_vmbus_channel_msg_info*              msg_info = NULL;
157
158         /**
159          * Make sure we are not connecting or connected
160          */
161         if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) {
162                 return (-1);
163         }
164
165         /**
166          * Initialize the vmbus connection
167          */
168         hv_vmbus_g_connection.connect_state = HV_CONNECTING;
169
170         TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor);
171         mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg",
172                 NULL, MTX_SPIN);
173
174         TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
175         mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
176                 NULL, MTX_DEF);
177
178         /**
179          * Setup the vmbus event connection for channel interrupt abstraction
180          * stuff
181          */
182         hv_vmbus_g_connection.interrupt_page = malloc(
183                                         PAGE_SIZE, M_DEVBUF,
184                                         M_WAITOK | M_ZERO);
185
186         hv_vmbus_g_connection.recv_interrupt_page =
187                 hv_vmbus_g_connection.interrupt_page;
188
189         hv_vmbus_g_connection.send_interrupt_page =
190                 ((uint8_t *) hv_vmbus_g_connection.interrupt_page +
191                     (PAGE_SIZE >> 1));
192
193         /**
194          * Set up the monitor notification facility. The 1st page for
195          * parent->child and the 2nd page for child->parent
196          */
197         hv_vmbus_g_connection.monitor_page_1 = malloc(
198                 PAGE_SIZE,
199                 M_DEVBUF,
200                 M_WAITOK | M_ZERO);
201         hv_vmbus_g_connection.monitor_page_2 = malloc(
202                 PAGE_SIZE,
203                 M_DEVBUF,
204                 M_WAITOK | M_ZERO);
205
206         msg_info = (hv_vmbus_channel_msg_info*)
207                 malloc(sizeof(hv_vmbus_channel_msg_info) +
208                         sizeof(hv_vmbus_channel_initiate_contact),
209                         M_DEVBUF, M_WAITOK | M_ZERO);
210
211         hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) *
212                 HV_CHANNEL_MAX_COUNT,
213                 M_DEVBUF, M_WAITOK | M_ZERO);
214         /*
215          * Find the highest vmbus version number we can support.
216          */
217         version = HV_VMBUS_VERSION_CURRENT;
218
219         do {
220                 ret = hv_vmbus_negotiate_version(msg_info, version);
221                 if (ret == EWOULDBLOCK) {
222                         /*
223                          * We timed out.
224                          */
225                         goto cleanup;
226                 }
227
228                 if (hv_vmbus_g_connection.connect_state == HV_CONNECTED)
229                         break;
230
231                 version = hv_vmbus_get_next_version(version);
232         } while (version != HV_VMBUS_VERSION_INVALID);
233
234         hv_vmbus_protocal_version = version;
235         if (bootverbose)
236                 printf("VMBUS: Protocol Version: %d.%d\n",
237                     version >> 16, version & 0xFFFF);
238
239         sema_destroy(&msg_info->wait_sema);
240         free(msg_info, M_DEVBUF);
241
242         return (0);
243
244         /*
245          * Cleanup after failure!
246          */
247         cleanup:
248
249         hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
250
251         mtx_destroy(&hv_vmbus_g_connection.channel_lock);
252         mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
253
254         if (hv_vmbus_g_connection.interrupt_page != NULL) {
255                 free(hv_vmbus_g_connection.interrupt_page, M_DEVBUF);
256                 hv_vmbus_g_connection.interrupt_page = NULL;
257         }
258
259         free(hv_vmbus_g_connection.monitor_page_1, M_DEVBUF);
260         free(hv_vmbus_g_connection.monitor_page_2, M_DEVBUF);
261
262         if (msg_info) {
263                 sema_destroy(&msg_info->wait_sema);
264                 free(msg_info, M_DEVBUF);
265         }
266
267         free(hv_vmbus_g_connection.channels, M_DEVBUF);
268         return (ret);
269 }
270
271 /**
272  * Send a disconnect request on the partition service connection
273  */
274 int
275 hv_vmbus_disconnect(void) {
276         int                      ret = 0;
277         hv_vmbus_channel_unload  msg;
278
279         msg.message_type = HV_CHANNEL_MESSAGE_UNLOAD;
280
281         ret = hv_vmbus_post_message(&msg, sizeof(hv_vmbus_channel_unload));
282
283         free(hv_vmbus_g_connection.interrupt_page, M_DEVBUF);
284
285         mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
286
287         free(hv_vmbus_g_connection.channels, M_DEVBUF);
288         hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
289
290         return (ret);
291 }
292
293 /**
294  * Handler for events
295  */
296 void
297 hv_vmbus_on_events(int cpu)
298 {
299         int bit;
300         int dword;
301         void *page_addr;
302         uint32_t* recv_interrupt_page = NULL;
303         int rel_id;
304         int maxdword;
305         hv_vmbus_synic_event_flags *event;
306         /* int maxdword = PAGE_SIZE >> 3; */
307
308         KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: "
309             "cpu out of range!"));
310
311         if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) ||
312             (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) {
313                 maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
314                 /*
315                  * receive size is 1/2 page and divide that by 4 bytes
316                  */
317                 recv_interrupt_page =
318                     hv_vmbus_g_connection.recv_interrupt_page;
319         } else {
320                 /*
321                  * On Host with Win8 or above, the event page can be
322                  * checked directly to get the id of the channel
323                  * that has the pending interrupt.
324                  */
325                 maxdword = HV_EVENT_FLAGS_DWORD_COUNT;
326                 page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu];
327                 event = (hv_vmbus_synic_event_flags *)
328                     page_addr + HV_VMBUS_MESSAGE_SINT;
329                 recv_interrupt_page = event->flags32;
330         }
331
332         /*
333          * Check events
334          */
335         if (recv_interrupt_page != NULL) {
336             for (dword = 0; dword < maxdword; dword++) {
337                 if (recv_interrupt_page[dword]) {
338                     for (bit = 0; bit < HV_CHANNEL_DWORD_LEN; bit++) {
339                         if (synch_test_and_clear_bit(bit,
340                             (uint32_t *) &recv_interrupt_page[dword])) {
341                             rel_id = (dword << 5) + bit;
342                             if (rel_id == 0) {
343                                 /*
344                                  * Special case -
345                                  * vmbus channel protocol msg.
346                                  */
347                                 continue;
348                             } else {
349                                 hv_vmbus_channel * channel = hv_vmbus_g_connection.channels[rel_id];
350                                 /* if channel is closed or closing */
351                                 if (channel == NULL || channel->rxq == NULL)
352                                         continue;
353
354                                 if (channel->batched_reading)
355                                         hv_ring_buffer_read_begin(&channel->inbound);
356                                 taskqueue_enqueue_fast(channel->rxq, &channel->channel_task);
357                             }
358                         }
359                     }
360                 }
361             }
362         }
363
364         return;
365 }
366
367 /**
368  * Send a msg on the vmbus's message connection
369  */
370 int hv_vmbus_post_message(void *buffer, size_t bufferLen) {
371         int ret = 0;
372         hv_vmbus_connection_id connId;
373         unsigned retries = 0;
374
375         /* NetScaler delays from previous code were consolidated here */
376         static int delayAmount[] = {100, 100, 100, 500, 500, 5000, 5000, 5000};
377
378         /* for(each entry in delayAmount) try to post message,
379          *  delay a little bit before retrying
380          */
381         for (retries = 0;
382             retries < sizeof(delayAmount)/sizeof(delayAmount[0]); retries++) {
383             connId.as_uint32_t = 0;
384             connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
385             ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer, bufferLen);
386             if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
387                 break;
388             /* TODO: KYS We should use a blocking wait call */
389             DELAY(delayAmount[retries]);
390         }
391
392         KASSERT(ret == 0, ("Error VMBUS: Message Post Failed\n"));
393
394         return (ret);
395 }
396
397 /**
398  * Send an event notification to the parent
399  */
400 int
401 hv_vmbus_set_event(hv_vmbus_channel *channel) {
402         int ret = 0;
403         uint32_t child_rel_id = channel->offer_msg.child_rel_id;
404
405         /* Each uint32_t represents 32 channels */
406
407         synch_set_bit(child_rel_id & 31,
408                 (((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
409                         + (child_rel_id >> 5))));
410         ret = hv_vmbus_signal_event(channel->signal_event_param);
411
412         return (ret);
413 }