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