]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/hyperv/vmbus/hv_channel_mgmt.c
MFH
[FreeBSD/FreeBSD.git] / sys / dev / hyperv / vmbus / hv_channel_mgmt.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/mbuf.h>
31
32 #include "hv_vmbus_priv.h"
33
34 /*
35  * Internal functions
36  */
37
38 static void vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr);
39 static void vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr);
40 static void vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr);
41 static void vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr);
42 static void vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr);
43 static void vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr);
44 static void vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr);
45
46 /**
47  * Channel message dispatch table
48  */
49 hv_vmbus_channel_msg_table_entry
50     g_channel_message_table[HV_CHANNEL_MESSAGE_COUNT] = {
51         { HV_CHANNEL_MESSAGE_INVALID,
52                 0, NULL },
53         { HV_CHANNEL_MESSAGE_OFFER_CHANNEL,
54                 0, vmbus_channel_on_offer },
55         { HV_CHANNEL_MESSAGE_RESCIND_CHANNEL_OFFER,
56                 0, vmbus_channel_on_offer_rescind },
57         { HV_CHANNEL_MESSAGE_REQUEST_OFFERS,
58                 0, NULL },
59         { HV_CHANNEL_MESSAGE_ALL_OFFERS_DELIVERED,
60                 1, vmbus_channel_on_offers_delivered },
61         { HV_CHANNEL_MESSAGE_OPEN_CHANNEL,
62                 0, NULL },
63         { HV_CHANNEL_MESSAGE_OPEN_CHANNEL_RESULT,
64                 1, vmbus_channel_on_open_result },
65         { HV_CHANNEL_MESSAGE_CLOSE_CHANNEL,
66                 0, NULL },
67         { HV_CHANNEL_MESSAGEL_GPADL_HEADER,
68                 0, NULL },
69         { HV_CHANNEL_MESSAGE_GPADL_BODY,
70                 0, NULL },
71         { HV_CHANNEL_MESSAGE_GPADL_CREATED,
72                 1, vmbus_channel_on_gpadl_created },
73         { HV_CHANNEL_MESSAGE_GPADL_TEARDOWN,
74                 0, NULL },
75         { HV_CHANNEL_MESSAGE_GPADL_TORNDOWN,
76                 1, vmbus_channel_on_gpadl_torndown },
77         { HV_CHANNEL_MESSAGE_REL_ID_RELEASED,
78                 0, NULL },
79         { HV_CHANNEL_MESSAGE_INITIATED_CONTACT,
80                 0, NULL },
81         { HV_CHANNEL_MESSAGE_VERSION_RESPONSE,
82                 1, vmbus_channel_on_version_response },
83         { HV_CHANNEL_MESSAGE_UNLOAD,
84                 0, NULL }
85 };
86
87
88 /**
89  * Implementation of the work abstraction.
90  */
91 static void
92 work_item_callback(void *work, int pending)
93 {
94         struct hv_work_item *w = (struct hv_work_item *)work;
95
96         /*
97          * Serialize work execution.
98          */
99         if (w->wq->work_sema != NULL) {
100                 sema_wait(w->wq->work_sema);
101         }
102
103         w->callback(w->context);
104
105         if (w->wq->work_sema != NULL) {
106                 sema_post(w->wq->work_sema);
107         } 
108
109         free(w, M_DEVBUF);
110 }
111
112 struct hv_work_queue*
113 hv_work_queue_create(char* name)
114 {
115         static unsigned int     qid = 0;
116         char                    qname[64];
117         int                     pri;
118         struct hv_work_queue*   wq;
119
120         wq = malloc(sizeof(struct hv_work_queue), M_DEVBUF, M_NOWAIT | M_ZERO);
121         KASSERT(wq != NULL, ("Error VMBUS: Failed to allocate work_queue\n"));
122         if (wq == NULL)
123             return (NULL);
124
125         /*
126          * We use work abstraction to handle messages
127          * coming from the host and these are typically offers.
128          * Some FreeBsd drivers appear to have a concurrency issue
129          * where probe/attach needs to be serialized. We ensure that
130          * by having only one thread process work elements in a 
131          * specific queue by serializing work execution.
132          *
133          */
134         if (strcmp(name, "vmbusQ") == 0) {
135             pri = PI_DISK;
136         } else { /* control */
137             pri = PI_NET;
138             /*
139              * Initialize semaphore for this queue by pointing
140              * to the globale semaphore used for synchronizing all
141              * control messages.
142              */
143             wq->work_sema = &hv_vmbus_g_connection.control_sema;
144         }
145
146         sprintf(qname, "hv_%s_%u", name, qid);
147
148         /*
149          * Fixme:  FreeBSD 8.2 has a different prototype for
150          * taskqueue_create(), and for certain other taskqueue functions.
151          * We need to research the implications of these changes.
152          * Fixme:  Not sure when the changes were introduced.
153          */
154         wq->queue = taskqueue_create(qname, M_NOWAIT, taskqueue_thread_enqueue,
155             &wq->queue
156             #if __FreeBSD_version < 800000
157             , &wq->proc
158             #endif
159             );
160
161         if (wq->queue == NULL) {
162             free(wq, M_DEVBUF);
163             return (NULL);
164         }
165
166         if (taskqueue_start_threads(&wq->queue, 1, pri, "%s taskq", qname)) {
167             taskqueue_free(wq->queue);
168             free(wq, M_DEVBUF);
169             return (NULL);
170         }
171
172         qid++;
173
174         return (wq);
175 }
176
177 void
178 hv_work_queue_close(struct hv_work_queue *wq)
179 {
180         /*
181          * KYS: Need to drain the taskqueue
182          * before we close the hv_work_queue.
183          */
184         /*KYS: taskqueue_drain(wq->tq, ); */
185         taskqueue_free(wq->queue);
186         free(wq, M_DEVBUF);
187 }
188
189 /**
190  * @brief Create work item
191  */
192 int
193 hv_queue_work_item(
194         struct hv_work_queue *wq,
195         void (*callback)(void *), void *context)
196 {
197         struct hv_work_item *w = malloc(sizeof(struct hv_work_item),
198                                         M_DEVBUF, M_NOWAIT | M_ZERO);
199         KASSERT(w != NULL, ("Error VMBUS: Failed to allocate WorkItem\n"));
200         if (w == NULL)
201             return (ENOMEM);
202
203         w->callback = callback;
204         w->context = context;
205         w->wq = wq;
206
207         TASK_INIT(&w->work, 0, work_item_callback, w);
208
209         return (taskqueue_enqueue(wq->queue, &w->work));
210 }
211
212
213 /**
214  * @brief Allocate and initialize a vmbus channel object
215  */
216 hv_vmbus_channel*
217 hv_vmbus_allocate_channel(void)
218 {
219         hv_vmbus_channel* channel;
220
221         channel = (hv_vmbus_channel*) malloc(
222                                         sizeof(hv_vmbus_channel),
223                                         M_DEVBUF,
224                                         M_NOWAIT | M_ZERO);
225         KASSERT(channel != NULL, ("Error VMBUS: Failed to allocate channel!"));
226         if (channel == NULL)
227             return (NULL);
228
229         mtx_init(&channel->inbound_lock, "channel inbound", NULL, MTX_DEF);
230         mtx_init(&channel->sc_lock, "vmbus multi channel", NULL, MTX_DEF);
231
232         TAILQ_INIT(&channel->sc_list_anchor);
233
234         return (channel);
235 }
236
237 /**
238  * @brief Release the vmbus channel object itself
239  */
240 static inline void
241 ReleaseVmbusChannel(void *context)
242 {
243         hv_vmbus_channel* channel = (hv_vmbus_channel*) context;
244         free(channel, M_DEVBUF);
245 }
246
247 /**
248  * @brief Release the resources used by the vmbus channel object
249  */
250 void
251 hv_vmbus_free_vmbus_channel(hv_vmbus_channel* channel)
252 {
253         mtx_destroy(&channel->sc_lock);
254         mtx_destroy(&channel->inbound_lock);
255         /*
256          * We have to release the channel's workqueue/thread in
257          *  the vmbus's workqueue/thread context
258          * ie we can't destroy ourselves
259          */
260         hv_queue_work_item(hv_vmbus_g_connection.work_queue,
261             ReleaseVmbusChannel, (void *) channel);
262 }
263
264 /**
265  * @brief Process the offer by creating a channel/device
266  * associated with this offer
267  */
268 static void
269 vmbus_channel_process_offer(hv_vmbus_channel *new_channel)
270 {
271         boolean_t               f_new;
272         hv_vmbus_channel*       channel;
273         int                     ret;
274         uint32_t                relid;
275
276         f_new = TRUE;
277         channel = NULL;
278         relid = new_channel->offer_msg.child_rel_id;
279         /*
280          * Make sure this is a new offer
281          */
282         mtx_lock(&hv_vmbus_g_connection.channel_lock);
283         hv_vmbus_g_connection.channels[relid] = new_channel;
284
285         TAILQ_FOREACH(channel, &hv_vmbus_g_connection.channel_anchor,
286             list_entry)
287         {
288                 if (memcmp(&channel->offer_msg.offer.interface_type,
289                     &new_channel->offer_msg.offer.interface_type,
290                     sizeof(hv_guid)) == 0 &&
291                     memcmp(&channel->offer_msg.offer.interface_instance,
292                     &new_channel->offer_msg.offer.interface_instance,
293                     sizeof(hv_guid)) == 0) {
294                         f_new = FALSE;
295                         break;
296                 }
297         }
298
299         if (f_new) {
300                 /* Insert at tail */
301                 TAILQ_INSERT_TAIL(
302                     &hv_vmbus_g_connection.channel_anchor,
303                     new_channel,
304                     list_entry);
305         }
306         mtx_unlock(&hv_vmbus_g_connection.channel_lock);
307
308         /*XXX add new channel to percpu_list */
309
310         if (!f_new) {
311                 /*
312                  * Check if this is a sub channel.
313                  */
314                 if (new_channel->offer_msg.offer.sub_channel_index != 0) {
315                         /*
316                          * It is a sub channel offer, process it.
317                          */
318                         new_channel->primary_channel = channel;
319                         mtx_lock(&channel->sc_lock);
320                         TAILQ_INSERT_TAIL(
321                             &channel->sc_list_anchor,
322                             new_channel,
323                             sc_list_entry);
324                         mtx_unlock(&channel->sc_lock);
325
326                         /* Insert new channel into channel_anchor. */
327                         printf("VMBUS get multi-channel offer, rel=%u,sub=%u\n",
328                             new_channel->offer_msg.child_rel_id,
329                             new_channel->offer_msg.offer.sub_channel_index);    
330                         mtx_lock(&hv_vmbus_g_connection.channel_lock);
331                         TAILQ_INSERT_TAIL(&hv_vmbus_g_connection.channel_anchor,
332                             new_channel, list_entry);                           
333                         mtx_unlock(&hv_vmbus_g_connection.channel_lock);
334
335                         if(bootverbose)
336                                 printf("VMBUS: new multi-channel offer <%p>, "
337                                     "its primary channel is <%p>.\n",
338                                     new_channel, new_channel->primary_channel);
339
340                         /*XXX add it to percpu_list */
341
342                         new_channel->state = HV_CHANNEL_OPEN_STATE;
343                         if (channel->sc_creation_callback != NULL) {
344                                 channel->sc_creation_callback(new_channel);
345                         }
346                         return;
347                 }
348
349             hv_vmbus_free_vmbus_channel(new_channel);
350             return;
351         }
352
353         new_channel->state = HV_CHANNEL_OPEN_STATE;
354
355         /*
356          * Start the process of binding this offer to the driver
357          * (We need to set the device field before calling
358          * hv_vmbus_child_device_add())
359          */
360         new_channel->device = hv_vmbus_child_device_create(
361             new_channel->offer_msg.offer.interface_type,
362             new_channel->offer_msg.offer.interface_instance, new_channel);
363
364         /*
365          * Add the new device to the bus. This will kick off device-driver
366          * binding which eventually invokes the device driver's AddDevice()
367          * method.
368          */
369         ret = hv_vmbus_child_device_register(new_channel->device);
370         if (ret != 0) {
371                 mtx_lock(&hv_vmbus_g_connection.channel_lock);
372                 TAILQ_REMOVE(
373                     &hv_vmbus_g_connection.channel_anchor,
374                     new_channel,
375                     list_entry);
376                 mtx_unlock(&hv_vmbus_g_connection.channel_lock);
377                 hv_vmbus_free_vmbus_channel(new_channel);
378         }
379 }
380
381 /**
382  * Array of device guids that are performance critical. We try to distribute
383  * the interrupt load for these devices across all online cpus. 
384  */
385 static const hv_guid high_perf_devices[] = {
386         {HV_NIC_GUID, },
387         {HV_IDE_GUID, },
388         {HV_SCSI_GUID, },
389 };
390
391 enum {
392         PERF_CHN_NIC = 0,
393         PERF_CHN_IDE,
394         PERF_CHN_SCSI,
395         MAX_PERF_CHN,
396 };
397
398 /*
399  * We use this static number to distribute the channel interrupt load.
400  */
401 static uint32_t next_vcpu;
402
403 /**
404  * Starting with Win8, we can statically distribute the incoming
405  * channel interrupt load by binding a channel to VCPU. We
406  * implement here a simple round robin scheme for distributing
407  * the interrupt load.
408  * We will bind channels that are not performance critical to cpu 0 and
409  * performance critical channels (IDE, SCSI and Network) will be uniformly
410  * distributed across all available CPUs.
411  */
412 static void
413 vmbus_channel_select_cpu(hv_vmbus_channel *channel, hv_guid *guid)
414 {
415         uint32_t current_cpu;
416         int i;
417         boolean_t is_perf_channel = FALSE;
418
419         for (i = PERF_CHN_NIC; i < MAX_PERF_CHN; i++) {
420                 if (memcmp(guid->data, high_perf_devices[i].data,
421                     sizeof(hv_guid)) == 0) {
422                         is_perf_channel = TRUE;
423                         break;
424                 }
425         }
426
427         if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) ||
428             (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7) ||
429             (!is_perf_channel)) {
430                 /* Host's view of guest cpu */
431                 channel->target_vcpu = 0;
432                 /* Guest's own view of cpu */
433                 channel->target_cpu = 0;
434                 return;
435         }
436         /* mp_ncpus should have the number cpus currently online */
437         current_cpu = (++next_vcpu % mp_ncpus);
438         channel->target_cpu = current_cpu;
439         channel->target_vcpu =
440             hv_vmbus_g_context.hv_vcpu_index[current_cpu];
441         if (bootverbose)
442                 printf("VMBUS: Total online cpus %d, assign perf channel %d "
443                     "to vcpu %d, cpu %d\n", mp_ncpus, i, channel->target_vcpu,
444                     current_cpu);
445 }
446
447 /**
448  * @brief Handler for channel offers from Hyper-V/Azure
449  *
450  * Handler for channel offers from vmbus in parent partition. We ignore
451  * all offers except network and storage offers. For each network and storage
452  * offers, we create a channel object and queue a work item to the channel
453  * object to process the offer synchronously
454  */
455 static void
456 vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr)
457 {
458         hv_vmbus_channel_offer_channel* offer;
459         hv_vmbus_channel* new_channel;
460
461         offer = (hv_vmbus_channel_offer_channel*) hdr;
462
463         hv_guid *guidType;
464         hv_guid *guidInstance;
465
466         guidType = &offer->offer.interface_type;
467         guidInstance = &offer->offer.interface_instance;
468
469         /* Allocate the channel object and save this offer */
470         new_channel = hv_vmbus_allocate_channel();
471         if (new_channel == NULL)
472             return;
473
474         /*
475          * By default we setup state to enable batched
476          * reading. A specific service can choose to
477          * disable this prior to opening the channel.
478          */
479         new_channel->batched_reading = TRUE;
480
481         new_channel->signal_event_param =
482             (hv_vmbus_input_signal_event *)
483             (HV_ALIGN_UP((unsigned long)
484                 &new_channel->signal_event_buffer,
485                 HV_HYPERCALL_PARAM_ALIGN));
486
487         new_channel->signal_event_param->connection_id.as_uint32_t = 0; 
488         new_channel->signal_event_param->connection_id.u.id =
489             HV_VMBUS_EVENT_CONNECTION_ID;
490         new_channel->signal_event_param->flag_number = 0;
491         new_channel->signal_event_param->rsvd_z = 0;
492
493         if (hv_vmbus_protocal_version != HV_VMBUS_VERSION_WS2008) {
494                 new_channel->is_dedicated_interrupt =
495                     (offer->is_dedicated_interrupt != 0);
496                 new_channel->signal_event_param->connection_id.u.id =
497                     offer->connection_id;
498         }
499
500         /*
501          * Bind the channel to a chosen cpu.
502          */
503         vmbus_channel_select_cpu(new_channel,
504             &offer->offer.interface_type);
505
506         memcpy(&new_channel->offer_msg, offer,
507             sizeof(hv_vmbus_channel_offer_channel));
508         new_channel->monitor_group = (uint8_t) offer->monitor_id / 32;
509         new_channel->monitor_bit = (uint8_t) offer->monitor_id % 32;
510
511         vmbus_channel_process_offer(new_channel);
512 }
513
514 /**
515  * @brief Rescind offer handler.
516  *
517  * We queue a work item to process this offer
518  * synchronously
519  */
520 static void
521 vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr)
522 {
523         hv_vmbus_channel_rescind_offer* rescind;
524         hv_vmbus_channel*               channel;
525
526         rescind = (hv_vmbus_channel_rescind_offer*) hdr;
527
528         channel = hv_vmbus_g_connection.channels[rescind->child_rel_id];
529         if (channel == NULL) 
530             return;
531
532         hv_vmbus_child_device_unregister(channel->device);
533         mtx_lock(&hv_vmbus_g_connection.channel_lock);
534         hv_vmbus_g_connection.channels[rescind->child_rel_id] = NULL;
535         mtx_unlock(&hv_vmbus_g_connection.channel_lock);
536 }
537
538 /**
539  *
540  * @brief Invoked when all offers have been delivered.
541  */
542 static void
543 vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr)
544 {
545 }
546
547 /**
548  * @brief Open result handler.
549  *
550  * This is invoked when we received a response
551  * to our channel open request. Find the matching request, copy the
552  * response and signal the requesting thread.
553  */
554 static void
555 vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr)
556 {
557         hv_vmbus_channel_open_result*   result;
558         hv_vmbus_channel_msg_info*      msg_info;
559         hv_vmbus_channel_msg_header*    requestHeader;
560         hv_vmbus_channel_open_channel*  openMsg;
561
562         result = (hv_vmbus_channel_open_result*) hdr;
563
564         /*
565          * Find the open msg, copy the result and signal/unblock the wait event
566          */
567         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
568
569         TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
570             msg_list_entry) {
571             requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg;
572
573             if (requestHeader->message_type ==
574                     HV_CHANNEL_MESSAGE_OPEN_CHANNEL) {
575                 openMsg = (hv_vmbus_channel_open_channel*) msg_info->msg;
576                 if (openMsg->child_rel_id == result->child_rel_id
577                     && openMsg->open_id == result->open_id) {
578                     memcpy(&msg_info->response.open_result, result,
579                         sizeof(hv_vmbus_channel_open_result));
580                     sema_post(&msg_info->wait_sema);
581                     break;
582                 }
583             }
584         }
585         mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
586
587 }
588
589 /**
590  * @brief GPADL created handler.
591  *
592  * This is invoked when we received a response
593  * to our gpadl create request. Find the matching request, copy the
594  * response and signal the requesting thread.
595  */
596 static void
597 vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr)
598 {
599         hv_vmbus_channel_gpadl_created*         gpadl_created;
600         hv_vmbus_channel_msg_info*              msg_info;
601         hv_vmbus_channel_msg_header*            request_header;
602         hv_vmbus_channel_gpadl_header*          gpadl_header;
603
604         gpadl_created = (hv_vmbus_channel_gpadl_created*) hdr;
605
606         /* Find the establish msg, copy the result and signal/unblock
607          * the wait event
608          */
609         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
610         TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
611                 msg_list_entry) {
612             request_header = (hv_vmbus_channel_msg_header*) msg_info->msg;
613             if (request_header->message_type ==
614                     HV_CHANNEL_MESSAGEL_GPADL_HEADER) {
615                 gpadl_header =
616                     (hv_vmbus_channel_gpadl_header*) request_header;
617
618                 if ((gpadl_created->child_rel_id == gpadl_header->child_rel_id)
619                     && (gpadl_created->gpadl == gpadl_header->gpadl)) {
620                     memcpy(&msg_info->response.gpadl_created,
621                         gpadl_created,
622                         sizeof(hv_vmbus_channel_gpadl_created));
623                     sema_post(&msg_info->wait_sema);
624                     break;
625                 }
626             }
627         }
628         mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
629 }
630
631 /**
632  * @brief GPADL torndown handler.
633  *
634  * This is invoked when we received a respons
635  * to our gpadl teardown request. Find the matching request, copy the
636  * response and signal the requesting thread
637  */
638 static void
639 vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr)
640 {
641         hv_vmbus_channel_gpadl_torndown*        gpadl_torndown;
642         hv_vmbus_channel_msg_info*              msg_info;
643         hv_vmbus_channel_msg_header*            requestHeader;
644         hv_vmbus_channel_gpadl_teardown*        gpadlTeardown;
645
646         gpadl_torndown = (hv_vmbus_channel_gpadl_torndown*)hdr;
647
648         /*
649          * Find the open msg, copy the result and signal/unblock the
650          * wait event.
651          */
652
653         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
654
655         TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
656                 msg_list_entry) {
657             requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg;
658
659             if (requestHeader->message_type
660                     == HV_CHANNEL_MESSAGE_GPADL_TEARDOWN) {
661                 gpadlTeardown =
662                     (hv_vmbus_channel_gpadl_teardown*) requestHeader;
663
664                 if (gpadl_torndown->gpadl == gpadlTeardown->gpadl) {
665                     memcpy(&msg_info->response.gpadl_torndown,
666                         gpadl_torndown,
667                         sizeof(hv_vmbus_channel_gpadl_torndown));
668                     sema_post(&msg_info->wait_sema);
669                     break;
670                 }
671             }
672         }
673     mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
674 }
675
676 /**
677  * @brief Version response handler.
678  *
679  * This is invoked when we received a response
680  * to our initiate contact request. Find the matching request, copy th
681  * response and signal the requesting thread.
682  */
683 static void
684 vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr)
685 {
686         hv_vmbus_channel_msg_info*              msg_info;
687         hv_vmbus_channel_msg_header*            requestHeader;
688         hv_vmbus_channel_initiate_contact*      initiate;
689         hv_vmbus_channel_version_response*      versionResponse;
690
691         versionResponse = (hv_vmbus_channel_version_response*)hdr;
692
693         mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
694         TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
695             msg_list_entry) {
696             requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg;
697             if (requestHeader->message_type
698                 == HV_CHANNEL_MESSAGE_INITIATED_CONTACT) {
699                 initiate =
700                     (hv_vmbus_channel_initiate_contact*) requestHeader;
701                 memcpy(&msg_info->response.version_response,
702                     versionResponse,
703                     sizeof(hv_vmbus_channel_version_response));
704                 sema_post(&msg_info->wait_sema);
705             }
706         }
707     mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
708
709 }
710
711 /**
712  * @brief Handler for channel protocol messages.
713  *
714  * This is invoked in the vmbus worker thread context.
715  */
716 void
717 hv_vmbus_on_channel_message(void *context)
718 {
719         hv_vmbus_message*               msg;
720         hv_vmbus_channel_msg_header*    hdr;
721         int                             size;
722
723         msg = (hv_vmbus_message*) context;
724         hdr = (hv_vmbus_channel_msg_header*) msg->u.payload;
725         size = msg->header.payload_size;
726
727         if (hdr->message_type >= HV_CHANNEL_MESSAGE_COUNT) {
728             free(msg, M_DEVBUF);
729             return;
730         }
731
732         if (g_channel_message_table[hdr->message_type].messageHandler) {
733             g_channel_message_table[hdr->message_type].messageHandler(hdr);
734         }
735
736         /* Free the msg that was allocated in VmbusOnMsgDPC() */
737         free(msg, M_DEVBUF);
738 }
739
740 /**
741  *  @brief Send a request to get all our pending offers.
742  */
743 int
744 hv_vmbus_request_channel_offers(void)
745 {
746         int                             ret;
747         hv_vmbus_channel_msg_header*    msg;
748         hv_vmbus_channel_msg_info*      msg_info;
749
750         msg_info = (hv_vmbus_channel_msg_info *)
751             malloc(sizeof(hv_vmbus_channel_msg_info)
752                     + sizeof(hv_vmbus_channel_msg_header), M_DEVBUF, M_NOWAIT);
753
754         if (msg_info == NULL) {
755             if(bootverbose)
756                 printf("Error VMBUS: malloc failed for Request Offers\n");
757             return (ENOMEM);
758         }
759
760         msg = (hv_vmbus_channel_msg_header*) msg_info->msg;
761         msg->message_type = HV_CHANNEL_MESSAGE_REQUEST_OFFERS;
762
763         ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_msg_header));
764
765         if (msg_info)
766             free(msg_info, M_DEVBUF);
767
768         return (ret);
769 }
770
771 /**
772  * @brief Release channels that are unattached/unconnected (i.e., no drivers associated)
773  */
774 void
775 hv_vmbus_release_unattached_channels(void) 
776 {
777         hv_vmbus_channel *channel;
778
779         mtx_lock(&hv_vmbus_g_connection.channel_lock);
780
781         while (!TAILQ_EMPTY(&hv_vmbus_g_connection.channel_anchor)) {
782             channel = TAILQ_FIRST(&hv_vmbus_g_connection.channel_anchor);
783             TAILQ_REMOVE(&hv_vmbus_g_connection.channel_anchor,
784                             channel, list_entry);
785
786             hv_vmbus_child_device_unregister(channel->device);
787             hv_vmbus_free_vmbus_channel(channel);
788         }
789         bzero(hv_vmbus_g_connection.channels, 
790                 sizeof(hv_vmbus_channel*) * HV_CHANNEL_MAX_COUNT);
791         mtx_unlock(&hv_vmbus_g_connection.channel_lock);
792 }
793
794 /**
795  * @brief Select the best outgoing channel
796  * 
797  * The channel whose vcpu binding is closest to the currect vcpu will
798  * be selected.
799  * If no multi-channel, always select primary channel
800  * 
801  * @param primary - primary channel
802  */
803 struct hv_vmbus_channel *
804 vmbus_select_outgoing_channel(struct hv_vmbus_channel *primary)
805 {
806         hv_vmbus_channel *new_channel = NULL;
807         hv_vmbus_channel *outgoing_channel = primary;
808         int old_cpu_distance = 0;
809         int new_cpu_distance = 0;
810         int cur_vcpu = 0;
811         int smp_pro_id = PCPU_GET(cpuid);
812
813         if (TAILQ_EMPTY(&primary->sc_list_anchor)) {
814                 return outgoing_channel;
815         }
816
817         if (smp_pro_id >= MAXCPU) {
818                 return outgoing_channel;
819         }
820
821         cur_vcpu = hv_vmbus_g_context.hv_vcpu_index[smp_pro_id];
822         
823         TAILQ_FOREACH(new_channel, &primary->sc_list_anchor, sc_list_entry) {
824                 if (new_channel->state != HV_CHANNEL_OPENED_STATE){
825                         continue;
826                 }
827
828                 if (new_channel->target_vcpu == cur_vcpu){
829                         return new_channel;
830                 }
831
832                 old_cpu_distance = ((outgoing_channel->target_vcpu > cur_vcpu) ?
833                     (outgoing_channel->target_vcpu - cur_vcpu) :
834                     (cur_vcpu - outgoing_channel->target_vcpu));
835
836                 new_cpu_distance = ((new_channel->target_vcpu > cur_vcpu) ?
837                     (new_channel->target_vcpu - cur_vcpu) :
838                     (cur_vcpu - new_channel->target_vcpu));
839
840                 if (old_cpu_distance < new_cpu_distance) {
841                         continue;
842                 }
843
844                 outgoing_channel = new_channel;
845         }
846
847         return(outgoing_channel);
848 }