hv_channel_mgmt.c revision 250200
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