connection.c 9.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 *
 * Copyright (c) 2009, Microsoft Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307 USA.
 *
 * Authors:
 *   Haiyang Zhang <haiyangz@microsoft.com>
 *   Hank Janssen  <hjanssen@microsoft.com>
 *
 */
23 24
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

25
#include <linux/kernel.h>
26 27
#include <linux/sched.h>
#include <linux/wait.h>
28
#include <linux/delay.h>
29
#include <linux/mm.h>
30
#include <linux/slab.h>
31
#include <linux/vmalloc.h>
32
#include <linux/hyperv.h>
33
#include <asm/hyperv.h>
34
#include "hyperv_vmbus.h"
35 36


37 38 39
struct vmbus_connection vmbus_connection = {
	.conn_state		= DISCONNECTED,
	.next_gpadl_handle	= ATOMIC_INIT(0xE1E10),
40 41
};

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
/*
 * VMBUS version is 32 bit entity broken up into
 * two 16 bit quantities: major_number. minor_number.
 *
 * 0 . 13 (Windows Server 2008)
 * 1 . 1  (Windows 7)
 * 2 . 4  (Windows 8)
 */

#define VERSION_WS2008	((0 << 16) | (13))
#define VERSION_WIN7	((1 << 16) | (1))
#define VERSION_WIN8	((2 << 16) | (4))

#define VERSION_INVAL -1

static __u32 vmbus_get_next_version(__u32 current_version)
{
	switch (current_version) {
	case (VERSION_WIN7):
		return VERSION_WS2008;

	case (VERSION_WIN8):
		return VERSION_WIN7;

	case (VERSION_WS2008):
	default:
		return VERSION_INVAL;
	}
}

static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo,
					__u32 version)
{
	int ret = 0;
	struct vmbus_channel_initiate_contact *msg;
	unsigned long flags;
	int t;

	init_completion(&msginfo->waitevent);

	msg = (struct vmbus_channel_initiate_contact *)msginfo->msg;

	msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT;
	msg->vmbus_version_requested = version;
	msg->interrupt_page = virt_to_phys(vmbus_connection.int_page);
	msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages);
	msg->monitor_page2 = virt_to_phys(
			(void *)((unsigned long)vmbus_connection.monitor_pages +
				 PAGE_SIZE));

	/*
	 * Add to list before we send the request since we may
	 * receive the response before returning from this routine
	 */
	spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
	list_add_tail(&msginfo->msglistentry,
		      &vmbus_connection.chn_msg_list);

	spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);

	ret = vmbus_post_msg(msg,
			       sizeof(struct vmbus_channel_initiate_contact));
	if (ret != 0) {
		spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
		list_del(&msginfo->msglistentry);
		spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
					flags);
		return ret;
	}

	/* Wait for the connection response */
	t =  wait_for_completion_timeout(&msginfo->waitevent, 5*HZ);
	if (t == 0) {
		spin_lock_irqsave(&vmbus_connection.channelmsg_lock,
				flags);
		list_del(&msginfo->msglistentry);
		spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
					flags);
		return -ETIMEDOUT;
	}

	spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
	list_del(&msginfo->msglistentry);
	spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);

	/* Check if successful */
	if (msginfo->response.version_response.version_supported) {
		vmbus_connection.conn_state = CONNECTED;
	} else {
		pr_err("Unable to connect, "
			"Version %d not supported by Hyper-V\n",
			version);
		return -ECONNREFUSED;
	}

	return ret;
}

140
/*
141
 * vmbus_connect - Sends a connect request on the partition service connection
142
 */
143
int vmbus_connect(void)
144
{
145
	int ret = 0;
146
	struct vmbus_channel_msginfo *msginfo = NULL;
147
	__u32 version;
148

149
	/* Initialize the vmbus connection */
150 151 152
	vmbus_connection.conn_state = CONNECTING;
	vmbus_connection.work_queue = create_workqueue("hv_vmbus_con");
	if (!vmbus_connection.work_queue) {
153
		ret = -ENOMEM;
154
		goto cleanup;
155
	}
156

157
	INIT_LIST_HEAD(&vmbus_connection.chn_msg_list);
158
	spin_lock_init(&vmbus_connection.channelmsg_lock);
159

160
	INIT_LIST_HEAD(&vmbus_connection.chn_list);
161
	spin_lock_init(&vmbus_connection.channel_lock);
162

163 164 165 166
	/*
	 * Setup the vmbus event connection for channel interrupt
	 * abstraction stuff
	 */
167 168
	vmbus_connection.int_page =
	(void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, 0);
169
	if (vmbus_connection.int_page == NULL) {
170
		ret = -ENOMEM;
171
		goto cleanup;
172 173
	}

174 175 176
	vmbus_connection.recv_int_page = vmbus_connection.int_page;
	vmbus_connection.send_int_page =
		(void *)((unsigned long)vmbus_connection.int_page +
177
			(PAGE_SIZE >> 1));
178

179 180 181
	/*
	 * Setup the monitor notification facility. The 1st page for
	 * parent->child and the 2nd page for child->parent
182
	 */
183 184
	vmbus_connection.monitor_pages =
	(void *)__get_free_pages((GFP_KERNEL|__GFP_ZERO), 1);
185
	if (vmbus_connection.monitor_pages == NULL) {
186
		ret = -ENOMEM;
187
		goto cleanup;
188 189
	}

190
	msginfo = kzalloc(sizeof(*msginfo) +
191 192
			  sizeof(struct vmbus_channel_initiate_contact),
			  GFP_KERNEL);
193
	if (msginfo == NULL) {
194
		ret = -ENOMEM;
195
		goto cleanup;
196 197
	}

198
	/*
199 200 201 202
	 * Negotiate a compatible VMBUS version number with the
	 * host. We start with the highest number we can support
	 * and work our way down until we negotiate a compatible
	 * version.
203
	 */
204

205
	version = VERSION_WS2008;
206

207 208 209 210
	do {
		ret = vmbus_negotiate_version(msginfo, version);
		if (ret == 0)
			break;
211

212 213
		version = vmbus_get_next_version(version);
	} while (version != VERSION_INVAL);
214

215
	if (version == VERSION_INVAL)
216
		goto cleanup;
217

218
	kfree(msginfo);
219 220
	return 0;

221
cleanup:
222
	vmbus_connection.conn_state = DISCONNECTED;
223

224 225
	if (vmbus_connection.work_queue)
		destroy_workqueue(vmbus_connection.work_queue);
226

227
	if (vmbus_connection.int_page) {
228
		free_pages((unsigned long)vmbus_connection.int_page, 0);
229
		vmbus_connection.int_page = NULL;
230 231
	}

232
	if (vmbus_connection.monitor_pages) {
233
		free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
234
		vmbus_connection.monitor_pages = NULL;
235 236
	}

237
	kfree(msginfo);
238 239 240 241 242

	return ret;
}


243
/*
244 245
 * relid2channel - Get the channel object given its
 * child relative id (ie channel id)
246
 */
247
struct vmbus_channel *relid2channel(u32 relid)
248
{
249
	struct vmbus_channel *channel;
250
	struct vmbus_channel *found_channel  = NULL;
251
	unsigned long flags;
252

253
	spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
254
	list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
255 256
		if (channel->offermsg.child_relid == relid) {
			found_channel = channel;
257 258 259
			break;
		}
	}
260
	spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
261

262
	return found_channel;
263 264
}

265
/*
266
 * process_chn_event - Process a channel event notification
267
 */
268
static void process_chn_event(u32 relid)
269
{
270
	struct vmbus_channel *channel;
271
	unsigned long flags;
272 273 274
	void *arg;
	bool read_state;
	u32 bytes_to_read;
275

276 277 278 279
	/*
	 * Find the channel based on this relid and invokes the
	 * channel callback to process the event
	 */
280
	channel = relid2channel(relid);
281

282 283 284 285 286 287 288 289 290 291 292 293 294 295
	if (!channel) {
		pr_err("channel not found for relid - %u\n", relid);
		return;
	}

	/*
	 * A channel once created is persistent even when there
	 * is no driver handling the device. An unloading driver
	 * sets the onchannel_callback to NULL under the
	 * protection of the channel inbound_lock. Thus, checking
	 * and invoking the driver specific callback takes care of
	 * orderly unloading of the driver.
	 */

296
	spin_lock_irqsave(&channel->inbound_lock, flags);
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
	if (channel->onchannel_callback != NULL) {
		arg = channel->channel_callback_context;
		read_state = channel->batched_reading;
		/*
		 * This callback reads the messages sent by the host.
		 * We can optimize host to guest signaling by ensuring:
		 * 1. While reading the channel, we disable interrupts from
		 *    host.
		 * 2. Ensure that we process all posted messages from the host
		 *    before returning from this callback.
		 * 3. Once we return, enable signaling from the host. Once this
		 *    state is set we check to see if additional packets are
		 *    available to read. In this case we repeat the process.
		 */

		do {
			hv_begin_read(&channel->inbound);
			channel->onchannel_callback(arg);
			bytes_to_read = hv_end_read(&channel->inbound);
		} while (read_state && (bytes_to_read != 0));
	} else {
318
		pr_err("no channel callback for relid - %u\n", relid);
319
	}
320

321
	spin_unlock_irqrestore(&channel->inbound_lock, flags);
322 323
}

324
/*
325
 * vmbus_on_event - Handler for events
326
 */
327
void vmbus_on_event(unsigned long data)
328
{
329 330
	u32 dword;
	u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
331
	int bit;
332
	u32 relid;
333
	u32 *recv_int_page = vmbus_connection.recv_int_page;
334

335
	/* Check events */
336 337 338 339 340 341
	if (!recv_int_page)
		return;
	for (dword = 0; dword < maxdword; dword++) {
		if (!recv_int_page[dword])
			continue;
		for (bit = 0; bit < 32; bit++) {
342 343
			if (sync_test_and_clear_bit(bit,
				(unsigned long *)&recv_int_page[dword])) {
344 345
				relid = (dword << 5) + bit;

346
				if (relid == 0)
347 348 349 350
					/*
					 * Special case - vmbus
					 * channel protocol msg
					 */
351
					continue;
352

353
				process_chn_event(relid);
354
			}
355
		}
356 357 358
	}
}

359
/*
360
 * vmbus_post_msg - Send a msg on the vmbus's message connection
361
 */
362
int vmbus_post_msg(void *buffer, size_t buflen)
363
{
364
	union hv_connection_id conn_id;
365 366
	int ret = 0;
	int retries = 0;
367

368 369
	conn_id.asu32 = 0;
	conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
370 371 372 373 374 375 376 377 378 379 380 381 382 383

	/*
	 * hv_post_message() can have transient failures because of
	 * insufficient resources. Retry the operation a couple of
	 * times before giving up.
	 */
	while (retries < 3) {
		ret =  hv_post_message(conn_id, 1, buffer, buflen);
		if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
			return ret;
		retries++;
		msleep(100);
	}
	return ret;
384 385
}

386
/*
387
 * vmbus_set_event - Send an event notification to the parent
388
 */
389
int vmbus_set_event(u32 child_relid)
390
{
391
	/* Each u32 represents 32 channels */
392
	sync_set_bit(child_relid & 31,
393
		(unsigned long *)vmbus_connection.send_int_page +
394
		(child_relid >> 5));
395

396
	return hv_signal_event();
397
}