diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/staging/hv/connection.c | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'drivers/staging/hv/connection.c')
-rw-r--r-- | drivers/staging/hv/connection.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/drivers/staging/hv/connection.c b/drivers/staging/hv/connection.c new file mode 100644 index 00000000000..e6b40392e08 --- /dev/null +++ b/drivers/staging/hv/connection.c | |||
@@ -0,0 +1,290 @@ | |||
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/mm.h> | ||
29 | #include <linux/slab.h> | ||
30 | #include <linux/vmalloc.h> | ||
31 | |||
32 | #include "hyperv.h" | ||
33 | #include "hyperv_vmbus.h" | ||
34 | |||
35 | |||
36 | struct vmbus_connection vmbus_connection = { | ||
37 | .conn_state = DISCONNECTED, | ||
38 | .next_gpadl_handle = ATOMIC_INIT(0xE1E10), | ||
39 | }; | ||
40 | |||
41 | /* | ||
42 | * vmbus_connect - Sends a connect request on the partition service connection | ||
43 | */ | ||
44 | int vmbus_connect(void) | ||
45 | { | ||
46 | int ret = 0; | ||
47 | int t; | ||
48 | struct vmbus_channel_msginfo *msginfo = NULL; | ||
49 | struct vmbus_channel_initiate_contact *msg; | ||
50 | unsigned long flags; | ||
51 | |||
52 | /* Make sure we are not connecting or connected */ | ||
53 | if (vmbus_connection.conn_state != DISCONNECTED) | ||
54 | return -EISCONN; | ||
55 | |||
56 | /* Initialize the vmbus connection */ | ||
57 | vmbus_connection.conn_state = CONNECTING; | ||
58 | vmbus_connection.work_queue = create_workqueue("hv_vmbus_con"); | ||
59 | if (!vmbus_connection.work_queue) { | ||
60 | ret = -ENOMEM; | ||
61 | goto cleanup; | ||
62 | } | ||
63 | |||
64 | INIT_LIST_HEAD(&vmbus_connection.chn_msg_list); | ||
65 | spin_lock_init(&vmbus_connection.channelmsg_lock); | ||
66 | |||
67 | INIT_LIST_HEAD(&vmbus_connection.chn_list); | ||
68 | spin_lock_init(&vmbus_connection.channel_lock); | ||
69 | |||
70 | /* | ||
71 | * Setup the vmbus event connection for channel interrupt | ||
72 | * abstraction stuff | ||
73 | */ | ||
74 | vmbus_connection.int_page = | ||
75 | (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, 0); | ||
76 | if (vmbus_connection.int_page == NULL) { | ||
77 | ret = -ENOMEM; | ||
78 | goto cleanup; | ||
79 | } | ||
80 | |||
81 | vmbus_connection.recv_int_page = vmbus_connection.int_page; | ||
82 | vmbus_connection.send_int_page = | ||
83 | (void *)((unsigned long)vmbus_connection.int_page + | ||
84 | (PAGE_SIZE >> 1)); | ||
85 | |||
86 | /* | ||
87 | * Setup the monitor notification facility. The 1st page for | ||
88 | * parent->child and the 2nd page for child->parent | ||
89 | */ | ||
90 | vmbus_connection.monitor_pages = | ||
91 | (void *)__get_free_pages((GFP_KERNEL|__GFP_ZERO), 1); | ||
92 | if (vmbus_connection.monitor_pages == NULL) { | ||
93 | ret = -ENOMEM; | ||
94 | goto cleanup; | ||
95 | } | ||
96 | |||
97 | msginfo = kzalloc(sizeof(*msginfo) + | ||
98 | sizeof(struct vmbus_channel_initiate_contact), | ||
99 | GFP_KERNEL); | ||
100 | if (msginfo == NULL) { | ||
101 | ret = -ENOMEM; | ||
102 | goto cleanup; | ||
103 | } | ||
104 | |||
105 | init_completion(&msginfo->waitevent); | ||
106 | |||
107 | msg = (struct vmbus_channel_initiate_contact *)msginfo->msg; | ||
108 | |||
109 | msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT; | ||
110 | msg->vmbus_version_requested = VMBUS_REVISION_NUMBER; | ||
111 | msg->interrupt_page = virt_to_phys(vmbus_connection.int_page); | ||
112 | msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages); | ||
113 | msg->monitor_page2 = virt_to_phys( | ||
114 | (void *)((unsigned long)vmbus_connection.monitor_pages + | ||
115 | PAGE_SIZE)); | ||
116 | |||
117 | /* | ||
118 | * Add to list before we send the request since we may | ||
119 | * receive the response before returning from this routine | ||
120 | */ | ||
121 | spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); | ||
122 | list_add_tail(&msginfo->msglistentry, | ||
123 | &vmbus_connection.chn_msg_list); | ||
124 | |||
125 | spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); | ||
126 | |||
127 | ret = vmbus_post_msg(msg, | ||
128 | sizeof(struct vmbus_channel_initiate_contact)); | ||
129 | if (ret != 0) { | ||
130 | spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); | ||
131 | list_del(&msginfo->msglistentry); | ||
132 | spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, | ||
133 | flags); | ||
134 | goto cleanup; | ||
135 | } | ||
136 | |||
137 | /* Wait for the connection response */ | ||
138 | t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ); | ||
139 | if (t == 0) { | ||
140 | spin_lock_irqsave(&vmbus_connection.channelmsg_lock, | ||
141 | flags); | ||
142 | list_del(&msginfo->msglistentry); | ||
143 | spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, | ||
144 | flags); | ||
145 | ret = -ETIMEDOUT; | ||
146 | goto cleanup; | ||
147 | } | ||
148 | |||
149 | spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); | ||
150 | list_del(&msginfo->msglistentry); | ||
151 | spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); | ||
152 | |||
153 | /* Check if successful */ | ||
154 | if (msginfo->response.version_response.version_supported) { | ||
155 | vmbus_connection.conn_state = CONNECTED; | ||
156 | } else { | ||
157 | pr_err("Unable to connect, " | ||
158 | "Version %d not supported by Hyper-V\n", | ||
159 | VMBUS_REVISION_NUMBER); | ||
160 | ret = -ECONNREFUSED; | ||
161 | goto cleanup; | ||
162 | } | ||
163 | |||
164 | kfree(msginfo); | ||
165 | return 0; | ||
166 | |||
167 | cleanup: | ||
168 | vmbus_connection.conn_state = DISCONNECTED; | ||
169 | |||
170 | if (vmbus_connection.work_queue) | ||
171 | destroy_workqueue(vmbus_connection.work_queue); | ||
172 | |||
173 | if (vmbus_connection.int_page) { | ||
174 | free_pages((unsigned long)vmbus_connection.int_page, 0); | ||
175 | vmbus_connection.int_page = NULL; | ||
176 | } | ||
177 | |||
178 | if (vmbus_connection.monitor_pages) { | ||
179 | free_pages((unsigned long)vmbus_connection.monitor_pages, 1); | ||
180 | vmbus_connection.monitor_pages = NULL; | ||
181 | } | ||
182 | |||
183 | kfree(msginfo); | ||
184 | |||
185 | return ret; | ||
186 | } | ||
187 | |||
188 | |||
189 | /* | ||
190 | * relid2channel - Get the channel object given its | ||
191 | * child relative id (ie channel id) | ||
192 | */ | ||
193 | struct vmbus_channel *relid2channel(u32 relid) | ||
194 | { | ||
195 | struct vmbus_channel *channel; | ||
196 | struct vmbus_channel *found_channel = NULL; | ||
197 | unsigned long flags; | ||
198 | |||
199 | spin_lock_irqsave(&vmbus_connection.channel_lock, flags); | ||
200 | list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) { | ||
201 | if (channel->offermsg.child_relid == relid) { | ||
202 | found_channel = channel; | ||
203 | break; | ||
204 | } | ||
205 | } | ||
206 | spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); | ||
207 | |||
208 | return found_channel; | ||
209 | } | ||
210 | |||
211 | /* | ||
212 | * process_chn_event - Process a channel event notification | ||
213 | */ | ||
214 | static void process_chn_event(u32 relid) | ||
215 | { | ||
216 | struct vmbus_channel *channel; | ||
217 | |||
218 | /* ASSERT(relId > 0); */ | ||
219 | |||
220 | /* | ||
221 | * Find the channel based on this relid and invokes the | ||
222 | * channel callback to process the event | ||
223 | */ | ||
224 | channel = relid2channel(relid); | ||
225 | |||
226 | if (channel) { | ||
227 | channel->onchannel_callback(channel->channel_callback_context); | ||
228 | } else { | ||
229 | pr_err("channel not found for relid - %u\n", relid); | ||
230 | } | ||
231 | } | ||
232 | |||
233 | /* | ||
234 | * vmbus_on_event - Handler for events | ||
235 | */ | ||
236 | void vmbus_on_event(unsigned long data) | ||
237 | { | ||
238 | u32 dword; | ||
239 | u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5; | ||
240 | int bit; | ||
241 | u32 relid; | ||
242 | u32 *recv_int_page = vmbus_connection.recv_int_page; | ||
243 | |||
244 | /* Check events */ | ||
245 | if (!recv_int_page) | ||
246 | return; | ||
247 | for (dword = 0; dword < maxdword; dword++) { | ||
248 | if (!recv_int_page[dword]) | ||
249 | continue; | ||
250 | for (bit = 0; bit < 32; bit++) { | ||
251 | if (sync_test_and_clear_bit(bit, (unsigned long *)&recv_int_page[dword])) { | ||
252 | relid = (dword << 5) + bit; | ||
253 | |||
254 | if (relid == 0) { | ||
255 | /* | ||
256 | * Special case - vmbus | ||
257 | * channel protocol msg | ||
258 | */ | ||
259 | continue; | ||
260 | } | ||
261 | process_chn_event(relid); | ||
262 | } | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | |||
267 | /* | ||
268 | * vmbus_post_msg - Send a msg on the vmbus's message connection | ||
269 | */ | ||
270 | int vmbus_post_msg(void *buffer, size_t buflen) | ||
271 | { | ||
272 | union hv_connection_id conn_id; | ||
273 | |||
274 | conn_id.asu32 = 0; | ||
275 | conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID; | ||
276 | return hv_post_message(conn_id, 1, buffer, buflen); | ||
277 | } | ||
278 | |||
279 | /* | ||
280 | * vmbus_set_event - Send an event notification to the parent | ||
281 | */ | ||
282 | int vmbus_set_event(u32 child_relid) | ||
283 | { | ||
284 | /* Each u32 represents 32 channels */ | ||
285 | sync_set_bit(child_relid & 31, | ||
286 | (unsigned long *)vmbus_connection.send_int_page + | ||
287 | (child_relid >> 5)); | ||
288 | |||
289 | return hv_signal_event(); | ||
290 | } | ||