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