aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hv/connection.c
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@suse.de>2011-10-04 15:29:52 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2011-10-11 00:52:55 -0400
commit46a971913611a23478283931460a95be962ce329 (patch)
tree7452d0f07ee9f1f5270a8da6c1387f35c439843d /drivers/hv/connection.c
parent715a4801e734ea9c8e528265ce3ff6aead85bce1 (diff)
Staging: hv: move hyperv code out of staging directory
After many years wandering the desert, it is finally time for the Microsoft HyperV code to move out of the staging directory. Or at least the core hyperv bus code, and the utility driver, the rest still have some review to get through by the various subsystem maintainers. Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Diffstat (limited to 'drivers/hv/connection.c')
-rw-r--r--drivers/hv/connection.c318
1 files changed, 318 insertions, 0 deletions
diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c
new file mode 100644
index 000000000000..5f438b650068
--- /dev/null
+++ b/drivers/hv/connection.c
@@ -0,0 +1,318 @@
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 */
23#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
24
25#include <linux/kernel.h>
26#include <linux/sched.h>
27#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
34#include "hyperv_vmbus.h"
35
36
37struct vmbus_connection vmbus_connection = {
38 .conn_state = DISCONNECTED,
39 .next_gpadl_handle = ATOMIC_INIT(0xE1E10),
40};
41
42/*
43 * vmbus_connect - Sends a connect request on the partition service connection
44 */
45int vmbus_connect(void)
46{
47 int ret = 0;
48 int t;
49 struct vmbus_channel_msginfo *msginfo = NULL;
50 struct vmbus_channel_initiate_contact *msg;
51 unsigned long flags;
52
53 /* Initialize the vmbus connection */
54 vmbus_connection.conn_state = CONNECTING;
55 vmbus_connection.work_queue = create_workqueue("hv_vmbus_con");
56 if (!vmbus_connection.work_queue) {
57 ret = -ENOMEM;
58 goto cleanup;
59 }
60
61 INIT_LIST_HEAD(&vmbus_connection.chn_msg_list);
62 spin_lock_init(&vmbus_connection.channelmsg_lock);
63
64 INIT_LIST_HEAD(&vmbus_connection.chn_list);
65 spin_lock_init(&vmbus_connection.channel_lock);
66
67 /*
68 * Setup the vmbus event connection for channel interrupt
69 * abstraction stuff
70 */
71 vmbus_connection.int_page =
72 (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, 0);
73 if (vmbus_connection.int_page == NULL) {
74 ret = -ENOMEM;
75 goto cleanup;
76 }
77
78 vmbus_connection.recv_int_page = vmbus_connection.int_page;
79 vmbus_connection.send_int_page =
80 (void *)((unsigned long)vmbus_connection.int_page +
81 (PAGE_SIZE >> 1));
82
83 /*
84 * Setup the monitor notification facility. The 1st page for
85 * parent->child and the 2nd page for child->parent
86 */
87 vmbus_connection.monitor_pages =
88 (void *)__get_free_pages((GFP_KERNEL|__GFP_ZERO), 1);
89 if (vmbus_connection.monitor_pages == NULL) {
90 ret = -ENOMEM;
91 goto cleanup;
92 }
93
94 msginfo = kzalloc(sizeof(*msginfo) +
95 sizeof(struct vmbus_channel_initiate_contact),
96 GFP_KERNEL);
97 if (msginfo == NULL) {
98 ret = -ENOMEM;
99 goto cleanup;
100 }
101
102 init_completion(&msginfo->waitevent);
103
104 msg = (struct vmbus_channel_initiate_contact *)msginfo->msg;
105
106 msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT;
107 msg->vmbus_version_requested = VMBUS_REVISION_NUMBER;
108 msg->interrupt_page = virt_to_phys(vmbus_connection.int_page);
109 msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages);
110 msg->monitor_page2 = virt_to_phys(
111 (void *)((unsigned long)vmbus_connection.monitor_pages +
112 PAGE_SIZE));
113
114 /*
115 * Add to list before we send the request since we may
116 * receive the response before returning from this routine
117 */
118 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
119 list_add_tail(&msginfo->msglistentry,
120 &vmbus_connection.chn_msg_list);
121
122 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
123
124 ret = vmbus_post_msg(msg,
125 sizeof(struct vmbus_channel_initiate_contact));
126 if (ret != 0) {
127 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
128 list_del(&msginfo->msglistentry);
129 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
130 flags);
131 goto cleanup;
132 }
133
134 /* Wait for the connection response */
135 t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ);
136 if (t == 0) {
137 spin_lock_irqsave(&vmbus_connection.channelmsg_lock,
138 flags);
139 list_del(&msginfo->msglistentry);
140 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
141 flags);
142 ret = -ETIMEDOUT;
143 goto cleanup;
144 }
145
146 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
147 list_del(&msginfo->msglistentry);
148 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
149
150 /* Check if successful */
151 if (msginfo->response.version_response.version_supported) {
152 vmbus_connection.conn_state = CONNECTED;
153 } else {
154 pr_err("Unable to connect, "
155 "Version %d not supported by Hyper-V\n",
156 VMBUS_REVISION_NUMBER);
157 ret = -ECONNREFUSED;
158 goto cleanup;
159 }
160
161 kfree(msginfo);
162 return 0;
163
164cleanup:
165 vmbus_connection.conn_state = DISCONNECTED;
166
167 if (vmbus_connection.work_queue)
168 destroy_workqueue(vmbus_connection.work_queue);
169
170 if (vmbus_connection.int_page) {
171 free_pages((unsigned long)vmbus_connection.int_page, 0);
172 vmbus_connection.int_page = NULL;
173 }
174
175 if (vmbus_connection.monitor_pages) {
176 free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
177 vmbus_connection.monitor_pages = NULL;
178 }
179
180 kfree(msginfo);
181
182 return ret;
183}
184
185
186/*
187 * relid2channel - Get the channel object given its
188 * child relative id (ie channel id)
189 */
190struct vmbus_channel *relid2channel(u32 relid)
191{
192 struct vmbus_channel *channel;
193 struct vmbus_channel *found_channel = NULL;
194 unsigned long flags;
195
196 spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
197 list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
198 if (channel->offermsg.child_relid == relid) {
199 found_channel = channel;
200 break;
201 }
202 }
203 spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
204
205 return found_channel;
206}
207
208/*
209 * process_chn_event - Process a channel event notification
210 */
211static void process_chn_event(u32 relid)
212{
213 struct vmbus_channel *channel;
214 unsigned long flags;
215
216 /*
217 * Find the channel based on this relid and invokes the
218 * channel callback to process the event
219 */
220 channel = relid2channel(relid);
221
222 if (!channel) {
223 pr_err("channel not found for relid - %u\n", relid);
224 return;
225 }
226
227 /*
228 * A channel once created is persistent even when there
229 * is no driver handling the device. An unloading driver
230 * sets the onchannel_callback to NULL under the
231 * protection of the channel inbound_lock. Thus, checking
232 * and invoking the driver specific callback takes care of
233 * orderly unloading of the driver.
234 */
235
236 spin_lock_irqsave(&channel->inbound_lock, flags);
237 if (channel->onchannel_callback != NULL)
238 channel->onchannel_callback(channel->channel_callback_context);
239 else
240 pr_err("no channel callback for relid - %u\n", relid);
241
242 spin_unlock_irqrestore(&channel->inbound_lock, flags);
243}
244
245/*
246 * vmbus_on_event - Handler for events
247 */
248void vmbus_on_event(unsigned long data)
249{
250 u32 dword;
251 u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
252 int bit;
253 u32 relid;
254 u32 *recv_int_page = vmbus_connection.recv_int_page;
255
256 /* Check events */
257 if (!recv_int_page)
258 return;
259 for (dword = 0; dword < maxdword; dword++) {
260 if (!recv_int_page[dword])
261 continue;
262 for (bit = 0; bit < 32; bit++) {
263 if (sync_test_and_clear_bit(bit,
264 (unsigned long *)&recv_int_page[dword])) {
265 relid = (dword << 5) + bit;
266
267 if (relid == 0)
268 /*
269 * Special case - vmbus
270 * channel protocol msg
271 */
272 continue;
273
274 process_chn_event(relid);
275 }
276 }
277 }
278}
279
280/*
281 * vmbus_post_msg - Send a msg on the vmbus's message connection
282 */
283int vmbus_post_msg(void *buffer, size_t buflen)
284{
285 union hv_connection_id conn_id;
286 int ret = 0;
287 int retries = 0;
288
289 conn_id.asu32 = 0;
290 conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
291
292 /*
293 * hv_post_message() can have transient failures because of
294 * insufficient resources. Retry the operation a couple of
295 * times before giving up.
296 */
297 while (retries < 3) {
298 ret = hv_post_message(conn_id, 1, buffer, buflen);
299 if (ret != HV_STATUS_INSUFFICIENT_BUFFERS)
300 return ret;
301 retries++;
302 msleep(100);
303 }
304 return ret;
305}
306
307/*
308 * vmbus_set_event - Send an event notification to the parent
309 */
310int vmbus_set_event(u32 child_relid)
311{
312 /* Each u32 represents 32 channels */
313 sync_set_bit(child_relid & 31,
314 (unsigned long *)vmbus_connection.send_int_page +
315 (child_relid >> 5));
316
317 return hv_signal_event();
318}