hv_connection.c revision 299890
1250199Sgrehan/*-
2298446Ssephe * Copyright (c) 2009-2012,2016 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>
30296028Ssephe#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, };
48250199Sgrehan
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}
69282212Swhu
70250199Sgrehan/**
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(
90295309Ssephe		hv_vmbus_g_connection.monitor_page_1);
91282212Swhu
92295308Ssephe	msg->monitor_page_2 = hv_get_phys_addr(
93295309Ssephe		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	 */
99297635Ssephe	mtx_lock(&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
106297635Ssephe	mtx_unlock(&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) {
113297635Ssephe		mtx_lock(&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);
118297635Ssephe		mtx_unlock(&hv_vmbus_g_connection.channel_msg_lock);
119282212Swhu		return (ret);
120282212Swhu	}
121282212Swhu
122282212Swhu	/**
123282212Swhu	 * Wait for the connection response
124282212Swhu	 */
125296028Ssephe	ret = sema_timedwait(&msg_info->wait_sema, 5 * hz); /* KYS 5 seconds */
126282212Swhu
127297635Ssephe	mtx_lock(&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);
132297635Ssephe	mtx_unlock(&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/**
147250199Sgrehan * Send a connect request on the partition service connection
148250199Sgrehan */
149250199Sgrehanint
150250199Sgrehanhv_vmbus_connect(void) {
151250199Sgrehan	int					ret = 0;
152282212Swhu	uint32_t				version;
153250199Sgrehan	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",
169297635Ssephe		NULL, MTX_DEF);
170250199Sgrehan
171250199Sgrehan	TAILQ_INIT(&hv_vmbus_g_connection.channel_anchor);
172250199Sgrehan	mtx_init(&hv_vmbus_g_connection.channel_lock, "vmbus channel",
173282212Swhu		NULL, MTX_DEF);
174250199Sgrehan
175250199Sgrehan	/**
176250199Sgrehan	 * Setup the vmbus event connection for channel interrupt abstraction
177250199Sgrehan	 * stuff
178250199Sgrehan	 */
179295309Ssephe	hv_vmbus_g_connection.interrupt_page = malloc(
180250199Sgrehan					PAGE_SIZE, M_DEVBUF,
181295309Ssephe					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	 */
194295309Ssephe	hv_vmbus_g_connection.monitor_page_1 = malloc(
195295309Ssephe		PAGE_SIZE,
196250199Sgrehan		M_DEVBUF,
197295309Ssephe		M_WAITOK | M_ZERO);
198295309Ssephe	hv_vmbus_g_connection.monitor_page_2 = malloc(
199250199Sgrehan		PAGE_SIZE,
200295309Ssephe		M_DEVBUF,
201295309Ssephe		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),
206295308Ssephe			M_DEVBUF, M_WAITOK | M_ZERO);
207250199Sgrehan
208294553Ssephe	hv_vmbus_g_connection.channels = malloc(sizeof(hv_vmbus_channel*) *
209294553Ssephe		HV_CHANNEL_MAX_COUNT,
210294553Ssephe		M_DEVBUF, M_WAITOK | M_ZERO);
211282212Swhu	/*
212282212Swhu	 * Find the highest vmbus version number we can support.
213250199Sgrehan	 */
214282212Swhu	version = HV_VMBUS_VERSION_CURRENT;
215250199Sgrehan
216282212Swhu	do {
217282212Swhu		ret = hv_vmbus_negotiate_version(msg_info, version);
218282212Swhu		if (ret == EWOULDBLOCK) {
219282212Swhu			/*
220282212Swhu			 * We timed out.
221282212Swhu			 */
222282212Swhu			goto cleanup;
223282212Swhu		}
224250199Sgrehan
225282212Swhu		if (hv_vmbus_g_connection.connect_state == HV_CONNECTED)
226282212Swhu			break;
227250199Sgrehan
228282212Swhu		version = hv_vmbus_get_next_version(version);
229282212Swhu	} while (version != HV_VMBUS_VERSION_INVALID);
230250199Sgrehan
231282212Swhu	hv_vmbus_protocal_version = version;
232282212Swhu	if (bootverbose)
233293870Ssephe		printf("VMBUS: Protocol Version: %d.%d\n",
234282212Swhu		    version >> 16, version & 0xFFFF);
235250199Sgrehan
236250199Sgrehan	sema_destroy(&msg_info->wait_sema);
237250199Sgrehan	free(msg_info, M_DEVBUF);
238250199Sgrehan
239250199Sgrehan	return (0);
240250199Sgrehan
241250199Sgrehan	/*
242250199Sgrehan	 * Cleanup after failure!
243250199Sgrehan	 */
244250199Sgrehan	cleanup:
245250199Sgrehan
246250199Sgrehan	hv_vmbus_g_connection.connect_state = HV_DISCONNECTED;
247250199Sgrehan
248250199Sgrehan	mtx_destroy(&hv_vmbus_g_connection.channel_lock);
249250199Sgrehan	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
250250199Sgrehan
251250199Sgrehan	if (hv_vmbus_g_connection.interrupt_page != NULL) {
252295964Ssephe		free(hv_vmbus_g_connection.interrupt_page, M_DEVBUF);
253250199Sgrehan		hv_vmbus_g_connection.interrupt_page = NULL;
254250199Sgrehan	}
255250199Sgrehan
256295309Ssephe	free(hv_vmbus_g_connection.monitor_page_1, M_DEVBUF);
257295309Ssephe	free(hv_vmbus_g_connection.monitor_page_2, M_DEVBUF);
258250199Sgrehan
259250199Sgrehan	if (msg_info) {
260250199Sgrehan		sema_destroy(&msg_info->wait_sema);
261250199Sgrehan		free(msg_info, M_DEVBUF);
262250199Sgrehan	}
263250199Sgrehan
264294553Ssephe	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;
274295308Ssephe	hv_vmbus_channel_unload  msg;
275250199Sgrehan
276295308Ssephe	msg.message_type = HV_CHANNEL_MESSAGE_UNLOAD;
277250199Sgrehan
278295308Ssephe	ret = hv_vmbus_post_message(&msg, sizeof(hv_vmbus_channel_unload));
279250199Sgrehan
280295964Ssephe	free(hv_vmbus_g_connection.interrupt_page, M_DEVBUF);
281250199Sgrehan
282250199Sgrehan	mtx_destroy(&hv_vmbus_g_connection.channel_msg_lock);
283250199Sgrehan
284294553Ssephe	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
294294886Ssephehv_vmbus_on_events(int cpu)
295250199Sgrehan{
296282212Swhu	int bit;
297250199Sgrehan	int dword;
298282212Swhu	void *page_addr;
299282212Swhu	uint32_t* recv_interrupt_page = NULL;
300250199Sgrehan	int rel_id;
301282212Swhu	int maxdword;
302282212Swhu	hv_vmbus_synic_event_flags *event;
303250199Sgrehan
304282212Swhu	KASSERT(cpu <= mp_maxid, ("VMBUS: hv_vmbus_on_events: "
305282212Swhu	    "cpu out of range!"));
306250199Sgrehan
307297908Ssephe	page_addr = hv_vmbus_g_context.syn_ic_event_page[cpu];
308297908Ssephe	event = (hv_vmbus_synic_event_flags *)
309297908Ssephe	    page_addr + HV_VMBUS_MESSAGE_SINT;
310282212Swhu	if ((hv_vmbus_protocal_version == HV_VMBUS_VERSION_WS2008) ||
311282212Swhu	    (hv_vmbus_protocal_version == HV_VMBUS_VERSION_WIN7)) {
312282212Swhu		maxdword = HV_MAX_NUM_CHANNELS_SUPPORTED >> 5;
313282212Swhu		/*
314282212Swhu		 * receive size is 1/2 page and divide that by 4 bytes
315282212Swhu		 */
316299889Ssephe		if (synch_test_and_clear_bit(0, &event->flags32[0])) {
317297908Ssephe			recv_interrupt_page =
318297908Ssephe			    hv_vmbus_g_connection.recv_interrupt_page;
319299889Ssephe		} else {
320299889Ssephe			return;
321299889Ssephe		}
322282212Swhu	} else {
323282212Swhu		/*
324282212Swhu		 * On Host with Win8 or above, the event page can be
325282212Swhu		 * checked directly to get the id of the channel
326282212Swhu		 * that has the pending interrupt.
327282212Swhu		 */
328282212Swhu		maxdword = HV_EVENT_FLAGS_DWORD_COUNT;
329282212Swhu		recv_interrupt_page = event->flags32;
330282212Swhu	}
331282212Swhu
332250199Sgrehan	/*
333250199Sgrehan	 * Check events
334250199Sgrehan	 */
335299889Ssephe	for (dword = 0; dword < maxdword; dword++) {
336299889Ssephe	    if (recv_interrupt_page[dword]) {
337299889Ssephe	        for (bit = 0; bit < HV_CHANNEL_DWORD_LEN; bit++) {
338299889Ssephe	    	if (synch_test_and_clear_bit(bit,
339299889Ssephe	    	    (uint32_t *) &recv_interrupt_page[dword])) {
340299890Ssephe			struct hv_vmbus_channel *channel;
341299890Ssephe
342299890Ssephe			rel_id = (dword << 5) + bit;
343299890Ssephe	    		channel = hv_vmbus_g_connection.channels[rel_id];
344299890Ssephe
345299889Ssephe	    		/* if channel is closed or closing */
346299889Ssephe	    		if (channel == NULL || channel->rxq == NULL)
347299889Ssephe	    			continue;
348250199Sgrehan
349299889Ssephe	    		if (channel->batched_reading)
350299889Ssephe	    			hv_ring_buffer_read_begin(&channel->inbound);
351299889Ssephe	    		taskqueue_enqueue(channel->rxq, &channel->channel_task);
352299889Ssephe	    	}
353299889Ssephe	        }
354250199Sgrehan	    }
355250199Sgrehan	}
356250199Sgrehan}
357250199Sgrehan
358250199Sgrehan/**
359250199Sgrehan * Send a msg on the vmbus's message connection
360250199Sgrehan */
361297219Ssepheint hv_vmbus_post_message(void *buffer, size_t bufferLen)
362297219Ssephe{
363250199Sgrehan	hv_vmbus_connection_id connId;
364297219Ssephe	sbintime_t time = SBT_1MS;
365297219Ssephe	int retries;
366297219Ssephe	int ret;
367250199Sgrehan
368297219Ssephe	connId.as_uint32_t = 0;
369297219Ssephe	connId.u.id = HV_VMBUS_MESSAGE_CONNECTION_ID;
370250199Sgrehan
371297219Ssephe	/*
372297219Ssephe	 * We retry to cope with transient failures caused by host side's
373297219Ssephe	 * insufficient resources. 20 times should suffice in practice.
374250199Sgrehan	 */
375297219Ssephe	for (retries = 0; retries < 20; retries++) {
376297219Ssephe		ret = hv_vmbus_post_msg_via_msg_ipc(connId, 1, buffer,
377297219Ssephe						    bufferLen);
378297219Ssephe		if (ret == HV_STATUS_SUCCESS)
379297219Ssephe			return (0);
380297219Ssephe
381297219Ssephe		pause_sbt("pstmsg", time, 0, C_HARDCLOCK);
382297219Ssephe		if (time < SBT_1S * 2)
383297219Ssephe			time *= 2;
384250199Sgrehan	}
385250199Sgrehan
386297219Ssephe	KASSERT(ret == HV_STATUS_SUCCESS,
387297219Ssephe		("Error VMBUS: Message Post Failed, ret=%d\n", ret));
388250199Sgrehan
389297219Ssephe	return (EAGAIN);
390250199Sgrehan}
391250199Sgrehan
392250199Sgrehan/**
393250199Sgrehan * Send an event notification to the parent
394250199Sgrehan */
395250199Sgrehanint
396282212Swhuhv_vmbus_set_event(hv_vmbus_channel *channel) {
397250199Sgrehan	int ret = 0;
398282212Swhu	uint32_t child_rel_id = channel->offer_msg.child_rel_id;
399250199Sgrehan
400250199Sgrehan	/* Each uint32_t represents 32 channels */
401250199Sgrehan
402250199Sgrehan	synch_set_bit(child_rel_id & 31,
403250199Sgrehan		(((uint32_t *)hv_vmbus_g_connection.send_interrupt_page
404250199Sgrehan			+ (child_rel_id >> 5))));
405282212Swhu	ret = hv_vmbus_signal_event(channel->signal_event_param);
406250199Sgrehan
407250199Sgrehan	return (ret);
408250199Sgrehan}
409