diff options
author | Greg Kroah-Hartman <gregkh@suse.de> | 2011-10-25 03:18:11 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-10-25 03:18:11 -0400 |
commit | 43a3beb6da994549ec28a9f31727b997a025f958 (patch) | |
tree | 9fea6f7e2abd5ba7ce4d5f725a8ceed0a4e0ab80 /drivers/hv/connection.c | |
parent | c3b92c8787367a8bb53d57d9789b558f1295cc96 (diff) | |
parent | 68cf162a1af23c35db8e3b78659c99196c9882ff (diff) |
Merge branch 'staging-next' into Linux 3.1
This was done to resolve a conflict in the
drivers/staging/comedi/drivers/ni_labpc.c file that resolved a build
bugfix in Linus's tree with a "better" bugfix that was in the
staging-next tree that resolved the issue in a more complete manner.
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/hv/connection.c')
-rw-r--r-- | drivers/hv/connection.c | 318 |
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..650c9f0b6642 --- /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 | #include <asm/hyperv.h> | ||
34 | #include "hyperv_vmbus.h" | ||
35 | |||
36 | |||
37 | struct 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 | */ | ||
45 | int 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 | |||
164 | cleanup: | ||
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 | */ | ||
190 | struct 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 | */ | ||
211 | static 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 | */ | ||
248 | void 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 | */ | ||
283 | int 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 | */ | ||
310 | int 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 | } | ||