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 <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
/*
 * Negotiated protocol version with the host.
 */
__u32 vmbus_proto_version;
EXPORT_SYMBOL_GPL(vmbus_proto_version);

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
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;
}

132
/*
133
 * vmbus_connect - Sends a connect request on the partition service connection
134
 */
135
int vmbus_connect(void)
136
{
137
	int ret = 0;
138
	struct vmbus_channel_msginfo *msginfo = NULL;
139
	__u32 version;
140

141
	/* Initialize the vmbus connection */
142 143 144
	vmbus_connection.conn_state = CONNECTING;
	vmbus_connection.work_queue = create_workqueue("hv_vmbus_con");
	if (!vmbus_connection.work_queue) {
145
		ret = -ENOMEM;
146
		goto cleanup;
147
	}
148

149
	INIT_LIST_HEAD(&vmbus_connection.chn_msg_list);
150
	spin_lock_init(&vmbus_connection.channelmsg_lock);
151

152
	INIT_LIST_HEAD(&vmbus_connection.chn_list);
153
	spin_lock_init(&vmbus_connection.channel_lock);
154

155 156 157 158
	/*
	 * Setup the vmbus event connection for channel interrupt
	 * abstraction stuff
	 */
159 160
	vmbus_connection.int_page =
	(void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, 0);
161
	if (vmbus_connection.int_page == NULL) {
162
		ret = -ENOMEM;
163
		goto cleanup;
164 165
	}

166 167 168
	vmbus_connection.recv_int_page = vmbus_connection.int_page;
	vmbus_connection.send_int_page =
		(void *)((unsigned long)vmbus_connection.int_page +
169
			(PAGE_SIZE >> 1));
170

171 172 173
	/*
	 * Setup the monitor notification facility. The 1st page for
	 * parent->child and the 2nd page for child->parent
174
	 */
175 176
	vmbus_connection.monitor_pages =
	(void *)__get_free_pages((GFP_KERNEL|__GFP_ZERO), 1);
177
	if (vmbus_connection.monitor_pages == NULL) {
178
		ret = -ENOMEM;
179
		goto cleanup;
180 181
	}

182
	msginfo = kzalloc(sizeof(*msginfo) +
183 184
			  sizeof(struct vmbus_channel_initiate_contact),
			  GFP_KERNEL);
185
	if (msginfo == NULL) {
186
		ret = -ENOMEM;
187
		goto cleanup;
188 189
	}

190
	/*
191 192 193 194
	 * 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.
195
	 */
196

197
	version = VERSION_WS2008;
198

199 200 201 202
	do {
		ret = vmbus_negotiate_version(msginfo, version);
		if (ret == 0)
			break;
203

204 205
		version = vmbus_get_next_version(version);
	} while (version != VERSION_INVAL);
206

207
	if (version == VERSION_INVAL)
208
		goto cleanup;
209

210 211
	vmbus_proto_version = version;
	pr_info("Negotiated host information %d\n", version);
212
	kfree(msginfo);
213 214
	return 0;

215
cleanup:
216
	vmbus_connection.conn_state = DISCONNECTED;
217

218 219
	if (vmbus_connection.work_queue)
		destroy_workqueue(vmbus_connection.work_queue);
220

221
	if (vmbus_connection.int_page) {
222
		free_pages((unsigned long)vmbus_connection.int_page, 0);
223
		vmbus_connection.int_page = NULL;
224 225
	}

226
	if (vmbus_connection.monitor_pages) {
227
		free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
228
		vmbus_connection.monitor_pages = NULL;
229 230
	}

231
	kfree(msginfo);
232 233 234 235 236

	return ret;
}


237
/*
238 239
 * relid2channel - Get the channel object given its
 * child relative id (ie channel id)
240
 */
241
struct vmbus_channel *relid2channel(u32 relid)
242
{
243
	struct vmbus_channel *channel;
244
	struct vmbus_channel *found_channel  = NULL;
245
	unsigned long flags;
246

247
	spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
248
	list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
249 250
		if (channel->offermsg.child_relid == relid) {
			found_channel = channel;
251 252 253
			break;
		}
	}
254
	spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
255

256
	return found_channel;
257 258
}

259
/*
260
 * process_chn_event - Process a channel event notification
261
 */
262
static void process_chn_event(u32 relid)
263
{
264
	struct vmbus_channel *channel;
265
	unsigned long flags;
266 267 268
	void *arg;
	bool read_state;
	u32 bytes_to_read;
269

270 271 272 273
	/*
	 * Find the channel based on this relid and invokes the
	 * channel callback to process the event
	 */
274
	channel = relid2channel(relid);
275

276 277 278 279 280 281 282 283 284 285 286 287 288 289
	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.
	 */

290
	spin_lock_irqsave(&channel->inbound_lock, flags);
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
	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 {
312
		pr_err("no channel callback for relid - %u\n", relid);
313
	}
314

315
	spin_unlock_irqrestore(&channel->inbound_lock, flags);
316 317
}

318
/*
319
 * vmbus_on_event - Handler for events
320
 */
321
void vmbus_on_event(unsigned long data)
322
{
323 324
	u32 dword;
	u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
325
	int bit;
326
	u32 relid;
327
	u32 *recv_int_page = vmbus_connection.recv_int_page;
328

329
	/* Check events */
330 331 332 333 334 335
	if (!recv_int_page)
		return;
	for (dword = 0; dword < maxdword; dword++) {
		if (!recv_int_page[dword])
			continue;
		for (bit = 0; bit < 32; bit++) {
336 337
			if (sync_test_and_clear_bit(bit,
				(unsigned long *)&recv_int_page[dword])) {
338 339
				relid = (dword << 5) + bit;

340
				if (relid == 0)
341 342 343 344
					/*
					 * Special case - vmbus
					 * channel protocol msg
					 */
345
					continue;
346

347
				process_chn_event(relid);
348
			}
349
		}
350 351 352
	}
}

353
/*
354
 * vmbus_post_msg - Send a msg on the vmbus's message connection
355
 */
356
int vmbus_post_msg(void *buffer, size_t buflen)
357
{
358
	union hv_connection_id conn_id;
359 360
	int ret = 0;
	int retries = 0;
361

362 363
	conn_id.asu32 = 0;
	conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
364 365 366 367 368 369 370 371 372 373 374 375 376 377

	/*
	 * 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;
378 379
}

380
/*
381
 * vmbus_set_event - Send an event notification to the parent
382
 */
383
int vmbus_set_event(struct vmbus_channel *channel)
384
{
385
	u32 child_relid = channel->offermsg.child_relid;
386

387 388 389 390 391 392 393 394
	if (!channel->is_dedicated_interrupt) {
		/* Each u32 represents 32 channels */
		sync_set_bit(child_relid & 31,
			(unsigned long *)vmbus_connection.send_int_page +
			(child_relid >> 5));
	}

	return hv_signal_event(channel->sig_event);
395
}