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