hv_connection.c revision 294886
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/malloc.h>
31#include <sys/systm.h>
32#include <sys/lock.h>
33#include <sys/mutex.h>
34#include <machine/bus.h>
35#include <vm/vm.h>
36#include <vm/vm_param.h>
37#include <vm/pmap.h>
38
39#include "hv_vmbus_priv.h"
40
41/*
42 * Globals
43 */
44hv_vmbus_connection hv_vmbus_g_connection =
45	{ .connect_state = HV_DISCONNECTED,
46	  .next_gpadl_handle = 0xE1E10, };
47
48uint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008;
49
50static uint32_t
51hv_vmbus_get_next_version(uint32_t current_ver)
52{
53	switch (current_ver) {
54	case (HV_VMBUS_VERSION_WIN7):
55		return(HV_VMBUS_VERSION_WS2008);
56
57	case (HV_VMBUS_VERSION_WIN8):
58		return(HV_VMBUS_VERSION_WIN7);
59
60	case (HV_VMBUS_VERSION_WIN8_1):
61		return(HV_VMBUS_VERSION_WIN8);
62
63	case (HV_VMBUS_VERSION_WS2008):
64	default:
65		return(HV_VMBUS_VERSION_INVALID);
66	}
67}
68
69/**
70 * Negotiate the highest supported hypervisor version.
71 */
72static int
73hv_vmbus_negotiate_version(hv_vmbus_channel_msg_info *msg_info,
74	uint32_t version)
75{
76	int					ret = 0;
77	hv_vmbus_channel_initiate_contact	*msg;
78
79	sema_init(&msg_info->wait_sema, 0, "Msg Info Sema");
80	msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg;
81
82	msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT;
83	msg->vmbus_version_requested = version;
84
85	msg->interrupt_page = hv_get_phys_addr(
86		hv_vmbus_g_connection.interrupt_page);
87
88	msg->monitor_page_1 = hv_get_phys_addr(
89		hv_vmbus_g_connection.monitor_pages);
90
91	msg->monitor_page_2 =
92		hv_get_phys_addr(
93			((uint8_t *) hv_vmbus_g_connection.monitor_pages
94			+ PAGE_SIZE));
95
96	/**
97	 * Add to list before we send the request since we may receive the
98	 * response before returning from this routine
99	 */
100	mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
101
102	TAILQ_INSERT_TAIL(
103		&hv_vmbus_g_connection.channel_msg_anchor,
104		msg_info,
105		msg_list_entry);
106
107	mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
108
109	ret = hv_vmbus_post_message(
110		msg,
111		sizeof(hv_vmbus_channel_initiate_contact));
112
113	if (ret != 0) {
114		mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
115		TAILQ_REMOVE(
116			&hv_vmbus_g_connection.channel_msg_anchor,
117			msg_info,
118			msg_list_entry);
119		mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
120		return (ret);
121	}
122
123	/**
124	 * Wait for the connection response
125	 */
126	ret = sema_timedwait(&msg_info->wait_sema, 500); /* KYS 5 seconds */
127
128	mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
129	TAILQ_REMOVE(
130		&hv_vmbus_g_connection.channel_msg_anchor,
131		msg_info,
132		msg_list_entry);
133	mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
134
135	/**
136	 * Check if successful
137	 */
138	if (msg_info->response.version_response.version_supported) {
139		hv_vmbus_g_connection.connect_state = HV_CONNECTED;
140	} else {
141		ret = ECONNREFUSED;
142	}
143
144	return (ret);
145}
146
147/**
148 * Send a connect request on the partition service connection
149 */
150int
151hv_vmbus_connect(void) {
152	int					ret = 0;
153	uint32_t				version;
154	hv_vmbus_channel_msg_info*		msg_info = NULL;
155
156	/**
157	 * Make sure we are not connecting or connected
158	 */
159	if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) {
160		return (-1);
161	}
162
163	/**
164	 * Initialize the vmbus connection
165	 */
166	hv_vmbus_g_connection.connect_state = HV_CONNECTING;
167	hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ");
168	sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema");
169
170	TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor);
171	mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg",
172		NULL, MTX_SPIN);
173
174	TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
175	mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
176		NULL, MTX_DEF);
177
178	/**
179	 * Setup the vmbus event connection for channel interrupt abstraction
180	 * stuff
181	 */
182	hv_vmbus_g_connection.interrupt_page = contigmalloc(
183					PAGE_SIZE, M_DEVBUF,
184					M_NOWAIT | M_ZERO, 0UL,
185					BUS_SPACE_MAXADDR,
186					PAGE_SIZE, 0);
187	KASSERT(hv_vmbus_g_connection.interrupt_page != NULL,
188	    ("Error VMBUS: malloc failed to allocate Channel"
189		" Request Event message!"));
190	if (hv_vmbus_g_connection.interrupt_page == NULL) {
191	    ret = ENOMEM;
192	    goto cleanup;
193	}
194
195	hv_vmbus_g_connection.recv_interrupt_page =
196		hv_vmbus_g_connection.interrupt_page;
197
198	hv_vmbus_g_connection.send_interrupt_page =
199		((uint8_t *) hv_vmbus_g_connection.interrupt_page +
200		    (PAGE_SIZE >> 1));
201
202	/**
203	 * Set up the monitor notification facility. The 1st page for
204	 * parent->child and the 2nd page for child->parent
205	 */
206	hv_vmbus_g_connection.monitor_pages = contigmalloc(
207		2 * PAGE_SIZE,
208		M_DEVBUF,
209		M_NOWAIT | M_ZERO,
210		0UL,
211		BUS_SPACE_MAXADDR,
212		PAGE_SIZE,
213		0);
214	KASSERT(hv_vmbus_g_connection.monitor_pages != NULL,
215	    ("Error VMBUS: malloc failed to allocate Monitor Pages!"));
216	if (hv_vmbus_g_connection.monitor_pages == NULL) {
217	    ret = ENOMEM;
218	    goto cleanup;
219	}
220
221	msg_info = (hv_vmbus_channel_msg_info*)
222		malloc(sizeof(hv_vmbus_channel_msg_info) +
223			sizeof(hv_vmbus_channel_initiate_contact),
224			M_DEVBUF, M_NOWAIT | M_ZERO);
225	KASSERT(msg_info != NULL,
226	    ("Error VMBUS: malloc failed for Initiate Contact message!"));
227	if (msg_info == NULL) {
228	    ret = ENOMEM;
229	    goto cleanup;
230	}
231
232	hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) *
233		HV_CHANNEL_MAX_COUNT,
234		M_DEVBUF, M_WAITOK | M_ZERO);
235	/*
236	 * Find the highest vmbus version number we can support.
237	 */
238	version = HV_VMBUS_VERSION_CURRENT;
239
240	do {
241		ret = hv_vmbus_negotiate_version(msg_info, version);
242		if (ret == EWOULDBLOCK) {
243			/*
244			 * We timed out.
245			 */
246			goto cleanup;
247		}
248
249		if (hv_vmbus_g_connection.connect_state == HV_CONNECTED)
250			break;
251
252		version = hv_vmbus_get_next_version(version);
253	} while (version != HV_VMBUS_VERSION_INVALID);
254
255	hv_vmbus_protocal_version = version;
256	if (bootverbose)
257		printf("VMBUS: Protocol Version: %d.%d\n",
258		    version >> 16, version & 0xFFFF);
259
260	sema_destroy(&msg_info->wait_sema);
261	free(msg_info, M_DEVBUF);
262
263	return (0);
264
265	/*
266	 * Cleanup after failure!
267	 */
268	cleanup:
269
270	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
271
272	hv_work_queue_close(hv_vmbus_g_connection.work_queue);
273	sema_destroy(&hv_vmbus_g_connection.control_sema);
274	mtx_destroy(&hv_vmbus_g_connection.channel_lock);
275	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
276
277	if (hv_vmbus_g_connection.interrupt_page != NULL) {
278		contigfree(
279			hv_vmbus_g_connection.interrupt_page,
280			PAGE_SIZE,
281			M_DEVBUF);
282		hv_vmbus_g_connection.interrupt_page = NULL;
283	}
284
285	if (hv_vmbus_g_connection.monitor_pages != NULL) {
286		contigfree(
287			hv_vmbus_g_connection.monitor_pages,
288			2 * PAGE_SIZE,
289			M_DEVBUF);
290		hv_vmbus_g_connection.monitor_pages = NULL;
291	}
292
293	if (msg_info) {
294		sema_destroy(&msg_info->wait_sema);
295		free(msg_info, M_DEVBUF);
296	}
297
298	free(hv_vmbus_g_connection.channels, M_DEVBUF);
299	return (ret);
300}
301
302/**
303 * Send a disconnect request on the partition service connection
304 */
305int
306hv_vmbus_disconnect(void) {
307	int			 ret = 0;
308	hv_vmbus_channel_unload* msg;
309
310	msg = malloc(sizeof(hv_vmbus_channel_unload),
311	    M_DEVBUF, M_NOWAIT | M_ZERO);
312	KASSERT(msg != NULL,
313	    ("Error VMBUS: malloc failed to allocate Channel Unload Msg!"));
314	if (msg == NULL)
315	    return (ENOMEM);
316
317	msg->message_type = HV_CHANNEL_MESSAGE_UNLOAD;
318
319	ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_unload));
320
321
322	contigfree(hv_vmbus_g_connection.interrupt_page, PAGE_SIZE, M_DEVBUF);
323
324	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
325
326	hv_work_queue_close(hv_vmbus_g_connection.work_queue);
327	sema_destroy(&hv_vmbus_g_connection.control_sema);
328
329	free(hv_vmbus_g_connection.channels, M_DEVBUF);
330	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
331
332	free(msg, M_DEVBUF);
333
334	return (ret);
335}
336
337/**
338 * Handler for events
339 */
340void
341hv_vmbus_on_events(int cpu)
342{
343	int bit;
344	int dword;
345	void *page_addr;
346	uint32_t* recv_interrupt_page = NULL;
347	int rel_id;
348	int maxdword;
349	hv_vmbus_synic_event_flags *event;
350	/* int maxdword = PAGE_SIZE >> 3; */
351
352	KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: "
353	    "cpu out of range!"));
354
355	if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) ||
356	    (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) {
357		maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
358		/*
359		 * receive size is 1/2 page and divide that by 4 bytes
360		 */
361		recv_interrupt_page =
362		    hv_vmbus_g_connection.recv_interrupt_page;
363	} else {
364		/*
365		 * On Host with Win8 or above, the event page can be
366		 * checked directly to get the id of the channel
367		 * that has the pending interrupt.
368		 */
369		maxdword = HV_EVENT_FLAGS_DWORD_COUNT;
370		page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu];
371		event = (hv_vmbus_synic_event_flags *)
372		    page_addr + HV_VMBUS_MESSAGE_SINT;
373		recv_interrupt_page = event->flags32;
374	}
375
376	/*
377	 * Check events
378	 */
379	if (recv_interrupt_page != NULL) {
380	    for (dword = 0; dword < maxdword; dword++) {
381		if (recv_interrupt_page[dword]) {
382		    for (bit = 0; bit < HV_CHANNEL_DWORD_LEN; bit++) {
383			if (synch_test_and_clear_bit(bit,
384			    (uint32_t *) &recv_interrupt_page[dword])) {
385			    rel_id = (dword << 5) + bit;
386			    if (rel_id == 0) {
387				/*
388				 * Special case -
389				 * vmbus channel protocol msg.
390				 */
391				continue;
392			    } else {
393				hv_vmbus_channel * channel = hv_vmbus_g_connection.channels[rel_id];
394				/* if channel is closed or closing */
395				if (channel == NULL || channel->rxq == NULL)
396					continue;
397
398				if (channel->batched_reading)
399					hv_ring_buffer_read_begin(&channel->inbound);
400				taskqueue_enqueue_fast(channel->rxq, &channel->channel_task);
401			    }
402			}
403		    }
404		}
405	    }
406	}
407
408	return;
409}
410
411/**
412 * Send a msg on the vmbus's message connection
413 */
414int hv_vmbus_post_message(void *buffer, size_t bufferLen) {
415	int ret = 0;
416	hv_vmbus_connection_id connId;
417	unsigned retries = 0;
418
419	/* NetScaler delays from previous code were consolidated here */
420	static int delayAmount[] = {100, 100, 100, 500, 500, 5000, 5000, 5000};
421
422	/* for(each entry in delayAmount) try to post message,
423	 *  delay a little bit before retrying
424	 */
425	for (retries = 0;
426	    retries < sizeof(delayAmount)/sizeof(delayAmount[0]); retries++) {
427	    connId.as_uint32_t = 0;
428	    connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
429	    ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer, bufferLen);
430	    if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
431		break;
432	    /* TODO: KYS We should use a blocking wait call */
433	    DELAY(delayAmount[retries]);
434	}
435
436	KASSERT(ret == 0, ("Error VMBUS: Message Post Failed\n"));
437
438	return (ret);
439}
440
441/**
442 * Send an event notification to the parent
443 */
444int
445hv_vmbus_set_event(hv_vmbus_channel *channel) {
446	int ret = 0;
447	uint32_t child_rel_id = channel->offer_msg.child_rel_id;
448
449	/* Each uint32_t represents 32 channels */
450
451	synch_set_bit(child_rel_id & 31,
452		(((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
453			+ (child_rel_id >> 5))));
454	ret = hv_vmbus_signal_event(channel->signal_event_param);
455
456	return (ret);
457}
458