hv_channel_mgmt.c revision 256281
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 34typedef void (*hv_pfn_channel_msg_handler)(hv_vmbus_channel_msg_header* msg); 35 36typedef 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; 40 41/* 42 * Internal functions 43 */ 44 45static void vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr); 46static void vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr); 47static void vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr); 48static void vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr); 49static void vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr); 50static void vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr); 51static void vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr); 52static void vmbus_channel_process_offer(void *context); 53 54/** 55 * Channel message dispatch table 56 */ 57hv_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 } 82}; 83 84 85/** 86 * Implementation of the work abstraction. 87 */ 88static void 89work_item_callback(void *work, int pending) 90{ 91 struct hv_work_item *w = (struct hv_work_item *)work; 92 93 /* 94 * Serialize work execution. 95 */ 96 if (w->wq->work_sema != NULL) { 97 sema_wait(w->wq->work_sema); 98 } 99 100 w->callback(w->context); 101 102 if (w->wq->work_sema != NULL) { 103 sema_post(w->wq->work_sema); 104 } 105 106 free(w, M_DEVBUF); 107} 108 109struct hv_work_queue* 110hv_work_queue_create(char* name) 111{ 112 static unsigned int qid = 0; 113 char qname[64]; 114 int pri; 115 struct hv_work_queue* wq; 116 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")); 119 if (wq == NULL) 120 return (NULL); 121 122 /* 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. 129 * 130 */ 131 if (strcmp(name, "vmbusQ") == 0) { 132 pri = PI_DISK; 133 } else { /* control */ 134 pri = PI_NET; 135 /* 136 * Initialize semaphore for this queue by pointing 137 * to the globale semaphore used for synchronizing all 138 * control messages. 139 */ 140 wq->work_sema = &hv_vmbus_g_connection.control_sema; 141 } 142 143 sprintf(qname, "hv_%s_%u", name, qid); 144 145 /* 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. 150 */ 151 wq->queue = taskqueue_create(qname, M_NOWAIT, taskqueue_thread_enqueue, 152 &wq->queue 153 #if __FreeBSD_version < 800000 154 , &wq->proc 155 #endif 156 ); 157 158 if (wq->queue == NULL) { 159 free(wq, M_DEVBUF); 160 return (NULL); 161 } 162 163 if (taskqueue_start_threads(&wq->queue, 1, pri, "%s taskq", qname)) { 164 taskqueue_free(wq->queue); 165 free(wq, M_DEVBUF); 166 return (NULL); 167 } 168 169 qid++; 170 171 return (wq); 172} 173 174void 175hv_work_queue_close(struct hv_work_queue *wq) 176{ 177 /* 178 * KYS: Need to drain the taskqueue 179 * before we close the hv_work_queue. 180 */ 181 /*KYS: taskqueue_drain(wq->tq, ); */ 182 taskqueue_free(wq->queue); 183 free(wq, M_DEVBUF); 184} 185 186/** 187 * @brief Create work item 188 */ 189int 190hv_queue_work_item( 191 struct hv_work_queue *wq, 192 void (*callback)(void *), void *context) 193{ 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")); 197 if (w == NULL) 198 return (ENOMEM); 199 200 w->callback = callback; 201 w->context = context; 202 w->wq = wq; 203 204 TASK_INIT(&w->work, 0, work_item_callback, w); 205 206 return (taskqueue_enqueue(wq->queue, &w->work)); 207} 208 209/** 210 * @brief Rescind the offer by initiating a device removal 211 */ 212static void 213vmbus_channel_process_rescind_offer(void *context) 214{ 215 hv_vmbus_channel* channel = (hv_vmbus_channel*) context; 216 hv_vmbus_child_device_unregister(channel->device); 217} 218 219/** 220 * @brief Allocate and initialize a vmbus channel object 221 */ 222hv_vmbus_channel* 223hv_vmbus_allocate_channel(void) 224{ 225 hv_vmbus_channel* channel; 226 227 channel = (hv_vmbus_channel*) malloc( 228 sizeof(hv_vmbus_channel), 229 M_DEVBUF, 230 M_NOWAIT | M_ZERO); 231 KASSERT(channel != NULL, ("Error VMBUS: Failed to allocate channel!")); 232 if (channel == NULL) 233 return (NULL); 234 235 mtx_init(&channel->inbound_lock, "channel inbound", NULL, MTX_DEF); 236 237 channel->control_work_queue = hv_work_queue_create("control"); 238 239 if (channel->control_work_queue == NULL) { 240 mtx_destroy(&channel->inbound_lock); 241 free(channel, M_DEVBUF); 242 return (NULL); 243 } 244 245 return (channel); 246} 247 248/** 249 * @brief Release the vmbus channel object itself 250 */ 251static inline void 252ReleaseVmbusChannel(void *context) 253{ 254 hv_vmbus_channel* channel = (hv_vmbus_channel*) context; 255 hv_work_queue_close(channel->control_work_queue); 256 free(channel, M_DEVBUF); 257} 258 259/** 260 * @brief Release the resources used by the vmbus channel object 261 */ 262void 263hv_vmbus_free_vmbus_channel(hv_vmbus_channel* channel) 264{ 265 mtx_destroy(&channel->inbound_lock); 266 /* 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 270 */ 271 hv_queue_work_item(hv_vmbus_g_connection.work_queue, 272 ReleaseVmbusChannel, (void *) channel); 273} 274 275/** 276 * @brief Process the offer by creating a channel/device 277 * associated with this offer 278 */ 279static void 280vmbus_channel_process_offer(void *context) 281{ 282 int ret; 283 hv_vmbus_channel* new_channel; 284 boolean_t f_new; 285 hv_vmbus_channel* channel; 286 287 new_channel = (hv_vmbus_channel*) context; 288 f_new = TRUE; 289 channel = NULL; 290 291 /* 292 * Make sure this is a new offer 293 */ 294 mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); 295 296 TAILQ_FOREACH(channel, &hv_vmbus_g_connection.channel_anchor, 297 list_entry) 298 { 299 if (!memcmp( 300 &channel->offer_msg.offer.interface_type, 301 &new_channel->offer_msg.offer.interface_type, 302 sizeof(hv_guid)) 303 && !memcmp( 304 &channel->offer_msg.offer.interface_instance, 305 &new_channel->offer_msg.offer.interface_instance, 306 sizeof(hv_guid))) { 307 f_new = FALSE; 308 break; 309 } 310 } 311 312 if (f_new) { 313 /* Insert at tail */ 314 TAILQ_INSERT_TAIL( 315 &hv_vmbus_g_connection.channel_anchor, 316 new_channel, 317 list_entry); 318 } 319 mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); 320 321 if (!f_new) { 322 hv_vmbus_free_vmbus_channel(new_channel); 323 return; 324 } 325 326 /* 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()) 330 */ 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); 334 335 /* 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 340 */ 341 342 /* 343 * Add the new device to the bus. This will kick off device-driver 344 * binding which eventually invokes the device driver's AddDevice() 345 * method. 346 */ 347 ret = hv_vmbus_child_device_register(new_channel->device); 348 if (ret != 0) { 349 mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); 350 TAILQ_REMOVE( 351 &hv_vmbus_g_connection.channel_anchor, 352 new_channel, 353 list_entry); 354 mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); 355 hv_vmbus_free_vmbus_channel(new_channel); 356 } else { 357 /* 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 361 */ 362 new_channel->state = HV_CHANNEL_OPEN_STATE; 363 364 } 365} 366 367/** 368 * @brief Handler for channel offers from Hyper-V/Azure 369 * 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 374 */ 375static void 376vmbus_channel_on_offer(hv_vmbus_channel_msg_header* hdr) 377{ 378 hv_vmbus_channel_offer_channel* offer; 379 hv_vmbus_channel* new_channel; 380 381 offer = (hv_vmbus_channel_offer_channel*) hdr; 382 383 hv_guid *guidType; 384 hv_guid *guidInstance; 385 386 guidType = &offer->offer.interface_type; 387 guidInstance = &offer->offer.interface_instance; 388 389 /* Allocate the channel object and save this offer */ 390 new_channel = hv_vmbus_allocate_channel(); 391 if (new_channel == NULL) 392 return; 393 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; 398 399 /* TODO: Make sure the offer comes from our parent partition */ 400 hv_queue_work_item( 401 new_channel->control_work_queue, 402 vmbus_channel_process_offer, 403 new_channel); 404} 405 406/** 407 * @brief Rescind offer handler. 408 * 409 * We queue a work item to process this offer 410 * synchronously 411 */ 412static void 413vmbus_channel_on_offer_rescind(hv_vmbus_channel_msg_header* hdr) 414{ 415 hv_vmbus_channel_rescind_offer* rescind; 416 hv_vmbus_channel* channel; 417 418 rescind = (hv_vmbus_channel_rescind_offer*) hdr; 419 420 channel = hv_vmbus_get_channel_from_rel_id(rescind->child_rel_id); 421 if (channel == NULL) 422 return; 423 424 hv_queue_work_item(channel->control_work_queue, 425 vmbus_channel_process_rescind_offer, channel); 426} 427 428/** 429 * 430 * @brief Invoked when all offers have been delivered. 431 */ 432static void 433vmbus_channel_on_offers_delivered(hv_vmbus_channel_msg_header* hdr) 434{ 435} 436 437/** 438 * @brief Open result handler. 439 * 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. 443 */ 444static void 445vmbus_channel_on_open_result(hv_vmbus_channel_msg_header* hdr) 446{ 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; 451 452 result = (hv_vmbus_channel_open_result*) hdr; 453 454 /* 455 * Find the open msg, copy the result and signal/unblock the wait event 456 */ 457 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 458 459 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor, 460 msg_list_entry) { 461 requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg; 462 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); 471 break; 472 } 473 } 474 } 475 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 476 477} 478 479/** 480 * @brief GPADL created handler. 481 * 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. 485 */ 486static void 487vmbus_channel_on_gpadl_created(hv_vmbus_channel_msg_header* hdr) 488{ 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; 493 494 gpadl_created = (hv_vmbus_channel_gpadl_created*) hdr; 495 496 /* Find the establish msg, copy the result and signal/unblock 497 * the wait event 498 */ 499 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 500 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor, 501 msg_list_entry) { 502 request_header = (hv_vmbus_channel_msg_header*) msg_info->msg; 503 if (request_header->message_type == 504 HV_CHANNEL_MESSAGEL_GPADL_HEADER) { 505 gpadl_header = 506 (hv_vmbus_channel_gpadl_header*) request_header; 507 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, 511 gpadl_created, 512 sizeof(hv_vmbus_channel_gpadl_created)); 513 sema_post(&msg_info->wait_sema); 514 break; 515 } 516 } 517 } 518 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 519} 520 521/** 522 * @brief GPADL torndown handler. 523 * 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 527 */ 528static void 529vmbus_channel_on_gpadl_torndown(hv_vmbus_channel_msg_header* hdr) 530{ 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; 535 536 gpadl_torndown = (hv_vmbus_channel_gpadl_torndown*)hdr; 537 538 /* 539 * Find the open msg, copy the result and signal/unblock the 540 * wait event. 541 */ 542 543 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 544 545 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor, 546 msg_list_entry) { 547 requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg; 548 549 if (requestHeader->message_type 550 == HV_CHANNEL_MESSAGE_GPADL_TEARDOWN) { 551 gpadlTeardown = 552 (hv_vmbus_channel_gpadl_teardown*) requestHeader; 553 554 if (gpadl_torndown->gpadl == gpadlTeardown->gpadl) { 555 memcpy(&msg_info->response.gpadl_torndown, 556 gpadl_torndown, 557 sizeof(hv_vmbus_channel_gpadl_torndown)); 558 sema_post(&msg_info->wait_sema); 559 break; 560 } 561 } 562 } 563 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 564} 565 566/** 567 * @brief Version response handler. 568 * 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. 572 */ 573static void 574vmbus_channel_on_version_response(hv_vmbus_channel_msg_header* hdr) 575{ 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; 580 581 versionResponse = (hv_vmbus_channel_version_response*)hdr; 582 583 mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock); 584 TAILQ_FOREACH(msg_info, &hv_vmbus_g_connection.channel_msg_anchor, 585 msg_list_entry) { 586 requestHeader = (hv_vmbus_channel_msg_header*) msg_info->msg; 587 if (requestHeader->message_type 588 == HV_CHANNEL_MESSAGE_INITIATED_CONTACT) { 589 initiate = 590 (hv_vmbus_channel_initiate_contact*) requestHeader; 591 memcpy(&msg_info->response.version_response, 592 versionResponse, 593 sizeof(hv_vmbus_channel_version_response)); 594 sema_post(&msg_info->wait_sema); 595 } 596 } 597 mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock); 598 599} 600 601/** 602 * @brief Handler for channel protocol messages. 603 * 604 * This is invoked in the vmbus worker thread context. 605 */ 606void 607hv_vmbus_on_channel_message(void *context) 608{ 609 hv_vmbus_message* msg; 610 hv_vmbus_channel_msg_header* hdr; 611 int size; 612 613 msg = (hv_vmbus_message*) context; 614 hdr = (hv_vmbus_channel_msg_header*) msg->u.payload; 615 size = msg->header.payload_size; 616 617 if (hdr->message_type >= HV_CHANNEL_MESSAGE_COUNT) { 618 free(msg, M_DEVBUF); 619 return; 620 } 621 622 if (g_channel_message_table[hdr->message_type].messageHandler) { 623 g_channel_message_table[hdr->message_type].messageHandler(hdr); 624 } 625 626 /* Free the msg that was allocated in VmbusOnMsgDPC() */ 627 free(msg, M_DEVBUF); 628} 629 630/** 631 * @brief Send a request to get all our pending offers. 632 */ 633int 634hv_vmbus_request_channel_offers(void) 635{ 636 int ret; 637 hv_vmbus_channel_msg_header* msg; 638 hv_vmbus_channel_msg_info* msg_info; 639 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); 643 644 if (msg_info == NULL) { 645 if(bootverbose) 646 printf("Error VMBUS: malloc failed for Request Offers\n"); 647 return (ENOMEM); 648 } 649 650 msg = (hv_vmbus_channel_msg_header*) msg_info->msg; 651 msg->message_type = HV_CHANNEL_MESSAGE_REQUEST_OFFERS; 652 653 ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_msg_header)); 654 655 if (msg_info) 656 free(msg_info, M_DEVBUF); 657 658 return (ret); 659} 660 661/** 662 * @brief Release channels that are unattached/unconnected (i.e., no drivers associated) 663 */ 664void 665hv_vmbus_release_unattached_channels(void) 666{ 667 hv_vmbus_channel *channel; 668 669 mtx_lock_spin(&hv_vmbus_g_connection.channel_lock); 670 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); 675 676 hv_vmbus_child_device_unregister(channel->device); 677 hv_vmbus_free_vmbus_channel(channel); 678 } 679 mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock); 680} 681