Root/
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 | } |
319 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9