hv_connection.c revision 251775
150476Speter/*-
21638Srgrimes * Copyright (c) 2009-2012 Microsoft Corp.
3172476Sedwin * Copyright (c) 2012 NetApp Inc.
4172476Sedwin * Copyright (c) 2012 Citrix Inc.
5172476Sedwin * All rights reserved.
6181426Sedwin *
7172476Sedwin * Redistribution and use in source and binary forms, with or without
8181426Sedwin * modification, are permitted provided that the following conditions
9172476Sedwin * are met:
10181426Sedwin * 1. Redistributions of source code must retain the above copyright
11181426Sedwin *    notice unmodified, this list of conditions, and the following
12181426Sedwin *    disclaimer.
13181426Sedwin * 2. Redistributions in binary form must reproduce the above copyright
14172476Sedwin *    notice, this list of conditions and the following disclaimer in the
15181426Sedwin *    documentation and/or other materials provided with the distribution.
16172476Sedwin *
17181426Sedwin * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18181426Sedwin * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19181426Sedwin * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20183865Sedwin * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21172476Sedwin * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22181426Sedwin * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23172476Sedwin * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24183865Sedwin * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25181426Sedwin * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26183865Sedwin * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27181426Sedwin */
28183865Sedwin
29172476Sedwin#include <sys/param.h>
30172476Sedwin#include <sys/malloc.h>
312747Swollman#include <sys/systm.h>
321638Srgrimes#include <sys/lock.h>
332747Swollman#include <sys/mutex.h>
3412319Sgpalmer#include <machine/bus.h>
352747Swollman#include <vm/vm.h>
3612319Sgpalmer#include <vm/vm_param.h>
372747Swollman#include <vm/pmap.h>
381638Srgrimes
392747Swollman#include "hv_vmbus_priv.h"
40149653Swollman
412747Swollman/*
421638Srgrimes * Globals
432747Swollman */
44149653Swollmanhv_vmbus_connection hv_vmbus_g_connection =
452747Swollman	{ .connect_state = HV_DISCONNECTED,
461638Srgrimes	  .next_gpadl_handle = 0xE1E10, };
4723569Sbde
481638Srgrimes/**
4923569Sbde * Send a connect request on the partition service connection
502747Swollman */
5183346Sruint
52136709Sruhv_vmbus_connect(void) {
53136709Sru	int					ret = 0;
54100872Sru	hv_vmbus_channel_msg_info*		msg_info = NULL;
5523569Sbde	hv_vmbus_channel_initiate_contact*	msg;
561638Srgrimes
57198351Sedwin	/**
58198351Sedwin	 * Make sure we are not connecting or connected
59198351Sedwin	 */
60198351Sedwin	if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) {
61198351Sedwin		return (-1);
62198351Sedwin	}
63198351Sedwin
64198351Sedwin	/**
65198351Sedwin	 * Initialize the vmbus connection
66198351Sedwin	 */
67198351Sedwin	hv_vmbus_g_connection.connect_state = HV_CONNECTING;
68198351Sedwin	hv_vmbus_g_connection.work_queue = hv_work_queue_create("vmbusQ");
69198351Sedwin	sema_init(&hv_vmbus_g_connection.control_sema, 1, "control_sema");
70198351Sedwin
71198351Sedwin	TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor);
72198351Sedwin	mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg",
73198351Sedwin		NULL, MTX_SPIN);
74198351Sedwin
75198351Sedwin	TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
76198351Sedwin	mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
771638Srgrimes		NULL, MTX_SPIN);
78
79	/**
80	 * Setup the vmbus event connection for channel interrupt abstraction
81	 * stuff
82	 */
83	hv_vmbus_g_connection.interrupt_page = contigmalloc(
84					PAGE_SIZE, M_DEVBUF,
85					M_NOWAIT | M_ZERO, 0UL,
86					BUS_SPACE_MAXADDR,
87					PAGE_SIZE, 0);
88	KASSERT(hv_vmbus_g_connection.interrupt_page != NULL,
89	    ("Error VMBUS: malloc failed to allocate Channel"
90		" Request Event message!"));
91	if (hv_vmbus_g_connection.interrupt_page == NULL) {
92	    ret = ENOMEM;
93	    goto cleanup;
94	}
95
96	hv_vmbus_g_connection.recv_interrupt_page =
97		hv_vmbus_g_connection.interrupt_page;
98
99	hv_vmbus_g_connection.send_interrupt_page =
100		((uint8_t *) hv_vmbus_g_connection.interrupt_page +
101		    (PAGE_SIZE >> 1));
102
103	/**
104	 * Set up the monitor notification facility. The 1st page for
105	 * parent->child and the 2nd page for child->parent
106	 */
107	hv_vmbus_g_connection.monitor_pages = contigmalloc(
108		2 * PAGE_SIZE,
109		M_DEVBUF,
110		M_NOWAIT | M_ZERO,
111		0UL,
112		BUS_SPACE_MAXADDR,
113		PAGE_SIZE,
114		0);
115	KASSERT(hv_vmbus_g_connection.monitor_pages != NULL,
116	    ("Error VMBUS: malloc failed to allocate Monitor Pages!"));
117	if (hv_vmbus_g_connection.monitor_pages == NULL) {
118	    ret = ENOMEM;
119	    goto cleanup;
120	}
121
122	msg_info = (hv_vmbus_channel_msg_info*)
123		malloc(sizeof(hv_vmbus_channel_msg_info) +
124			sizeof(hv_vmbus_channel_initiate_contact),
125			M_DEVBUF, M_NOWAIT | M_ZERO);
126	KASSERT(msg_info != NULL,
127	    ("Error VMBUS: malloc failed for Initiate Contact message!"));
128	if (msg_info == NULL) {
129	    ret = ENOMEM;
130	    goto cleanup;
131	}
132
133	sema_init(&msg_info->wait_sema, 0, "Msg Info Sema");
134	msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg;
135
136	msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT;
137	msg->vmbus_version_requested = HV_VMBUS_REVISION_NUMBER;
138
139	msg->interrupt_page = hv_get_phys_addr(
140		hv_vmbus_g_connection.interrupt_page);
141
142	msg->monitor_page_1 = hv_get_phys_addr(
143		hv_vmbus_g_connection.monitor_pages);
144
145	msg->monitor_page_2 =
146		hv_get_phys_addr(
147			((uint8_t *) hv_vmbus_g_connection.monitor_pages
148			+ PAGE_SIZE));
149
150	/**
151	 * Add to list before we send the request since we may receive the
152	 * response before returning from this routine
153	 */
154	mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
155
156	TAILQ_INSERT_TAIL(
157		&hv_vmbus_g_connection.channel_msg_anchor,
158		msg_info,
159		msg_list_entry);
160
161	mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
162
163	ret = hv_vmbus_post_message(
164		msg,
165		sizeof(hv_vmbus_channel_initiate_contact));
166
167	if (ret != 0) {
168		mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
169		TAILQ_REMOVE(
170			&hv_vmbus_g_connection.channel_msg_anchor,
171			msg_info,
172			msg_list_entry);
173		mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
174		goto cleanup;
175	}
176
177	/**
178	 * Wait for the connection response
179	 */
180	ret = sema_timedwait(&msg_info->wait_sema, 500); /* KYS 5 seconds */
181
182	mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
183	TAILQ_REMOVE(
184		&hv_vmbus_g_connection.channel_msg_anchor,
185		msg_info,
186		msg_list_entry);
187	mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
188
189	/**
190	 * Check if successful
191	 */
192	if (msg_info->response.version_response.version_supported) {
193		hv_vmbus_g_connection.connect_state = HV_CONNECTED;
194	} else {
195		ret = ECONNREFUSED;
196		goto cleanup;
197	}
198
199	sema_destroy(&msg_info->wait_sema);
200	free(msg_info, M_DEVBUF);
201
202	return (0);
203
204	/*
205	 * Cleanup after failure!
206	 */
207	cleanup:
208
209	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
210
211	hv_work_queue_close(hv_vmbus_g_connection.work_queue);
212	sema_destroy(&hv_vmbus_g_connection.control_sema);
213	mtx_destroy(&hv_vmbus_g_connection.channel_lock);
214	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
215
216	if (hv_vmbus_g_connection.interrupt_page != NULL) {
217		contigfree(
218			hv_vmbus_g_connection.interrupt_page,
219			PAGE_SIZE,
220			M_DEVBUF);
221		hv_vmbus_g_connection.interrupt_page = NULL;
222	}
223
224	if (hv_vmbus_g_connection.monitor_pages != NULL) {
225		contigfree(
226			hv_vmbus_g_connection.monitor_pages,
227			2 * PAGE_SIZE,
228			M_DEVBUF);
229		hv_vmbus_g_connection.monitor_pages = NULL;
230	}
231
232	if (msg_info) {
233		sema_destroy(&msg_info->wait_sema);
234		free(msg_info, M_DEVBUF);
235	}
236
237	return (ret);
238}
239
240/**
241 * Send a disconnect request on the partition service connection
242 */
243int
244hv_vmbus_disconnect(void) {
245	int			 ret = 0;
246	hv_vmbus_channel_unload* msg;
247
248	msg = malloc(sizeof(hv_vmbus_channel_unload),
249	    M_DEVBUF, M_NOWAIT | M_ZERO);
250	KASSERT(msg != NULL,
251	    ("Error VMBUS: malloc failed to allocate Channel Unload Msg!"));
252	if (msg == NULL)
253	    return (ENOMEM);
254
255	msg->message_type = HV_CHANNEL_MESSAGE_UNLOAD;
256
257	ret = hv_vmbus_post_message(msg, sizeof(hv_vmbus_channel_unload));
258
259
260	contigfree(hv_vmbus_g_connection.interrupt_page, PAGE_SIZE, M_DEVBUF);
261
262	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
263
264	hv_work_queue_close(hv_vmbus_g_connection.work_queue);
265	sema_destroy(&hv_vmbus_g_connection.control_sema);
266
267	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
268
269	free(msg, M_DEVBUF);
270
271	return (ret);
272}
273
274/**
275 * Get the channel object given its child relative id (ie channel id)
276 */
277hv_vmbus_channel*
278hv_vmbus_get_channel_from_rel_id(uint32_t rel_id) {
279
280	hv_vmbus_channel* channel;
281	hv_vmbus_channel* foundChannel = NULL;
282
283	/*
284	 * TODO:
285	 * Consider optimization where relids are stored in a fixed size array
286	 *  and channels are accessed without the need to take this lock or search
287	 *  the list.
288	 */
289	mtx_lock_spin(&hv_vmbus_g_connection.channel_lock);
290	TAILQ_FOREACH(channel,
291		&hv_vmbus_g_connection.channel_anchor, list_entry) {
292
293	    if (channel->offer_msg.child_rel_id == rel_id) {
294		foundChannel = channel;
295		break;
296	    }
297	}
298	mtx_unlock_spin(&hv_vmbus_g_connection.channel_lock);
299
300	return (foundChannel);
301}
302
303/**
304 * Process a channel event notification
305 */
306static void
307VmbusProcessChannelEvent(uint32_t relid)
308{
309	hv_vmbus_channel* channel;
310
311	/**
312	 * Find the channel based on this relid and invokes
313	 * the channel callback to process the event
314	 */
315
316	channel = hv_vmbus_get_channel_from_rel_id(relid);
317
318	if (channel == NULL) {
319		return;
320	}
321	/**
322	 * To deal with the race condition where we might
323	 * receive a packet while the relevant driver is
324	 * being unloaded, dispatch the callback while
325	 * holding the channel lock. The unloading driver
326	 * will acquire the same channel lock to set the
327	 * callback to NULL. This closes the window.
328	 */
329
330	mtx_lock(&channel->inbound_lock);
331	if (channel->on_channel_callback != NULL) {
332		channel->on_channel_callback(channel->channel_callback_context);
333	}
334	mtx_unlock(&channel->inbound_lock);
335}
336
337/**
338 * Handler for events
339 */
340void
341hv_vmbus_on_events(void *arg)
342{
343	int dword;
344	int bit;
345	int rel_id;
346	int maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
347	/* int maxdword = PAGE_SIZE >> 3; */
348
349	/*
350	 * receive size is 1/2 page and divide that by 4 bytes
351	 */
352
353	uint32_t* recv_interrupt_page =
354	    hv_vmbus_g_connection.recv_interrupt_page;
355
356	/*
357	 * Check events
358	 */
359	if (recv_interrupt_page != NULL) {
360	    for (dword = 0; dword < maxdword; dword++) {
361		if (recv_interrupt_page[dword]) {
362		    for (bit = 0; bit < 32; bit++) {
363			if (synch_test_and_clear_bit(bit,
364			    (uint32_t *) &recv_interrupt_page[dword])) {
365			    rel_id = (dword << 5) + bit;
366			    if (rel_id == 0) {
367				/*
368				 * Special case -
369				 * vmbus channel protocol msg.
370				 */
371				continue;
372			    } else {
373				VmbusProcessChannelEvent(rel_id);
374
375			    }
376			}
377		    }
378		}
379	    }
380	}
381
382	return;
383}
384
385/**
386 * Send a msg on the vmbus's message connection
387 */
388int hv_vmbus_post_message(void *buffer, size_t bufferLen) {
389	int ret = 0;
390	hv_vmbus_connection_id connId;
391	unsigned retries = 0;
392
393	/* NetScaler delays from previous code were consolidated here */
394	static int delayAmount[] = {100, 100, 100, 500, 500, 5000, 5000, 5000};
395
396	/* for(each entry in delayAmount) try to post message,
397	 *  delay a little bit before retrying
398	 */
399	for (retries = 0;
400	    retries < sizeof(delayAmount)/sizeof(delayAmount[0]); retries++) {
401	    connId.as_uint32_t = 0;
402	    connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
403	    ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer, bufferLen);
404	    if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
405		break;
406	    /* TODO: KYS We should use a blocking wait call */
407	    DELAY(delayAmount[retries]);
408	}
409
410	KASSERT(ret == 0, ("Error VMBUS: Message Post Failed\n"));
411
412	return (ret);
413}
414
415/**
416 * Send an event notification to the parent
417 */
418int
419hv_vmbus_set_event(uint32_t child_rel_id) {
420	int ret = 0;
421
422	/* Each uint32_t represents 32 channels */
423
424	synch_set_bit(child_rel_id & 31,
425		(((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
426			+ (child_rel_id >> 5))));
427	ret = hv_vmbus_signal_event();
428
429	return (ret);
430}
431
432