hv_connection.c revision 297219
1250199Sgrehan/*-
2250199Sgrehan * Copyright (c) 2009-2012 Microsoft Corp.
3250199Sgrehan * Copyright (c) 2012 NetApp Inc.
4250199Sgrehan * Copyright (c) 2012 Citrix Inc.
5250199Sgrehan * All rights reserved.
6250199Sgrehan *
7250199Sgrehan * Redistribution and use in source and binary forms, with or without
8250199Sgrehan * modification, are permitted provided that the following conditions
9250199Sgrehan * are met:
10250199Sgrehan * 1. Redistributions of source code must retain the above copyright
11250199Sgrehan *    notice unmodified, this list of conditions, and the following
12250199Sgrehan *    disclaimer.
13250199Sgrehan * 2. Redistributions in binary form must reproduce the above copyright
14250199Sgrehan *    notice, this list of conditions and the following disclaimer in the
15250199Sgrehan *    documentation and/or other materials provided with the distribution.
16250199Sgrehan *
17250199Sgrehan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18250199Sgrehan * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19250199Sgrehan * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20250199Sgrehan * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21250199Sgrehan * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22250199Sgrehan * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23250199Sgrehan * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24250199Sgrehan * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25250199Sgrehan * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26250199Sgrehan * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27250199Sgrehan */
28250199Sgrehan
29250199Sgrehan#include <sys/param.h>
30250199Sgrehan#include <sys/kernel.h>
31250199Sgrehan#include <sys/malloc.h>
32250199Sgrehan#include <sys/systm.h>
33250199Sgrehan#include <sys/lock.h>
34250199Sgrehan#include <sys/mutex.h>
35250199Sgrehan#include <machine/bus.h>
36250199Sgrehan#include <vm/vm.h>
37250199Sgrehan#include <vm/vm_param.h>
38250199Sgrehan#include <vm/pmap.h>
39250199Sgrehan
40250199Sgrehan#include "hv_vmbus_priv.h"
41250199Sgrehan
42250199Sgrehan/*
43250199Sgrehan * Globals
44250199Sgrehan */
45250199Sgrehanhv_vmbus_connection hv_vmbus_g_connection =
46250199Sgrehan	{ .connect_state = HV_DISCONNECTED,
47250199Sgrehan	  .next_gpadl_handle = 0xE1E10, };
48282212Swhu
49282212Swhuuint32_t hv_vmbus_protocal_version = HV_VMBUS_VERSION_WS2008;
50282212Swhu
51282212Swhustatic uint32_t
52282212Swhuhv_vmbus_get_next_version(uint32_t current_ver)
53282212Swhu{
54282212Swhu	switch (current_ver) {
55282212Swhu	case (HV_VMBUS_VERSION_WIN7):
56282212Swhu		return(HV_VMBUS_VERSION_WS2008);
57282212Swhu
58282212Swhu	case (HV_VMBUS_VERSION_WIN8):
59282212Swhu		return(HV_VMBUS_VERSION_WIN7);
60282212Swhu
61282212Swhu	case (HV_VMBUS_VERSION_WIN8_1):
62282212Swhu		return(HV_VMBUS_VERSION_WIN8);
63282212Swhu
64282212Swhu	case (HV_VMBUS_VERSION_WS2008):
65282212Swhu	default:
66282212Swhu		return(HV_VMBUS_VERSION_INVALID);
67282212Swhu	}
68282212Swhu}
69250199Sgrehan
70282212Swhu/**
71282212Swhu * Negotiate the highest supported hypervisor version.
72282212Swhu */
73282212Swhustatic int
74282212Swhuhv_vmbus_negotiate_version(hv_vmbus_channel_msg_info *msg_info,
75282212Swhu	uint32_t version)
76282212Swhu{
77282212Swhu	int					ret = 0;
78282212Swhu	hv_vmbus_channel_initiate_contact	*msg;
79282212Swhu
80282212Swhu	sema_init(&msg_info->wait_sema, 0, "Msg Info Sema");
81282212Swhu	msg = (hv_vmbus_channel_initiate_contact*) msg_info->msg;
82282212Swhu
83282212Swhu	msg->header.message_type = HV_CHANNEL_MESSAGE_INITIATED_CONTACT;
84282212Swhu	msg->vmbus_version_requested = version;
85282212Swhu
86282212Swhu	msg->interrupt_page = hv_get_phys_addr(
87282212Swhu		hv_vmbus_g_connection.interrupt_page);
88282212Swhu
89282212Swhu	msg->monitor_page_1 = hv_get_phys_addr(
90282212Swhu		hv_vmbus_g_connection.monitor_page_1);
91282212Swhu
92282212Swhu	msg->monitor_page_2 = hv_get_phys_addr(
93282212Swhu		hv_vmbus_g_connection.monitor_page_2);
94282212Swhu
95282212Swhu	/**
96282212Swhu	 * Add to list before we send the request since we may receive the
97282212Swhu	 * response before returning from this routine
98282212Swhu	 */
99282212Swhu	mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
100282212Swhu
101282212Swhu	TAILQ_INSERT_TAIL(
102282212Swhu		&hv_vmbus_g_connection.channel_msg_anchor,
103282212Swhu		msg_info,
104282212Swhu		msg_list_entry);
105282212Swhu
106282212Swhu	mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
107282212Swhu
108282212Swhu	ret = hv_vmbus_post_message(
109282212Swhu		msg,
110282212Swhu		sizeof(hv_vmbus_channel_initiate_contact));
111282212Swhu
112282212Swhu	if (ret != 0) {
113282212Swhu		mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
114282212Swhu		TAILQ_REMOVE(
115282212Swhu			&hv_vmbus_g_connection.channel_msg_anchor,
116282212Swhu			msg_info,
117282212Swhu			msg_list_entry);
118282212Swhu		mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
119282212Swhu		return (ret);
120282212Swhu	}
121282212Swhu
122282212Swhu	/**
123282212Swhu	 * Wait for the connection response
124282212Swhu	 */
125282212Swhu	ret = sema_timedwait(&msg_info->wait_sema, 5 * hz); /* KYS 5 seconds */
126282212Swhu
127282212Swhu	mtx_lock_spin(&hv_vmbus_g_connection.channel_msg_lock);
128282212Swhu	TAILQ_REMOVE(
129282212Swhu		&hv_vmbus_g_connection.channel_msg_anchor,
130282212Swhu		msg_info,
131282212Swhu		msg_list_entry);
132282212Swhu	mtx_unlock_spin(&hv_vmbus_g_connection.channel_msg_lock);
133282212Swhu
134282212Swhu	/**
135282212Swhu	 * Check if successful
136282212Swhu	 */
137282212Swhu	if (msg_info->response.version_response.version_supported) {
138282212Swhu		hv_vmbus_g_connection.connect_state = HV_CONNECTED;
139282212Swhu	} else {
140282212Swhu		ret = ECONNREFUSED;
141282212Swhu	}
142282212Swhu
143282212Swhu	return (ret);
144282212Swhu}
145282212Swhu
146282212Swhu/**
147282212Swhu * Send a connect request on the partition service connection
148250199Sgrehan */
149250199Sgrehanint
150250199Sgrehanhv_vmbus_connect(void) {
151250199Sgrehan	int					ret = 0;
152250199Sgrehan	uint32_t				version;
153282212Swhu	hv_vmbus_channel_msg_info*		msg_info = NULL;
154250199Sgrehan
155250199Sgrehan	/**
156250199Sgrehan	 * Make sure we are not connecting or connected
157250199Sgrehan	 */
158250199Sgrehan	if (hv_vmbus_g_connection.connect_state != HV_DISCONNECTED) {
159250199Sgrehan		return (-1);
160250199Sgrehan	}
161250199Sgrehan
162250199Sgrehan	/**
163250199Sgrehan	 * Initialize the vmbus connection
164250199Sgrehan	 */
165250199Sgrehan	hv_vmbus_g_connection.connect_state = HV_CONNECTING;
166250199Sgrehan
167250199Sgrehan	TAILQ_INIT(&hv_vmbus_g_connection.channel_msg_anchor);
168250199Sgrehan	mtx_init(&hv_vmbus_g_connection.channel_msg_lock, "vmbus channel msg",
169250199Sgrehan		NULL, MTX_SPIN);
170250199Sgrehan
171250199Sgrehan	TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
172250199Sgrehan	mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
173250199Sgrehan		NULL, MTX_DEF);
174250199Sgrehan
175250199Sgrehan	/**
176282212Swhu	 * Setup the vmbus event connection for channel interrupt abstraction
177250199Sgrehan	 * stuff
178250199Sgrehan	 */
179250199Sgrehan	hv_vmbus_g_connection.interrupt_page = malloc(
180250199Sgrehan					PAGE_SIZE, M_DEVBUF,
181250199Sgrehan					M_WAITOK | M_ZERO);
182250199Sgrehan
183250199Sgrehan	hv_vmbus_g_connection.recv_interrupt_page =
184250199Sgrehan		hv_vmbus_g_connection.interrupt_page;
185250199Sgrehan
186250199Sgrehan	hv_vmbus_g_connection.send_interrupt_page =
187250199Sgrehan		((uint8_t *) hv_vmbus_g_connection.interrupt_page +
188250199Sgrehan		    (PAGE_SIZE >> 1));
189250199Sgrehan
190250199Sgrehan	/**
191250199Sgrehan	 * Set up the monitor notification facility. The 1st page for
192250199Sgrehan	 * parent->child and the 2nd page for child->parent
193250199Sgrehan	 */
194250199Sgrehan	hv_vmbus_g_connection.monitor_page_1 = malloc(
195250199Sgrehan		PAGE_SIZE,
196250199Sgrehan		M_DEVBUF,
197250199Sgrehan		M_WAITOK | M_ZERO);
198250199Sgrehan	hv_vmbus_g_connection.monitor_page_2 = malloc(
199250199Sgrehan		PAGE_SIZE,
200250199Sgrehan		M_DEVBUF,
201250199Sgrehan		M_WAITOK | M_ZERO);
202250199Sgrehan
203250199Sgrehan	msg_info = (hv_vmbus_channel_msg_info*)
204250199Sgrehan		malloc(sizeof(hv_vmbus_channel_msg_info) +
205250199Sgrehan			sizeof(hv_vmbus_channel_initiate_contact),
206250199Sgrehan			M_DEVBUF, M_WAITOK | M_ZERO);
207250199Sgrehan
208250199Sgrehan	hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) *
209250199Sgrehan		HV_CHANNEL_MAX_COUNT,
210250199Sgrehan		M_DEVBUF, M_WAITOK | M_ZERO);
211250199Sgrehan	/*
212250199Sgrehan	 * Find the highest vmbus version number we can support.
213250199Sgrehan	 */
214250199Sgrehan	version = HV_VMBUS_VERSION_CURRENT;
215250199Sgrehan
216250199Sgrehan	do {
217250199Sgrehan		ret = hv_vmbus_negotiate_version(msg_info, version);
218250199Sgrehan		if (ret == EWOULDBLOCK) {
219250199Sgrehan			/*
220250199Sgrehan			 * We timed out.
221250199Sgrehan			 */
222250199Sgrehan			goto cleanup;
223250199Sgrehan		}
224250199Sgrehan
225250199Sgrehan		if (hv_vmbus_g_connection.connect_state == HV_CONNECTED)
226250199Sgrehan			break;
227250199Sgrehan
228250199Sgrehan		version = hv_vmbus_get_next_version(version);
229250199Sgrehan	} while (version != HV_VMBUS_VERSION_INVALID);
230250199Sgrehan
231250199Sgrehan	hv_vmbus_protocal_version = version;
232294553Ssephe	if (bootverbose)
233294553Ssephe		printf("VMBUS: Protocol Version: %d.%d\n",
234294553Ssephe		    version >> 16, version & 0xFFFF);
235282212Swhu
236282212Swhu	sema_destroy(&msg_info->wait_sema);
237250199Sgrehan	free(msg_info, M_DEVBUF);
238282212Swhu
239250199Sgrehan	return (0);
240282212Swhu
241282212Swhu	/*
242282212Swhu	 * Cleanup after failure!
243282212Swhu	 */
244282212Swhu	cleanup:
245282212Swhu
246282212Swhu	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
247282212Swhu
248250199Sgrehan	mtx_destroy(&hv_vmbus_g_connection.channel_lock);
249282212Swhu	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
250282212Swhu
251250199Sgrehan	if (hv_vmbus_g_connection.interrupt_page != NULL) {
252282212Swhu		free(hv_vmbus_g_connection.interrupt_page, M_DEVBUF);
253282212Swhu		hv_vmbus_g_connection.interrupt_page = NULL;
254250199Sgrehan	}
255282212Swhu
256282212Swhu	free(hv_vmbus_g_connection.monitor_page_1, M_DEVBUF);
257293870Ssephe	free(hv_vmbus_g_connection.monitor_page_2, M_DEVBUF);
258282212Swhu
259250199Sgrehan	if (msg_info) {
260250199Sgrehan		sema_destroy(&msg_info->wait_sema);
261250199Sgrehan		free(msg_info, M_DEVBUF);
262250199Sgrehan	}
263250199Sgrehan
264250199Sgrehan	free(hv_vmbus_g_connection.channels, M_DEVBUF);
265250199Sgrehan	return (ret);
266250199Sgrehan}
267250199Sgrehan
268250199Sgrehan/**
269250199Sgrehan * Send a disconnect request on the partition service connection
270250199Sgrehan */
271250199Sgrehanint
272250199Sgrehanhv_vmbus_disconnect(void) {
273250199Sgrehan	int			 ret = 0;
274250199Sgrehan	hv_vmbus_channel_unload  msg;
275250199Sgrehan
276250199Sgrehan	msg.message_type = HV_CHANNEL_MESSAGE_UNLOAD;
277250199Sgrehan
278250199Sgrehan	ret = hv_vmbus_post_message(&msg, sizeof(hv_vmbus_channel_unload));
279250199Sgrehan
280250199Sgrehan	free(hv_vmbus_g_connection.interrupt_page, M_DEVBUF);
281250199Sgrehan
282250199Sgrehan	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
283250199Sgrehan
284250199Sgrehan	free(hv_vmbus_g_connection.channels, M_DEVBUF);
285250199Sgrehan	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
286250199Sgrehan
287250199Sgrehan	return (ret);
288250199Sgrehan}
289250199Sgrehan
290250199Sgrehan/**
291250199Sgrehan * Handler for events
292250199Sgrehan */
293250199Sgrehanvoid
294250199Sgrehanhv_vmbus_on_events(int cpu)
295250199Sgrehan{
296250199Sgrehan	int bit;
297250199Sgrehan	int dword;
298294553Ssephe	void *page_addr;
299250199Sgrehan	uint32_t* recv_interrupt_page = NULL;
300250199Sgrehan	int rel_id;
301250199Sgrehan	int maxdword;
302250199Sgrehan	hv_vmbus_synic_event_flags *event;
303250199Sgrehan	/* int maxdword = PAGE_SIZE >> 3; */
304250199Sgrehan
305250199Sgrehan	KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: "
306250199Sgrehan	    "cpu out of range!"));
307250199Sgrehan
308250199Sgrehan	if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) ||
309250199Sgrehan	    (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) {
310250199Sgrehan		maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
311250199Sgrehan		/*
312250199Sgrehan		 * receive size is 1/2 page and divide that by 4 bytes
313250199Sgrehan		 */
314250199Sgrehan		recv_interrupt_page =
315250199Sgrehan		    hv_vmbus_g_connection.recv_interrupt_page;
316250199Sgrehan	} else {
317250199Sgrehan		/*
318250199Sgrehan		 * On Host with Win8 or above, the event page can be
319250199Sgrehan		 * checked directly to get the id of the channel
320250199Sgrehan		 * that has the pending interrupt.
321250199Sgrehan		 */
322250199Sgrehan		maxdword = HV_EVENT_FLAGS_DWORD_COUNT;
323250199Sgrehan		page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu];
324250199Sgrehan		event = (hv_vmbus_synic_event_flags *)
325250199Sgrehan		    page_addr + HV_VMBUS_MESSAGE_SINT;
326250199Sgrehan		recv_interrupt_page = event->flags32;
327250199Sgrehan	}
328250199Sgrehan
329294553Ssephe	/*
330250199Sgrehan	 * Check events
331250199Sgrehan	 */
332250199Sgrehan	if (recv_interrupt_page != NULL) {
333250199Sgrehan	    for (dword = 0; dword < maxdword; dword++) {
334250199Sgrehan		if (recv_interrupt_page[dword]) {
335250199Sgrehan		    for (bit = 0; bit < HV_CHANNEL_DWORD_LEN; bit++) {
336250199Sgrehan			if (synch_test_and_clear_bit(bit,
337250199Sgrehan			    (uint32_t *) &recv_interrupt_page[dword])) {
338250199Sgrehan			    rel_id = (dword << 5) + bit;
339250199Sgrehan			    if (rel_id == 0) {
340250199Sgrehan				/*
341250199Sgrehan				 * Special case -
342250199Sgrehan				 * vmbus channel protocol msg.
343282212Swhu				 */
344282212Swhu				continue;
345250199Sgrehan			    } else {
346282212Swhu				hv_vmbus_channel * channel = hv_vmbus_g_connection.channels[rel_id];
347250199Sgrehan				/* if channel is closed or closing */
348250199Sgrehan				if (channel == NULL || channel->rxq == NULL)
349250199Sgrehan					continue;
350250199Sgrehan
351250199Sgrehan				if (channel->batched_reading)
352250199Sgrehan					hv_ring_buffer_read_begin(&channel->inbound);
353294553Ssephe				taskqueue_enqueue(channel->rxq, &channel->channel_task);
354250199Sgrehan			    }
355250199Sgrehan			}
356250199Sgrehan		    }
357250199Sgrehan		}
358250199Sgrehan	    }
359250199Sgrehan	}
360250199Sgrehan
361250199Sgrehan	return;
362250199Sgrehan}
363250199Sgrehan
364250199Sgrehan/**
365250199Sgrehan * Send a msg on the vmbus's message connection
366250199Sgrehan */
367282212Swhuint hv_vmbus_post_message(void *buffer, size_t bufferLen)
368282212Swhu{
369282212Swhu	hv_vmbus_connection_id connId;
370282212Swhu	sbintime_t time = SBT_1MS;
371282212Swhu	int retries;
372282212Swhu	int ret;
373250199Sgrehan
374282212Swhu	connId.as_uint32_t = 0;
375282212Swhu	connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
376282212Swhu
377282212Swhu	/*
378282212Swhu	 * We retry to cope with transient failures caused by host side's
379282212Swhu	 * insufficient resources. 20 times should suffice in practice.
380282212Swhu	 */
381282212Swhu	for (retries = 0; retries < 20; retries++) {
382282212Swhu		ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer,
383282212Swhu						    bufferLen);
384282212Swhu		if (ret == HV_STATUS_SUCCESS)
385282212Swhu			return (0);
386282212Swhu
387282212Swhu		pause_sbt("pstmsg", time, 0, C_HARDCLOCK);
388282212Swhu		if (time < SBT_1S * 2)
389282212Swhu			time *= 2;
390282212Swhu	}
391282212Swhu
392282212Swhu	KASSERT(ret == HV_STATUS_SUCCESS,
393282212Swhu		("Error VMBUS: Message Post Failed, ret=%d\n", ret));
394282212Swhu
395282212Swhu	return (EAGAIN);
396282212Swhu}
397282212Swhu
398250199Sgrehan/**
399282212Swhu * Send an event notification to the parent
400250199Sgrehan */
401250199Sgrehanint
402250199Sgrehanhv_vmbus_set_event(hv_vmbus_channel *channel) {
403250199Sgrehan	int ret = 0;
404250199Sgrehan	uint32_t child_rel_id = channel->offer_msg.child_rel_id;
405250199Sgrehan
406250199Sgrehan	/* Each uint32_t represents 32 channels */
407250199Sgrehan
408282212Swhu	synch_set_bit(child_rel_id & 31,
409282212Swhu		(((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
410250199Sgrehan			+ (child_rel_id >> 5))));
411282212Swhu	ret = hv_vmbus_signal_event(channel->signal_event_param);
412282212Swhu
413250199Sgrehan	return (ret);
414282212Swhu}
415282212Swhu