Merge git://git.kernel.org/pub/scm/linux/kernel/git/cmetcalf/linux-tile
[deliverable/linux.git] / drivers / staging / hv / connection.c
CommitLineData
3e7ee490
HJ
1/*
2 *
3 * Copyright (c) 2009, Microsoft Corporation.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU General Public License,
7 * version 2, as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16 * Place - Suite 330, Boston, MA 02111-1307 USA.
17 *
18 * Authors:
19 * Haiyang Zhang <haiyangz@microsoft.com>
20 * Hank Janssen <hjanssen@microsoft.com>
21 *
22 */
0a46618d
HJ
23#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
24
a0086dc5 25#include <linux/kernel.h>
0c3b7b2f
S
26#include <linux/sched.h>
27#include <linux/wait.h>
a0086dc5 28#include <linux/mm.h>
5a0e3ad6 29#include <linux/slab.h>
a0086dc5 30#include <linux/vmalloc.h>
3f335ea2
S
31
32#include "hyperv.h"
0f2a6619 33#include "hyperv_vmbus.h"
3e7ee490 34
3e7ee490 35
da9fcb72
HZ
36struct vmbus_connection vmbus_connection = {
37 .conn_state = DISCONNECTED,
38 .next_gpadl_handle = ATOMIC_INIT(0xE1E10),
3e7ee490
HJ
39};
40
3e189519 41/*
c6977677 42 * vmbus_connect - Sends a connect request on the partition service connection
fd8b85ea 43 */
c6977677 44int vmbus_connect(void)
3e7ee490 45{
fd8b85ea 46 int ret = 0;
9568a193 47 int t;
15b2f647 48 struct vmbus_channel_msginfo *msginfo = NULL;
82250213 49 struct vmbus_channel_initiate_contact *msg;
dd0813b6 50 unsigned long flags;
3e7ee490 51
454f18a9 52 /* Make sure we are not connecting or connected */
da9fcb72 53 if (vmbus_connection.conn_state != DISCONNECTED)
3e7ee490
HJ
54 return -1;
55
454f18a9 56 /* Initialize the vmbus connection */
da9fcb72
HZ
57 vmbus_connection.conn_state = CONNECTING;
58 vmbus_connection.work_queue = create_workqueue("hv_vmbus_con");
59 if (!vmbus_connection.work_queue) {
de65a384 60 ret = -1;
b0043863 61 goto cleanup;
de65a384 62 }
3e7ee490 63
da9fcb72 64 INIT_LIST_HEAD(&vmbus_connection.chn_msg_list);
15b2f647 65 spin_lock_init(&vmbus_connection.channelmsg_lock);
3e7ee490 66
da9fcb72 67 INIT_LIST_HEAD(&vmbus_connection.chn_list);
15b2f647 68 spin_lock_init(&vmbus_connection.channel_lock);
3e7ee490 69
454f18a9
BP
70 /*
71 * Setup the vmbus event connection for channel interrupt
72 * abstraction stuff
73 */
df3493e0
S
74 vmbus_connection.int_page =
75 (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, 0);
da9fcb72 76 if (vmbus_connection.int_page == NULL) {
3e7ee490 77 ret = -1;
b0043863 78 goto cleanup;
3e7ee490
HJ
79 }
80
da9fcb72
HZ
81 vmbus_connection.recv_int_page = vmbus_connection.int_page;
82 vmbus_connection.send_int_page =
83 (void *)((unsigned long)vmbus_connection.int_page +
fd8b85ea 84 (PAGE_SIZE >> 1));
3e7ee490 85
fd8b85ea
GKH
86 /*
87 * Setup the monitor notification facility. The 1st page for
88 * parent->child and the 2nd page for child->parent
454f18a9 89 */
df3493e0
S
90 vmbus_connection.monitor_pages =
91 (void *)__get_free_pages((GFP_KERNEL|__GFP_ZERO), 1);
da9fcb72 92 if (vmbus_connection.monitor_pages == NULL) {
3e7ee490 93 ret = -1;
b0043863 94 goto cleanup;
3e7ee490
HJ
95 }
96
15b2f647 97 msginfo = kzalloc(sizeof(*msginfo) +
fd8b85ea
GKH
98 sizeof(struct vmbus_channel_initiate_contact),
99 GFP_KERNEL);
15b2f647 100 if (msginfo == NULL) {
8cad0af9 101 ret = -ENOMEM;
b0043863 102 goto cleanup;
3e7ee490
HJ
103 }
104
9568a193 105 init_completion(&msginfo->waitevent);
80d11b2a 106
15b2f647 107 msg = (struct vmbus_channel_initiate_contact *)msginfo->msg;
3e7ee490 108
c50f7fb2
HZ
109 msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT;
110 msg->vmbus_version_requested = VMBUS_REVISION_NUMBER;
da9fcb72
HZ
111 msg->interrupt_page = virt_to_phys(vmbus_connection.int_page);
112 msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages);
c50f7fb2 113 msg->monitor_page2 = virt_to_phys(
da9fcb72 114 (void *)((unsigned long)vmbus_connection.monitor_pages +
fd8b85ea 115 PAGE_SIZE));
3e7ee490 116
454f18a9
BP
117 /*
118 * Add to list before we send the request since we may
119 * receive the response before returning from this routine
120 */
15b2f647
HZ
121 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
122 list_add_tail(&msginfo->msglistentry,
da9fcb72 123 &vmbus_connection.chn_msg_list);
53af545b 124
15b2f647 125 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
3e7ee490 126
c6977677 127 ret = vmbus_post_msg(msg,
fd8b85ea
GKH
128 sizeof(struct vmbus_channel_initiate_contact));
129 if (ret != 0) {
0c3b7b2f 130 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
15b2f647 131 list_del(&msginfo->msglistentry);
0c3b7b2f
S
132 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
133 flags);
b0043863 134 goto cleanup;
3e7ee490
HJ
135 }
136
454f18a9 137 /* Wait for the connection response */
9568a193
S
138 t = wait_for_completion_timeout(&msginfo->waitevent, HZ);
139 if (t == 0) {
0c3b7b2f
S
140 spin_lock_irqsave(&vmbus_connection.channelmsg_lock,
141 flags);
142 list_del(&msginfo->msglistentry);
143 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
144 flags);
145 ret = -ETIMEDOUT;
b0043863 146 goto cleanup;
0c3b7b2f 147 }
3e7ee490 148
0c3b7b2f 149 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
15b2f647 150 list_del(&msginfo->msglistentry);
0c3b7b2f 151 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
3e7ee490 152
454f18a9 153 /* Check if successful */
15b2f647 154 if (msginfo->response.version_response.version_supported) {
da9fcb72 155 vmbus_connection.conn_state = CONNECTED;
fd8b85ea 156 } else {
0a46618d
HJ
157 pr_err("Unable to connect, "
158 "Version %d not supported by Hyper-V\n",
159 VMBUS_REVISION_NUMBER);
3e7ee490 160 ret = -1;
b0043863 161 goto cleanup;
3e7ee490
HJ
162 }
163
15b2f647 164 kfree(msginfo);
3e7ee490
HJ
165 return 0;
166
b0043863 167cleanup:
da9fcb72 168 vmbus_connection.conn_state = DISCONNECTED;
3e7ee490 169
da9fcb72
HZ
170 if (vmbus_connection.work_queue)
171 destroy_workqueue(vmbus_connection.work_queue);
3e7ee490 172
da9fcb72 173 if (vmbus_connection.int_page) {
df3493e0 174 free_pages((unsigned long)vmbus_connection.int_page, 0);
da9fcb72 175 vmbus_connection.int_page = NULL;
3e7ee490
HJ
176 }
177
da9fcb72 178 if (vmbus_connection.monitor_pages) {
df3493e0 179 free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
da9fcb72 180 vmbus_connection.monitor_pages = NULL;
3e7ee490
HJ
181 }
182
dd9b15dc 183 kfree(msginfo);
3e7ee490 184
3e7ee490
HJ
185 return ret;
186}
187
3e189519 188/*
c6977677
HZ
189 * vmbus_disconnect -
190 * Sends a disconnect request on the partition service connection
fd8b85ea 191 */
c6977677 192int vmbus_disconnect(void)
3e7ee490 193{
fd8b85ea 194 int ret = 0;
82250213 195 struct vmbus_channel_message_header *msg;
3e7ee490 196
454f18a9 197 /* Make sure we are connected */
da9fcb72 198 if (vmbus_connection.conn_state != CONNECTED)
3e7ee490
HJ
199 return -1;
200
82250213 201 msg = kzalloc(sizeof(struct vmbus_channel_message_header), GFP_KERNEL);
8cad0af9
BP
202 if (!msg)
203 return -ENOMEM;
3e7ee490 204
c50f7fb2 205 msg->msgtype = CHANNELMSG_UNLOAD;
3e7ee490 206
c6977677 207 ret = vmbus_post_msg(msg,
fd8b85ea 208 sizeof(struct vmbus_channel_message_header));
3e7ee490 209 if (ret != 0)
b0043863 210 goto cleanup;
3e7ee490 211
df3493e0
S
212 free_pages((unsigned long)vmbus_connection.int_page, 0);
213 free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
3e7ee490 214
454f18a9 215 /* TODO: iterate thru the msg list and free up */
da9fcb72 216 destroy_workqueue(vmbus_connection.work_queue);
3e7ee490 217
da9fcb72 218 vmbus_connection.conn_state = DISCONNECTED;
3e7ee490 219
0a46618d 220 pr_info("hv_vmbus disconnected\n");
3e7ee490 221
b0043863 222cleanup:
fd8b85ea 223 kfree(msg);
3e7ee490
HJ
224 return ret;
225}
226
3e189519 227/*
c6977677
HZ
228 * relid2channel - Get the channel object given its
229 * child relative id (ie channel id)
fd8b85ea 230 */
c6977677 231struct vmbus_channel *relid2channel(u32 relid)
3e7ee490 232{
aded7165 233 struct vmbus_channel *channel;
15b2f647 234 struct vmbus_channel *found_channel = NULL;
0f5e44ca 235 unsigned long flags;
3e7ee490 236
15b2f647 237 spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
da9fcb72 238 list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
15b2f647
HZ
239 if (channel->offermsg.child_relid == relid) {
240 found_channel = channel;
3e7ee490
HJ
241 break;
242 }
243 }
15b2f647 244 spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
3e7ee490 245
15b2f647 246 return found_channel;
3e7ee490
HJ
247}
248
3e189519 249/*
c6977677 250 * process_chn_event - Process a channel event notification
fd8b85ea 251 */
35436487 252static void process_chn_event(u32 relid)
3e7ee490 253{
aded7165 254 struct vmbus_channel *channel;
3e7ee490 255
972b9529 256 /* ASSERT(relId > 0); */
3e7ee490 257
454f18a9
BP
258 /*
259 * Find the channel based on this relid and invokes the
260 * channel callback to process the event
261 */
c6977677 262 channel = relid2channel(relid);
3e7ee490 263
fd8b85ea 264 if (channel) {
fff41b2e 265 vmbus_onchannel_event(channel);
fd8b85ea 266 } else {
35436487 267 pr_err("channel not found for relid - %u\n", relid);
3e7ee490
HJ
268 }
269}
270
3e189519 271/*
c6977677 272 * vmbus_on_event - Handler for events
fd8b85ea 273 */
6de3d6aa 274void vmbus_on_event(unsigned long data)
3e7ee490 275{
35436487
OH
276 u32 dword;
277 u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
3e7ee490 278 int bit;
35436487 279 u32 relid;
da9fcb72 280 u32 *recv_int_page = vmbus_connection.recv_int_page;
3e7ee490 281
454f18a9 282 /* Check events */
242b45aa
OH
283 if (!recv_int_page)
284 return;
285 for (dword = 0; dword < maxdword; dword++) {
286 if (!recv_int_page[dword])
287 continue;
288 for (bit = 0; bit < 32; bit++) {
289 if (sync_test_and_clear_bit(bit, (unsigned long *)&recv_int_page[dword])) {
290 relid = (dword << 5) + bit;
291
292 if (relid == 0) {
6d81d330
S
293 /*
294 * Special case - vmbus
295 * channel protocol msg
296 */
242b45aa 297 continue;
3e7ee490 298 }
35436487 299 process_chn_event(relid);
3e7ee490 300 }
242b45aa 301 }
3e7ee490 302 }
3e7ee490
HJ
303}
304
3e189519 305/*
c6977677 306 * vmbus_post_msg - Send a msg on the vmbus's message connection
fd8b85ea 307 */
c6977677 308int vmbus_post_msg(void *buffer, size_t buflen)
3e7ee490 309{
15b2f647 310 union hv_connection_id conn_id;
3e7ee490 311
15b2f647
HZ
312 conn_id.asu32 = 0;
313 conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
314 return hv_post_message(conn_id, 1, buffer, buflen);
3e7ee490
HJ
315}
316
3e189519 317/*
c6977677 318 * vmbus_set_event - Send an event notification to the parent
fd8b85ea 319 */
c6977677 320int vmbus_set_event(u32 child_relid)
3e7ee490 321{
454f18a9 322 /* Each u32 represents 32 channels */
22356585 323 sync_set_bit(child_relid & 31,
da9fcb72 324 (unsigned long *)vmbus_connection.send_int_page +
15b2f647 325 (child_relid >> 5));
7c369f40 326
d44890c8 327 return hv_signal_event();
3e7ee490 328}
This page took 0.34293 seconds and 5 git commands to generate.