2 * Copyright (c) 2009-2012 Microsoft Corp.
3 * Copyright (c) 2012 NetApp Inc.
4 * Copyright (c) 2012 Citrix Inc.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice unmodified, this list of conditions, and the following
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.
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.
29 #include <sys/param.h>
32 #include "hv_vmbus_priv.h"
34 typedef void (*hv_pfn_channel_msg_handler)(hv_vmbus_channel_msg_header* msg);
36 typedef struct hv_vmbus_channel_msg_table_entry {
37 hv_vmbus_channel_msg_type messageType;
38 hv_pfn_channel_msg_handler messageHandler;
39 } hv_vmbus_channel_msg_table_entry;
45 static void vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr);
46 static void vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr);
47 static void vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr);
48 static void vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr);
49 static void vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr);
50 static void vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr);
51 static void vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr);
52 static void vmbus_channel_process_offer(void *context);
55 * Channel message dispatch table
57 hv_vmbus_channel_msg_table_entry
58 g_channel_message_table[HV_CHANNEL_MESSAGE_COUNT] = {
59 { HV_CHANNEL_MESSAGE_INVALID, NULL },
60 { HV_CHANNEL_MESSAGE_OFFER_CHANNEL, vmbus_channel_on_offer },
61 { HV_CHANNEL_MESSAGE_RESCIND_CHANNEL_OFFER,
62 vmbus_channel_on_offer_rescind },
63 { HV_CHANNEL_MESSAGE_REQUEST_OFFERS, NULL },
64 { HV_CHANNEL_MESSAGE_ALL_OFFERS_DELIVERED,
65 vmbus_channel_on_offers_delivered },
66 { HV_CHANNEL_MESSAGE_OPEN_CHANNEL, NULL },
67 { HV_CHANNEL_MESSAGE_OPEN_CHANNEL_RESULT,
68 vmbus_channel_on_open_result },
69 { HV_CHANNEL_MESSAGE_CLOSE_CHANNEL, NULL },
70 { HV_CHANNEL_MESSAGEL_GPADL_HEADER, NULL },
71 { HV_CHANNEL_MESSAGE_GPADL_BODY, NULL },
72 { HV_CHANNEL_MESSAGE_GPADL_CREATED,
73 vmbus_channel_on_gpadl_created },
74 { HV_CHANNEL_MESSAGE_GPADL_TEARDOWN, NULL },
75 { HV_CHANNEL_MESSAGE_GPADL_TORNDOWN,
76 vmbus_channel_on_gpadl_torndown },
77 { HV_CHANNEL_MESSAGE_REL_ID_RELEASED, NULL },
78 { HV_CHANNEL_MESSAGE_INITIATED_CONTACT, NULL },
79 { HV_CHANNEL_MESSAGE_VERSION_RESPONSE,
80 vmbus_channel_on_version_response },
81 { HV_CHANNEL_MESSAGE_UNLOAD, NULL }
86 * Implementation of the work abstraction.
89 work_item_callback(void *work, int pending)
91 struct hv_work_item *w = (struct hv_work_item *)work;
94 * Serialize work execution.
96 if (w->wq->work_sema != NULL) {
97 sema_wait(w->wq->work_sema);
100 w->callback(w->context);
102 if (w->wq->work_sema != NULL) {
103 sema_post(w->wq->work_sema);
109 struct hv_work_queue*
110 hv_work_queue_create(char* name)
112 static unsigned int qid = 0;
115 struct hv_work_queue* wq;
117 wq = malloc(sizeof(struct hv_work_queue), M_DEVBUF, M_NOWAIT | M_ZERO);
118 KASSERT(wq != NULL, ("Error VMBUS: Failed to allocate work_queue\n"));
123 * We use work abstraction to handle messages
124 * coming from the host and these are typically offers.
125 * Some FreeBsd drivers appear to have a concurrency issue
126 * where probe/attach needs to be serialized. We ensure that
127 * by having only one thread process work elements in a
128 * specific queue by serializing work execution.
131 if (strcmp(name, "vmbusQ") == 0) {
133 } else { /* control */
136 * Initialize semaphore for this queue by pointing
137 * to the globale semaphore used for synchronizing all
140 wq->work_sema = &hv_vmbus_g_connection.control_sema;
143 sprintf(qname, "hv_%s_%u", name, qid);
146 * Fixme: FreeBSD 8.2 has a different prototype for
147 * taskqueue_create(), and for certain other taskqueue functions.
148 * We need to research the implications of these changes.
149 * Fixme: Not sure when the changes were introduced.
151 wq->queue = taskqueue_create(qname, M_NOWAIT, taskqueue_thread_enqueue,
153 #if __FreeBSD_version < 800000
158 if (wq->queue == NULL) {
163 if (taskqueue_start_threads(&wq->queue, 1, pri, "%s taskq", qname)) {
164 taskqueue_free(wq->queue);
175 hv_work_queue_close(struct hv_work_queue *wq)
178 * KYS: Need to drain the taskqueue
179 * before we close the hv_work_queue.
181 /*KYS: taskqueue_drain(wq->tq, ); */
182 taskqueue_free(wq->queue);
187 * @brief Create work item
191 struct hv_work_queue *wq,
192 void (*callback)(void *), void *context)
194 struct hv_work_item *w = malloc(sizeof(struct hv_work_item),
195 M_DEVBUF, M_NOWAIT | M_ZERO);
196 KASSERT(w != NULL, ("Error VMBUS: Failed to allocate WorkItem\n"));
200 w->callback = callback;
201 w->context = context;
204 TASK_INIT(&w->work, 0, work_item_callback, w);
206 return (taskqueue_enqueue(wq->queue, &w->work));
210 * @brief Rescind the offer by initiating a device removal
213 vmbus_channel_process_rescind_offer(void *context)
215 hv_vmbus_channel* channel = (hv_vmbus_channel*) context;
216 hv_vmbus_child_device_unregister(channel->device);
220 * @brief Allocate and initialize a vmbus channel object
223 hv_vmbus_allocate_channel(void)
225 hv_vmbus_channel* channel;
227 channel = (hv_vmbus_channel*) malloc(
228 sizeof(hv_vmbus_channel),
231 KASSERT(channel != NULL, ("Error VMBUS: Failed to allocate channel!"));
235 mtx_init(&channel->inbound_lock, "channel inbound", NULL, MTX_DEF);
237 channel->control_work_queue = hv_work_queue_create("control");
239 if (channel->control_work_queue == NULL) {
240 mtx_destroy(&channel->inbound_lock);
241 free(channel, M_DEVBUF);
249 * @brief Release the vmbus channel object itself
252 ReleaseVmbusChannel(void *context)
254 hv_vmbus_channel* channel = (hv_vmbus_channel*) context;
255 hv_work_queue_close(channel->control_work_queue);
256 free(channel, M_DEVBUF);
260 * @brief Release the resources used by the vmbus channel object
263 hv_vmbus_free_vmbus_channel(hv_vmbus_channel* channel)
265 mtx_destroy(&channel->inbound_lock);
267 * We have to release the channel's workqueue/thread in
268 * the vmbus's workqueue/thread context
269 * ie we can't destroy ourselves
271 hv_queue_work_item(hv_vmbus_g_connection.work_queue,
272 ReleaseVmbusChannel, (void *) channel);
276 * @brief Process the offer by creating a channel/device
277 * associated with this offer
280 vmbus_channel_process_offer(void *context)
283 hv_vmbus_channel* new_channel;
285 hv_vmbus_channel* channel;
287 new_channel = (hv_vmbus_channel*) context;
292 * Make sure this is a new offer
294 mtx_lock_spin(&hv_vmbus_g_connection.channel_lock);
296 TAILQ_FOREACH(channel, &hv_vmbus_g_connection.channel_anchor,
300 &channel->offer_msg.offer.interface_type,
301 &new_channel->offer_msg.offer.interface_type,
304 &channel->offer_msg.offer.interface_instance,
305 &new_channel->offer_msg.offer.interface_instance,
315 &hv_vmbus_g_connection.channel_anchor,
319 mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock);
322 hv_vmbus_free_vmbus_channel(new_channel);
327 * Start the process of binding this offer to the driver
328 * (We need to set the device field before calling
329 * hv_vmbus_child_device_add())
331 new_channel->device = hv_vmbus_child_device_create(
332 new_channel->offer_msg.offer.interface_type,
333 new_channel->offer_msg.offer.interface_instance, new_channel);
336 * TODO - the HV_CHANNEL_OPEN_STATE flag should not be set below
337 * but in the "open" channel request. The ret != 0 logic below
338 * doesn't take into account that a channel
339 * may have been opened successfully
343 * Add the new device to the bus. This will kick off device-driver
344 * binding which eventually invokes the device driver's AddDevice()
347 ret = hv_vmbus_child_device_register(new_channel->device);
349 mtx_lock_spin(&hv_vmbus_g_connection.channel_lock);
351 &hv_vmbus_g_connection.channel_anchor,
354 mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock);
355 hv_vmbus_free_vmbus_channel(new_channel);
358 * This state is used to indicate a successful open
359 * so that when we do close the channel normally,
360 * we can clean up properly
362 new_channel->state = HV_CHANNEL_OPEN_STATE;
368 * @brief Handler for channel offers from Hyper-V/Azure
370 * Handler for channel offers from vmbus in parent partition. We ignore
371 * all offers except network and storage offers. For each network and storage
372 * offers, we create a channel object and queue a work item to the channel
373 * object to process the offer synchronously
376 vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr)
378 hv_vmbus_channel_offer_channel* offer;
379 hv_vmbus_channel* new_channel;
381 offer = (hv_vmbus_channel_offer_channel*) hdr;
384 hv_guid *guidInstance;
386 guidType = &offer->offer.interface_type;
387 guidInstance = &offer->offer.interface_instance;
389 /* Allocate the channel object and save this offer */
390 new_channel = hv_vmbus_allocate_channel();
391 if (new_channel == NULL)
394 memcpy(&new_channel->offer_msg, offer,
395 sizeof(hv_vmbus_channel_offer_channel));
396 new_channel->monitor_group = (uint8_t) offer->monitor_id / 32;
397 new_channel->monitor_bit = (uint8_t) offer->monitor_id % 32;
399 /* TODO: Make sure the offer comes from our parent partition */
401 new_channel->control_work_queue,
402 vmbus_channel_process_offer,
407 * @brief Rescind offer handler.
409 * We queue a work item to process this offer
413 vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr)
415 hv_vmbus_channel_rescind_offer* rescind;
416 hv_vmbus_channel* channel;
418 rescind = (hv_vmbus_channel_rescind_offer*) hdr;
420 channel = hv_vmbus_get_channel_from_rel_id(rescind->child_rel_id);
424 hv_queue_work_item(channel->control_work_queue,
425 vmbus_channel_process_rescind_offer, channel);
430 * @brief Invoked when all offers have been delivered.
433 vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr)
438 * @brief Open result handler.
440 * This is invoked when we received a response
441 * to our channel open request. Find the matching request, copy the
442 * response and signal the requesting thread.
445 vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr)
447 hv_vmbus_channel_open_result* result;
448 hv_vmbus_channel_msg_info* msg_info;
449 hv_vmbus_channel_msg_header* requestHeader;
450 hv_vmbus_channel_open_channel* openMsg;
452 result = (hv_vmbus_channel_open_result*) hdr;
455 * Find the open msg, copy the result and signal/unblock the wait event
457 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
459 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
461 requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg;
463 if (requestHeader->message_type ==
464 HV_CHANNEL_MESSAGE_OPEN_CHANNEL) {
465 openMsg = (hv_vmbus_channel_open_channel*) msg_info->msg;
466 if (openMsg->child_rel_id == result->child_rel_id
467 && openMsg->open_id == result->open_id) {
468 memcpy(&msg_info->response.open_result, result,
469 sizeof(hv_vmbus_channel_open_result));
470 sema_post(&msg_info->wait_sema);
475 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
480 * @brief GPADL created handler.
482 * This is invoked when we received a response
483 * to our gpadl create request. Find the matching request, copy the
484 * response and signal the requesting thread.
487 vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr)
489 hv_vmbus_channel_gpadl_created* gpadl_created;
490 hv_vmbus_channel_msg_info* msg_info;
491 hv_vmbus_channel_msg_header* request_header;
492 hv_vmbus_channel_gpadl_header* gpadl_header;
494 gpadl_created = (hv_vmbus_channel_gpadl_created*) hdr;
496 /* Find the establish msg, copy the result and signal/unblock
499 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
500 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
502 request_header = (hv_vmbus_channel_msg_header*) msg_info->msg;
503 if (request_header->message_type ==
504 HV_CHANNEL_MESSAGEL_GPADL_HEADER) {
506 (hv_vmbus_channel_gpadl_header*) request_header;
508 if ((gpadl_created->child_rel_id == gpadl_header->child_rel_id)
509 && (gpadl_created->gpadl == gpadl_header->gpadl)) {
510 memcpy(&msg_info->response.gpadl_created,
512 sizeof(hv_vmbus_channel_gpadl_created));
513 sema_post(&msg_info->wait_sema);
518 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
522 * @brief GPADL torndown handler.
524 * This is invoked when we received a respons
525 * to our gpadl teardown request. Find the matching request, copy the
526 * response and signal the requesting thread
529 vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr)
531 hv_vmbus_channel_gpadl_torndown* gpadl_torndown;
532 hv_vmbus_channel_msg_info* msg_info;
533 hv_vmbus_channel_msg_header* requestHeader;
534 hv_vmbus_channel_gpadl_teardown* gpadlTeardown;
536 gpadl_torndown = (hv_vmbus_channel_gpadl_torndown*)hdr;
539 * Find the open msg, copy the result and signal/unblock the
543 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
545 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
547 requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg;
549 if (requestHeader->message_type
550 == HV_CHANNEL_MESSAGE_GPADL_TEARDOWN) {
552 (hv_vmbus_channel_gpadl_teardown*) requestHeader;
554 if (gpadl_torndown->gpadl == gpadlTeardown->gpadl) {
555 memcpy(&msg_info->response.gpadl_torndown,
557 sizeof(hv_vmbus_channel_gpadl_torndown));
558 sema_post(&msg_info->wait_sema);
563 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
567 * @brief Version response handler.
569 * This is invoked when we received a response
570 * to our initiate contact request. Find the matching request, copy th
571 * response and signal the requesting thread.
574 vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr)
576 hv_vmbus_channel_msg_info* msg_info;
577 hv_vmbus_channel_msg_header* requestHeader;
578 hv_vmbus_channel_initiate_contact* initiate;
579 hv_vmbus_channel_version_response* versionResponse;
581 versionResponse = (hv_vmbus_channel_version_response*)hdr;
583 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
584 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor,
586 requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg;
587 if (requestHeader->message_type
588 == HV_CHANNEL_MESSAGE_INITIATED_CONTACT) {
590 (hv_vmbus_channel_initiate_contact*) requestHeader;
591 memcpy(&msg_info->response.version_response,
593 sizeof(hv_vmbus_channel_version_response));
594 sema_post(&msg_info->wait_sema);
597 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
602 * @brief Handler for channel protocol messages.
604 * This is invoked in the vmbus worker thread context.
607 hv_vmbus_on_channel_message(void *context)
609 hv_vmbus_message* msg;
610 hv_vmbus_channel_msg_header* hdr;
613 msg = (hv_vmbus_message*) context;
614 hdr = (hv_vmbus_channel_msg_header*) msg->u.payload;
615 size = msg->header.payload_size;
617 if (hdr->message_type >= HV_CHANNEL_MESSAGE_COUNT) {
622 if (g_channel_message_table[hdr->message_type].messageHandler) {
623 g_channel_message_table[hdr->message_type].messageHandler(hdr);
626 /* Free the msg that was allocated in VmbusOnMsgDPC() */
631 * @brief Send a request to get all our pending offers.
634 hv_vmbus_request_channel_offers(void)
637 hv_vmbus_channel_msg_header* msg;
638 hv_vmbus_channel_msg_info* msg_info;
640 msg_info = (hv_vmbus_channel_msg_info *)
641 malloc(sizeof(hv_vmbus_channel_msg_info)
642 + sizeof(hv_vmbus_channel_msg_header), M_DEVBUF, M_NOWAIT);
644 if (msg_info == NULL) {
646 printf("Error VMBUS: malloc failed for Request Offers\n");
650 msg = (hv_vmbus_channel_msg_header*) msg_info->msg;
651 msg->message_type = HV_CHANNEL_MESSAGE_REQUEST_OFFERS;
653 ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_msg_header));
656 free(msg_info, M_DEVBUF);
662 * @brief Release channels that are unattached/unconnected (i.e., no drivers associated)
665 hv_vmbus_release_unattached_channels(void)
667 hv_vmbus_channel *channel;
669 mtx_lock_spin(&hv_vmbus_g_connection.channel_lock);
671 while (!TAILQ_EMPTY(&hv_vmbus_g_connection.channel_anchor)) {
672 channel = TAILQ_FIRST(&hv_vmbus_g_connection.channel_anchor);
673 TAILQ_REMOVE(&hv_vmbus_g_connection.channel_anchor,
674 channel, list_entry);
676 hv_vmbus_child_device_unregister(channel->device);
677 hv_vmbus_free_vmbus_channel(channel);
679 mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock);