Staging: hv: vmbus: Properly deal with de-registering channel callback
[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>
5289d3d1 28#include <linux/delay.h>
a0086dc5 29#include <linux/mm.h>
5a0e3ad6 30#include <linux/slab.h>
a0086dc5 31#include <linux/vmalloc.h>
3f335ea2
S
32
33#include "hyperv.h"
0f2a6619 34#include "hyperv_vmbus.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)
3a7546d9 55 return -EISCONN;
3e7ee490 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) {
3a7546d9 61 ret = -ENOMEM;
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) {
3a7546d9 78 ret = -ENOMEM;
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) {
3a7546d9 94 ret = -ENOMEM;
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 */
2dfde964 139 t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ);
9568a193 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);
3a7546d9 161 ret = -ECONNREFUSED;
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
3e7ee490 189
3e189519 190/*
c6977677
HZ
191 * relid2channel - Get the channel object given its
192 * child relative id (ie channel id)
fd8b85ea 193 */
c6977677 194struct vmbus_channel *relid2channel(u32 relid)
3e7ee490 195{
aded7165 196 struct vmbus_channel *channel;
15b2f647 197 struct vmbus_channel *found_channel = NULL;
0f5e44ca 198 unsigned long flags;
3e7ee490 199
15b2f647 200 spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
da9fcb72 201 list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
15b2f647
HZ
202 if (channel->offermsg.child_relid == relid) {
203 found_channel = channel;
3e7ee490
HJ
204 break;
205 }
206 }
15b2f647 207 spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
3e7ee490 208
15b2f647 209 return found_channel;
3e7ee490
HJ
210}
211
3e189519 212/*
c6977677 213 * process_chn_event - Process a channel event notification
fd8b85ea 214 */
35436487 215static void process_chn_event(u32 relid)
3e7ee490 216{
aded7165 217 struct vmbus_channel *channel;
dad76bf7 218 unsigned long flags;
3e7ee490 219
454f18a9
BP
220 /*
221 * Find the channel based on this relid and invokes the
222 * channel callback to process the event
223 */
c6977677 224 channel = relid2channel(relid);
3e7ee490 225
dad76bf7 226 spin_lock_irqsave(&channel->inbound_lock, flags);
76c39d42 227 if (channel && (channel->onchannel_callback != NULL)) {
df452fa1 228 channel->onchannel_callback(channel->channel_callback_context);
fd8b85ea 229 } else {
35436487 230 pr_err("channel not found for relid - %u\n", relid);
3e7ee490 231 }
dad76bf7 232 spin_unlock_irqrestore(&channel->inbound_lock, flags);
3e7ee490
HJ
233}
234
3e189519 235/*
c6977677 236 * vmbus_on_event - Handler for events
fd8b85ea 237 */
6de3d6aa 238void vmbus_on_event(unsigned long data)
3e7ee490 239{
35436487
OH
240 u32 dword;
241 u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
3e7ee490 242 int bit;
35436487 243 u32 relid;
da9fcb72 244 u32 *recv_int_page = vmbus_connection.recv_int_page;
3e7ee490 245
454f18a9 246 /* Check events */
242b45aa
OH
247 if (!recv_int_page)
248 return;
249 for (dword = 0; dword < maxdword; dword++) {
250 if (!recv_int_page[dword])
251 continue;
252 for (bit = 0; bit < 32; bit++) {
253 if (sync_test_and_clear_bit(bit, (unsigned long *)&recv_int_page[dword])) {
254 relid = (dword << 5) + bit;
255
256 if (relid == 0) {
6d81d330
S
257 /*
258 * Special case - vmbus
259 * channel protocol msg
260 */
242b45aa 261 continue;
3e7ee490 262 }
35436487 263 process_chn_event(relid);
3e7ee490 264 }
242b45aa 265 }
3e7ee490 266 }
3e7ee490
HJ
267}
268
3e189519 269/*
c6977677 270 * vmbus_post_msg - Send a msg on the vmbus's message connection
fd8b85ea 271 */
c6977677 272int vmbus_post_msg(void *buffer, size_t buflen)
3e7ee490 273{
15b2f647 274 union hv_connection_id conn_id;
5289d3d1
S
275 int ret = 0;
276 int retries = 0;
3e7ee490 277
15b2f647
HZ
278 conn_id.asu32 = 0;
279 conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
5289d3d1
S
280
281 /*
282 * hv_post_message() can have transient failures because of
283 * insufficient resources. Retry the operation a couple of
284 * times before giving up.
285 */
286 while (retries < 3) {
287 ret = hv_post_message(conn_id, 1, buffer, buflen);
288 if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
289 return ret;
290 retries++;
291 msleep(100);
292 }
293 return ret;
3e7ee490
HJ
294}
295
3e189519 296/*
c6977677 297 * vmbus_set_event - Send an event notification to the parent
fd8b85ea 298 */
c6977677 299int vmbus_set_event(u32 child_relid)
3e7ee490 300{
454f18a9 301 /* Each u32 represents 32 channels */
22356585 302 sync_set_bit(child_relid & 31,
da9fcb72 303 (unsigned long *)vmbus_connection.send_int_page +
15b2f647 304 (child_relid >> 5));
7c369f40 305
d44890c8 306 return hv_signal_event();
3e7ee490 307}
This page took 0.25078 seconds and 5 git commands to generate.