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