connection.c 10.2 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 <linux/export.h>
34
#include <asm/hyperv.h>
35
#include "hyperv_vmbus.h"
36 37


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

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
/*
 * 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

58 59 60 61 62 63
/*
 * Negotiated protocol version with the host.
 */
__u32 vmbus_proto_version;
EXPORT_SYMBOL_GPL(vmbus_proto_version);

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 140 141 142 143 144 145 146
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;
}

147
/*
148
 * vmbus_connect - Sends a connect request on the partition service connection
149
 */
150
int vmbus_connect(void)
151
{
152
	int ret = 0;
153
	struct vmbus_channel_msginfo *msginfo = NULL;
154
	__u32 version;
155

156
	/* Initialize the vmbus connection */
157 158 159
	vmbus_connection.conn_state = CONNECTING;
	vmbus_connection.work_queue = create_workqueue("hv_vmbus_con");
	if (!vmbus_connection.work_queue) {
160
		ret = -ENOMEM;
161
		goto cleanup;
162
	}
163

164
	INIT_LIST_HEAD(&vmbus_connection.chn_msg_list);
165
	spin_lock_init(&vmbus_connection.channelmsg_lock);
166

167
	INIT_LIST_HEAD(&vmbus_connection.chn_list);
168
	spin_lock_init(&vmbus_connection.channel_lock);
169

170 171 172 173
	/*
	 * Setup the vmbus event connection for channel interrupt
	 * abstraction stuff
	 */
174 175
	vmbus_connection.int_page =
	(void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, 0);
176
	if (vmbus_connection.int_page == NULL) {
177
		ret = -ENOMEM;
178
		goto cleanup;
179 180
	}

181 182 183
	vmbus_connection.recv_int_page = vmbus_connection.int_page;
	vmbus_connection.send_int_page =
		(void *)((unsigned long)vmbus_connection.int_page +
184
			(PAGE_SIZE >> 1));
185

186 187 188
	/*
	 * Setup the monitor notification facility. The 1st page for
	 * parent->child and the 2nd page for child->parent
189
	 */
190 191
	vmbus_connection.monitor_pages =
	(void *)__get_free_pages((GFP_KERNEL|__GFP_ZERO), 1);
192
	if (vmbus_connection.monitor_pages == NULL) {
193
		ret = -ENOMEM;
194
		goto cleanup;
195 196
	}

197
	msginfo = kzalloc(sizeof(*msginfo) +
198 199
			  sizeof(struct vmbus_channel_initiate_contact),
			  GFP_KERNEL);
200
	if (msginfo == NULL) {
201
		ret = -ENOMEM;
202
		goto cleanup;
203 204
	}

205
	/*
206 207 208 209
	 * 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.
210
	 */
211

212
	version = VERSION_WS2008;
213

214 215 216 217
	do {
		ret = vmbus_negotiate_version(msginfo, version);
		if (ret == 0)
			break;
218

219 220
		version = vmbus_get_next_version(version);
	} while (version != VERSION_INVAL);
221

222
	if (version == VERSION_INVAL)
223
		goto cleanup;
224

225 226
	vmbus_proto_version = version;
	pr_info("Negotiated host information %d\n", version);
227
	kfree(msginfo);
228 229
	return 0;

230
cleanup:
231
	vmbus_connection.conn_state = DISCONNECTED;
232

233 234
	if (vmbus_connection.work_queue)
		destroy_workqueue(vmbus_connection.work_queue);
235

236
	if (vmbus_connection.int_page) {
237
		free_pages((unsigned long)vmbus_connection.int_page, 0);
238
		vmbus_connection.int_page = NULL;
239 240
	}

241
	if (vmbus_connection.monitor_pages) {
242
		free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
243
		vmbus_connection.monitor_pages = NULL;
244 245
	}

246
	kfree(msginfo);
247 248 249 250 251

	return ret;
}


252
/*
253 254
 * relid2channel - Get the channel object given its
 * child relative id (ie channel id)
255
 */
256
struct vmbus_channel *relid2channel(u32 relid)
257
{
258
	struct vmbus_channel *channel;
259
	struct vmbus_channel *found_channel  = NULL;
260
	unsigned long flags;
261

262
	spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
263
	list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
264 265
		if (channel->offermsg.child_relid == relid) {
			found_channel = channel;
266 267 268
			break;
		}
	}
269
	spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
270

271
	return found_channel;
272 273
}

274
/*
275
 * process_chn_event - Process a channel event notification
276
 */
277
static void process_chn_event(u32 relid)
278
{
279
	struct vmbus_channel *channel;
280
	unsigned long flags;
281 282 283
	void *arg;
	bool read_state;
	u32 bytes_to_read;
284

285 286 287 288
	/*
	 * Find the channel based on this relid and invokes the
	 * channel callback to process the event
	 */
289
	channel = relid2channel(relid);
290

291 292 293 294 295 296 297 298 299 300 301 302 303 304
	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.
	 */

305
	spin_lock_irqsave(&channel->inbound_lock, flags);
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
	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 {
327
		pr_err("no channel callback for relid - %u\n", relid);
328
	}
329

330
	spin_unlock_irqrestore(&channel->inbound_lock, flags);
331 332
}

333
/*
334
 * vmbus_on_event - Handler for events
335
 */
336
void vmbus_on_event(unsigned long data)
337
{
338 339
	u32 dword;
	u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
340
	int bit;
341
	u32 relid;
342
	u32 *recv_int_page = vmbus_connection.recv_int_page;
343

344
	/* Check events */
345 346 347 348 349 350
	if (!recv_int_page)
		return;
	for (dword = 0; dword < maxdword; dword++) {
		if (!recv_int_page[dword])
			continue;
		for (bit = 0; bit < 32; bit++) {
351 352
			if (sync_test_and_clear_bit(bit,
				(unsigned long *)&recv_int_page[dword])) {
353 354
				relid = (dword << 5) + bit;

355
				if (relid == 0)
356 357 358 359
					/*
					 * Special case - vmbus
					 * channel protocol msg
					 */
360
					continue;
361

362
				process_chn_event(relid);
363
			}
364
		}
365 366 367
	}
}

368
/*
369
 * vmbus_post_msg - Send a msg on the vmbus's message connection
370
 */
371
int vmbus_post_msg(void *buffer, size_t buflen)
372
{
373
	union hv_connection_id conn_id;
374 375
	int ret = 0;
	int retries = 0;
376

377 378
	conn_id.asu32 = 0;
	conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
379 380 381 382 383 384 385 386 387 388 389 390 391 392

	/*
	 * 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;
393 394
}

395
/*
396
 * vmbus_set_event - Send an event notification to the parent
397
 */
398
int vmbus_set_event(u32 child_relid)
399
{
400
	/* Each u32 represents 32 channels */
401
	sync_set_bit(child_relid & 31,
402
		(unsigned long *)vmbus_connection.send_int_page +
403
		(child_relid >> 5));
404

405
	return hv_signal_event(hv_context.signal_event_param);
406
}