sh: select ARCH_NO_SYSDEV_OPS.
[pandora-kernel.git] / drivers / staging / hv / connection.c
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 #include <linux/kernel.h>
24 #include <linux/sched.h>
25 #include <linux/wait.h>
26 #include <linux/mm.h>
27 #include <linux/slab.h>
28 #include <linux/vmalloc.h>
29 #include "hv_api.h"
30 #include "logging.h"
31 #include "vmbus_private.h"
32
33
34 struct vmbus_connection vmbus_connection = {
35         .conn_state             = DISCONNECTED,
36         .next_gpadl_handle      = ATOMIC_INIT(0xE1E10),
37 };
38
39 /*
40  * vmbus_connect - Sends a connect request on the partition service connection
41  */
42 int vmbus_connect(void)
43 {
44         int ret = 0;
45         struct vmbus_channel_msginfo *msginfo = NULL;
46         struct vmbus_channel_initiate_contact *msg;
47         unsigned long flags;
48
49         /* Make sure we are not connecting or connected */
50         if (vmbus_connection.conn_state != DISCONNECTED)
51                 return -1;
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 = -1;
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 = -1;
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 = -1;
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_waitqueue_head(&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         DPRINT_DBG(VMBUS, "Vmbus connection - interrupt pfn %llx, "
125                    "monitor1 pfn %llx,, monitor2 pfn %llx",
126                    msg->interrupt_page, msg->monitor_page1, msg->monitor_page2);
127
128         DPRINT_DBG(VMBUS, "Sending channel initiate msg...");
129         ret = vmbus_post_msg(msg,
130                                sizeof(struct vmbus_channel_initiate_contact));
131         if (ret != 0) {
132                 spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
133                 list_del(&msginfo->msglistentry);
134                 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
135                                         flags);
136                 goto Cleanup;
137         }
138
139         /* Wait for the connection response */
140         msginfo->wait_condition = 0;
141         wait_event_timeout(msginfo->waitevent, msginfo->wait_condition,
142                         msecs_to_jiffies(1000));
143         if (msginfo->wait_condition == 0) {
144                 spin_lock_irqsave(&vmbus_connection.channelmsg_lock,
145                                 flags);
146                 list_del(&msginfo->msglistentry);
147                 spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock,
148                                         flags);
149                 ret = -ETIMEDOUT;
150                 goto Cleanup;
151         }
152
153         spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags);
154         list_del(&msginfo->msglistentry);
155         spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
156
157         /* Check if successful */
158         if (msginfo->response.version_response.version_supported) {
159                 DPRINT_INFO(VMBUS, "Vmbus connected!!");
160                 vmbus_connection.conn_state = CONNECTED;
161
162         } else {
163                 DPRINT_ERR(VMBUS, "Vmbus connection failed!!..."
164                            "current version (%d) not supported",
165                            VMBUS_REVISION_NUMBER);
166                 ret = -1;
167                 goto Cleanup;
168         }
169
170         kfree(msginfo);
171         return 0;
172
173 Cleanup:
174         vmbus_connection.conn_state = DISCONNECTED;
175
176         if (vmbus_connection.work_queue)
177                 destroy_workqueue(vmbus_connection.work_queue);
178
179         if (vmbus_connection.int_page) {
180                 free_pages((unsigned long)vmbus_connection.int_page, 0);
181                 vmbus_connection.int_page = NULL;
182         }
183
184         if (vmbus_connection.monitor_pages) {
185                 free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
186                 vmbus_connection.monitor_pages = NULL;
187         }
188
189         kfree(msginfo);
190
191         return ret;
192 }
193
194 /*
195  * vmbus_disconnect -
196  * Sends a disconnect request on the partition service connection
197  */
198 int vmbus_disconnect(void)
199 {
200         int ret = 0;
201         struct vmbus_channel_message_header *msg;
202
203         /* Make sure we are connected */
204         if (vmbus_connection.conn_state != CONNECTED)
205                 return -1;
206
207         msg = kzalloc(sizeof(struct vmbus_channel_message_header), GFP_KERNEL);
208         if (!msg)
209                 return -ENOMEM;
210
211         msg->msgtype = CHANNELMSG_UNLOAD;
212
213         ret = vmbus_post_msg(msg,
214                                sizeof(struct vmbus_channel_message_header));
215         if (ret != 0)
216                 goto Cleanup;
217
218         free_pages((unsigned long)vmbus_connection.int_page, 0);
219         free_pages((unsigned long)vmbus_connection.monitor_pages, 1);
220
221         /* TODO: iterate thru the msg list and free up */
222         destroy_workqueue(vmbus_connection.work_queue);
223
224         vmbus_connection.conn_state = DISCONNECTED;
225
226         DPRINT_INFO(VMBUS, "Vmbus disconnected!!");
227
228 Cleanup:
229         kfree(msg);
230         return ret;
231 }
232
233 /*
234  * relid2channel - Get the channel object given its
235  * child relative id (ie channel id)
236  */
237 struct vmbus_channel *relid2channel(u32 relid)
238 {
239         struct vmbus_channel *channel;
240         struct vmbus_channel *found_channel  = NULL;
241         unsigned long flags;
242
243         spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
244         list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
245                 if (channel->offermsg.child_relid == relid) {
246                         found_channel = channel;
247                         break;
248                 }
249         }
250         spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
251
252         return found_channel;
253 }
254
255 /*
256  * process_chn_event - Process a channel event notification
257  */
258 static void process_chn_event(void *context)
259 {
260         struct vmbus_channel *channel;
261         u32 relid = (u32)(unsigned long)context;
262
263         /* ASSERT(relId > 0); */
264
265         /*
266          * Find the channel based on this relid and invokes the
267          * channel callback to process the event
268          */
269         channel = relid2channel(relid);
270
271         if (channel) {
272                 vmbus_onchannel_event(channel);
273                 /*
274                  * WorkQueueQueueWorkItem(channel->dataWorkQueue,
275                  *                        vmbus_onchannel_event,
276                  *                        (void*)channel);
277                  */
278         } else {
279                 DPRINT_ERR(VMBUS, "channel not found for relid - %d.", relid);
280         }
281 }
282
283 /*
284  * vmbus_on_event - Handler for events
285  */
286 void vmbus_on_event(unsigned long data)
287 {
288         int dword;
289         int maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
290         int bit;
291         int relid;
292         u32 *recv_int_page = vmbus_connection.recv_int_page;
293
294         /* Check events */
295         if (recv_int_page) {
296                 for (dword = 0; dword < maxdword; dword++) {
297                         if (recv_int_page[dword]) {
298                                 for (bit = 0; bit < 32; bit++) {
299                                         if (test_and_clear_bit(bit,
300                                                 (unsigned long *)
301                                                 &recv_int_page[dword])) {
302                                                 relid = (dword << 5) + bit;
303                                                 DPRINT_DBG(VMBUS, "event detected for relid - %d", relid);
304
305                                                 if (relid == 0) {
306                                                         /* special case - vmbus channel protocol msg */
307                                                         DPRINT_DBG(VMBUS, "invalid relid - %d", relid);
308                                                         continue;
309                                                 } else {
310                                                         /* QueueWorkItem(VmbusProcessEvent, (void*)relid); */
311                                                         /* ret = WorkQueueQueueWorkItem(gVmbusConnection.workQueue, VmbusProcessChannelEvent, (void*)relid); */
312                                                 process_chn_event((void *)
313                                                 (unsigned long)relid);
314                                                 }
315                                         }
316                                 }
317                         }
318                  }
319         }
320         return;
321 }
322
323 /*
324  * vmbus_post_msg - Send a msg on the vmbus's message connection
325  */
326 int vmbus_post_msg(void *buffer, size_t buflen)
327 {
328         union hv_connection_id conn_id;
329
330         conn_id.asu32 = 0;
331         conn_id.u.id = VMBUS_MESSAGE_CONNECTION_ID;
332         return hv_post_message(conn_id, 1, buffer, buflen);
333 }
334
335 /*
336  * vmbus_set_event - Send an event notification to the parent
337  */
338 int vmbus_set_event(u32 child_relid)
339 {
340         /* Each u32 represents 32 channels */
341         set_bit(child_relid & 31,
342                 (unsigned long *)vmbus_connection.send_int_page +
343                 (child_relid >> 5));
344
345         return hv_signal_event();
346 }