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