]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - sys/dev/hyperv/vmbus/hv_connection.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[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/param.h>
30 #include <sys/malloc.h>
31 #include <sys/systm.h>
32 #include <sys/lock.h>
33 #include <sys/mutex.h>
34 #include <machine/bus.h>
35 #include <vm/vm.h>
36 #include <vm/vm_param.h>
37 #include <vm/pmap.h>
38
39 #include "hv_vmbus_priv.h"
40
41 /*
42  * Globals
43  */
44 hv_vmbus_connection hv_vmbus_g_connection =
45         { .connect_state = HV_DISCONNECTED,
46           .next_gpadl_handle = 0xE1E10, };
47
48 /**
49  * Send a connect request on the partition service connection
50  */
51 int
52 hv_vmbus_connect(void) {
53         int                                     ret = 0;
54         hv_vmbus_channel_msg_info*              msg_info = NULL;
55         hv_vmbus_channel_initiate_contact*      msg;
56
57         /**
58          * Make sure we are not connecting or connected
59          */
60         if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) {
61                 return (-1);
62         }
63
64         /**
65          * Initialize the vmbus connection
66          */
67         hv_vmbus_g_connection.connect_state = HV_CONNECTING;
68         hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ");
69         sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema");
70
71         TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor);
72         mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg",
73                 NULL, MTX_SPIN);
74
75         TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
76         mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
77                 NULL, MTX_SPIN);
78
79         /**
80          * Setup the vmbus event connection for channel interrupt abstraction
81          * stuff
82          */
83         hv_vmbus_g_connection.interrupt_page = contigmalloc(
84                                         PAGE_SIZE, M_DEVBUF,
85                                         M_NOWAIT | M_ZERO, 0UL,
86                                         BUS_SPACE_MAXADDR,
87                                         PAGE_SIZE, 0);
88         KASSERT(hv_vmbus_g_connection.interrupt_page != NULL,
89             ("Error VMBUS: malloc failed to allocate Channel"
90                 " Request Event message!"));
91         if (hv_vmbus_g_connection.interrupt_page == NULL) {
92             ret = ENOMEM;
93             goto cleanup;
94         }
95
96         hv_vmbus_g_connection.recv_interrupt_page =
97                 hv_vmbus_g_connection.interrupt_page;
98
99         hv_vmbus_g_connection.send_interrupt_page =
100                 ((uint8_t *) hv_vmbus_g_connection.interrupt_page +
101                     (PAGE_SIZE >> 1));
102
103         /**
104          * Set up the monitor notification facility. The 1st page for
105          * parent->child and the 2nd page for child->parent
106          */
107         hv_vmbus_g_connection.monitor_pages = contigmalloc(
108                 2 * PAGE_SIZE,
109                 M_DEVBUF,
110                 M_NOWAIT | M_ZERO,
111                 0UL,
112                 BUS_SPACE_MAXADDR,
113                 PAGE_SIZE,
114                 0);
115         KASSERT(hv_vmbus_g_connection.monitor_pages != NULL,
116             ("Error VMBUS: malloc failed to allocate Monitor Pages!"));
117         if (hv_vmbus_g_connection.monitor_pages == NULL) {
118             ret = ENOMEM;
119             goto cleanup;
120         }
121
122         msg_info = (hv_vmbus_channel_msg_info*)
123                 malloc(sizeof(hv_vmbus_channel_msg_info) +
124                         sizeof(hv_vmbus_channel_initiate_contact),
125                         M_DEVBUF, M_NOWAIT | M_ZERO);
126         KASSERT(msg_info != NULL,
127             ("Error VMBUS: malloc failed for Initiate Contact message!"));
128         if (msg_info == NULL) {
129             ret = ENOMEM;
130             goto cleanup;
131         }
132
133         sema_init(&msg_info->wait_sema, 0, "Msg Info Sema");
134         msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg;
135
136         msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT;
137         msg->vmbus_version_requested = HV_VMBUS_REVISION_NUMBER;
138
139         msg->interrupt_page = hv_get_phys_addr(
140                 hv_vmbus_g_connection.interrupt_page);
141
142         msg->monitor_page_1 = hv_get_phys_addr(
143                 hv_vmbus_g_connection.monitor_pages);
144
145         msg->monitor_page_2 =
146                 hv_get_phys_addr(
147                         ((uint8_t *) hv_vmbus_g_connection.monitor_pages
148                         + PAGE_SIZE));
149
150         /**
151          * Add to list before we send the request since we may receive the
152          * response before returning from this routine
153          */
154         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
155
156         TAILQ_INSERT_TAIL(
157                 &hv_vmbus_g_connection.channel_msg_anchor,
158                 msg_info,
159                 msg_list_entry);
160
161         mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
162
163         ret = hv_vmbus_post_message(
164                 msg,
165                 sizeof(hv_vmbus_channel_initiate_contact));
166
167         if (ret != 0) {
168                 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
169                 TAILQ_REMOVE(
170                         &hv_vmbus_g_connection.channel_msg_anchor,
171                         msg_info,
172                         msg_list_entry);
173                 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
174                 goto cleanup;
175         }
176
177         /**
178          * Wait for the connection response
179          */
180         ret = sema_timedwait(&msg_info->wait_sema, 500); /* KYS 5 seconds */
181
182         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
183         TAILQ_REMOVE(
184                 &hv_vmbus_g_connection.channel_msg_anchor,
185                 msg_info,
186                 msg_list_entry);
187         mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
188
189         /**
190          * Check if successful
191          */
192         if (msg_info->response.version_response.version_supported) {
193                 hv_vmbus_g_connection.connect_state = HV_CONNECTED;
194         } else {
195                 ret = ECONNREFUSED;
196                 goto cleanup;
197         }
198
199         sema_destroy(&msg_info->wait_sema);
200         free(msg_info, M_DEVBUF);
201
202         return (0);
203
204         /*
205          * Cleanup after failure!
206          */
207         cleanup:
208
209         hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
210
211         hv_work_queue_close(hv_vmbus_g_connection.work_queue);
212         sema_destroy(&hv_vmbus_g_connection.control_sema);
213         mtx_destroy(&hv_vmbus_g_connection.channel_lock);
214         mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
215
216         if (hv_vmbus_g_connection.interrupt_page != NULL) {
217                 contigfree(
218                         hv_vmbus_g_connection.interrupt_page,
219                         PAGE_SIZE,
220                         M_DEVBUF);
221                 hv_vmbus_g_connection.interrupt_page = NULL;
222         }
223
224         if (hv_vmbus_g_connection.monitor_pages != NULL) {
225                 contigfree(
226                         hv_vmbus_g_connection.monitor_pages,
227                         2 * PAGE_SIZE,
228                         M_DEVBUF);
229                 hv_vmbus_g_connection.monitor_pages = NULL;
230         }
231
232         if (msg_info) {
233                 sema_destroy(&msg_info->wait_sema);
234                 free(msg_info, M_DEVBUF);
235         }
236
237         return (ret);
238 }
239
240 /**
241  * Send a disconnect request on the partition service connection
242  */
243 int
244 hv_vmbus_disconnect(void) {
245         int                      ret = 0;
246         hv_vmbus_channel_unload* msg;
247
248         msg = malloc(sizeof(hv_vmbus_channel_unload),
249             M_DEVBUF, M_NOWAIT | M_ZERO);
250         KASSERT(msg != NULL,
251             ("Error VMBUS: malloc failed to allocate Channel Unload Msg!"));
252         if (msg == NULL)
253             return (ENOMEM);
254
255         msg->message_type = HV_CHANNEL_MESSAGE_UNLOAD;
256
257         ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_unload));
258
259
260         contigfree(hv_vmbus_g_connection.interrupt_page, PAGE_SIZE, M_DEVBUF);
261
262         mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
263
264         hv_work_queue_close(hv_vmbus_g_connection.work_queue);
265         sema_destroy(&hv_vmbus_g_connection.control_sema);
266
267         hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
268
269         free(msg, M_DEVBUF);
270
271         return (ret);
272 }
273
274 /**
275  * Get the channel object given its child relative id (ie channel id)
276  */
277 hv_vmbus_channel*
278 hv_vmbus_get_channel_from_rel_id(uint32_t rel_id) {
279
280         hv_vmbus_channel* channel;
281         hv_vmbus_channel* foundChannel = NULL;
282
283         /*
284          * TODO:
285          * Consider optimization where relids are stored in a fixed size array
286          *  and channels are accessed without the need to take this lock or search
287          *  the list.
288          */
289         mtx_lock_spin(&hv_vmbus_g_connection.channel_lock);
290         TAILQ_FOREACH(channel,
291                 &hv_vmbus_g_connection.channel_anchor, list_entry) {
292
293             if (channel->offer_msg.child_rel_id == rel_id) {
294                 foundChannel = channel;
295                 break;
296             }
297         }
298         mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock);
299
300         return (foundChannel);
301 }
302
303 /**
304  * Process a channel event notification
305  */
306 static void
307 VmbusProcessChannelEvent(uint32_t relid) 
308 {
309         hv_vmbus_channel* channel;
310
311         /**
312          * Find the channel based on this relid and invokes
313          * the channel callback to process the event
314          */
315
316         channel = hv_vmbus_get_channel_from_rel_id(relid);
317
318         if (channel == NULL) {
319                 return;
320         }
321         /**
322          * To deal with the race condition where we might
323          * receive a packet while the relevant driver is 
324          * being unloaded, dispatch the callback while 
325          * holding the channel lock. The unloading driver
326          * will acquire the same channel lock to set the
327          * callback to NULL. This closes the window.
328          */
329
330         mtx_lock(&channel->inbound_lock);
331         if (channel->on_channel_callback != NULL) {
332                 channel->on_channel_callback(channel->channel_callback_context);
333         }
334         mtx_unlock(&channel->inbound_lock);
335 }
336
337 /**
338  * Handler for events
339  */
340 void
341 hv_vmbus_on_events(void *arg) 
342 {
343         int dword;
344         int bit;
345         int rel_id;
346         int maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
347         /* int maxdword = PAGE_SIZE >> 3; */
348
349         /*
350          * receive size is 1/2 page and divide that by 4 bytes
351          */
352
353         uint32_t* recv_interrupt_page =
354             hv_vmbus_g_connection.recv_interrupt_page;
355
356         /*
357          * Check events
358          */
359         if (recv_interrupt_page != NULL) {
360             for (dword = 0; dword < maxdword; dword++) {
361                 if (recv_interrupt_page[dword]) {
362                     for (bit = 0; bit < 32; bit++) {
363                         if (synch_test_and_clear_bit(bit,
364                             (uint32_t *) &recv_interrupt_page[dword])) {
365                             rel_id = (dword << 5) + bit;
366                             if (rel_id == 0) {
367                                 /*
368                                  * Special case -
369                                  * vmbus channel protocol msg.
370                                  */
371                                 continue;
372                             } else {
373                                 VmbusProcessChannelEvent(rel_id);
374
375                             }
376                         }
377                     }
378                 }
379             }
380         }
381
382         return;
383 }
384
385 /**
386  * Send a msg on the vmbus's message connection
387  */
388 int hv_vmbus_post_message(void *buffer, size_t bufferLen) {
389         int ret = 0;
390         hv_vmbus_connection_id connId;
391         unsigned retries = 0;
392
393         /* NetScaler delays from previous code were consolidated here */
394         static int delayAmount[] = {100, 100, 100, 500, 500, 5000, 5000, 5000};
395
396         /* for(each entry in delayAmount) try to post message,
397          *  delay a little bit before retrying
398          */
399         for (retries = 0;
400             retries < sizeof(delayAmount)/sizeof(delayAmount[0]); retries++) {
401             connId.as_uint32_t = 0;
402             connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
403             ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer, bufferLen);
404             if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
405                 break;
406             /* TODO: KYS We should use a blocking wait call */
407             DELAY(delayAmount[retries]);
408         }
409
410         KASSERT(ret == 0, ("Error VMBUS: Message Post Failed\n"));
411
412         return (ret);
413 }
414
415 /**
416  * Send an event notification to the parent
417  */
418 int
419 hv_vmbus_set_event(uint32_t child_rel_id) {
420         int ret = 0;
421
422         /* Each uint32_t represents 32 channels */
423
424         synch_set_bit(child_rel_id & 31,
425                 (((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
426                         + (child_rel_id >> 5))));
427         ret = hv_vmbus_signal_event();
428
429         return (ret);
430 }
431