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