Merge branch 'linux-next' of git://git.kernel.org/pub/scm/linux/kernel/git/jbarnes...
[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/mm.h>
25 #include <linux/slab.h>
26 #include <linux/vmalloc.h>
27 #include "osd.h"
28 #include "logging.h"
29 #include "vmbus_private.h"
30
31
32 struct VMBUS_CONNECTION gVmbusConnection = {
33         .ConnectState           = Disconnected,
34         .NextGpadlHandle        = ATOMIC_INIT(0xE1E10),
35 };
36
37 /*
38  * VmbusConnect - Sends a connect request on the partition service connection
39  */
40 int VmbusConnect(void)
41 {
42         int ret = 0;
43         struct vmbus_channel_msginfo *msgInfo = NULL;
44         struct vmbus_channel_initiate_contact *msg;
45         unsigned long flags;
46
47         DPRINT_ENTER(VMBUS);
48
49         /* Make sure we are not connecting or connected */
50         if (gVmbusConnection.ConnectState != Disconnected)
51                 return -1;
52
53         /* Initialize the vmbus connection */
54         gVmbusConnection.ConnectState = Connecting;
55         gVmbusConnection.WorkQueue = create_workqueue("hv_vmbus_con");
56         if (!gVmbusConnection.WorkQueue) {
57                 ret = -1;
58                 goto Cleanup;
59         }
60
61         INIT_LIST_HEAD(&gVmbusConnection.ChannelMsgList);
62         spin_lock_init(&gVmbusConnection.channelmsg_lock);
63
64         INIT_LIST_HEAD(&gVmbusConnection.ChannelList);
65         spin_lock_init(&gVmbusConnection.channel_lock);
66
67         /*
68          * Setup the vmbus event connection for channel interrupt
69          * abstraction stuff
70          */
71         gVmbusConnection.InterruptPage = osd_PageAlloc(1);
72         if (gVmbusConnection.InterruptPage == NULL) {
73                 ret = -1;
74                 goto Cleanup;
75         }
76
77         gVmbusConnection.RecvInterruptPage = gVmbusConnection.InterruptPage;
78         gVmbusConnection.SendInterruptPage =
79                 (void *)((unsigned long)gVmbusConnection.InterruptPage +
80                         (PAGE_SIZE >> 1));
81
82         /*
83          * Setup the monitor notification facility. The 1st page for
84          * parent->child and the 2nd page for child->parent
85          */
86         gVmbusConnection.MonitorPages = osd_PageAlloc(2);
87         if (gVmbusConnection.MonitorPages == NULL) {
88                 ret = -1;
89                 goto Cleanup;
90         }
91
92         msgInfo = kzalloc(sizeof(*msgInfo) +
93                           sizeof(struct vmbus_channel_initiate_contact),
94                           GFP_KERNEL);
95         if (msgInfo == NULL) {
96                 ret = -ENOMEM;
97                 goto Cleanup;
98         }
99
100         msgInfo->WaitEvent = osd_WaitEventCreate();
101         if (!msgInfo->WaitEvent) {
102                 ret = -ENOMEM;
103                 goto Cleanup;
104         }
105
106         msg = (struct vmbus_channel_initiate_contact *)msgInfo->Msg;
107
108         msg->Header.MessageType = ChannelMessageInitiateContact;
109         msg->VMBusVersionRequested = VMBUS_REVISION_NUMBER;
110         msg->InterruptPage = virt_to_phys(gVmbusConnection.InterruptPage);
111         msg->MonitorPage1 = virt_to_phys(gVmbusConnection.MonitorPages);
112         msg->MonitorPage2 = virt_to_phys(
113                         (void *)((unsigned long)gVmbusConnection.MonitorPages +
114                                  PAGE_SIZE));
115
116         /*
117          * Add to list before we send the request since we may
118          * receive the response before returning from this routine
119          */
120         spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags);
121         list_add_tail(&msgInfo->MsgListEntry,
122                       &gVmbusConnection.ChannelMsgList);
123
124         spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags);
125
126         DPRINT_DBG(VMBUS, "Vmbus connection - interrupt pfn %llx, "
127                    "monitor1 pfn %llx,, monitor2 pfn %llx",
128                    msg->InterruptPage, msg->MonitorPage1, msg->MonitorPage2);
129
130         DPRINT_DBG(VMBUS, "Sending channel initiate msg...");
131         ret = VmbusPostMessage(msg,
132                                sizeof(struct vmbus_channel_initiate_contact));
133         if (ret != 0) {
134                 list_del(&msgInfo->MsgListEntry);
135                 goto Cleanup;
136         }
137
138         /* Wait for the connection response */
139         osd_WaitEventWait(msgInfo->WaitEvent);
140
141         list_del(&msgInfo->MsgListEntry);
142
143         /* Check if successful */
144         if (msgInfo->Response.VersionResponse.VersionSupported) {
145                 DPRINT_INFO(VMBUS, "Vmbus connected!!");
146                 gVmbusConnection.ConnectState = Connected;
147
148         } else {
149                 DPRINT_ERR(VMBUS, "Vmbus connection failed!!..."
150                            "current version (%d) not supported",
151                            VMBUS_REVISION_NUMBER);
152                 ret = -1;
153                 goto Cleanup;
154         }
155
156         kfree(msgInfo->WaitEvent);
157         kfree(msgInfo);
158         DPRINT_EXIT(VMBUS);
159
160         return 0;
161
162 Cleanup:
163         gVmbusConnection.ConnectState = Disconnected;
164
165         if (gVmbusConnection.WorkQueue)
166                 destroy_workqueue(gVmbusConnection.WorkQueue);
167
168         if (gVmbusConnection.InterruptPage) {
169                 osd_PageFree(gVmbusConnection.InterruptPage, 1);
170                 gVmbusConnection.InterruptPage = NULL;
171         }
172
173         if (gVmbusConnection.MonitorPages) {
174                 osd_PageFree(gVmbusConnection.MonitorPages, 2);
175                 gVmbusConnection.MonitorPages = NULL;
176         }
177
178         if (msgInfo) {
179                 kfree(msgInfo->WaitEvent);
180                 kfree(msgInfo);
181         }
182
183         DPRINT_EXIT(VMBUS);
184
185         return ret;
186 }
187
188 /*
189  * VmbusDisconnect - Sends a disconnect request on the partition service connection
190  */
191 int VmbusDisconnect(void)
192 {
193         int ret = 0;
194         struct vmbus_channel_message_header *msg;
195
196         DPRINT_ENTER(VMBUS);
197
198         /* Make sure we are connected */
199         if (gVmbusConnection.ConnectState != Connected)
200                 return -1;
201
202         msg = kzalloc(sizeof(struct vmbus_channel_message_header), GFP_KERNEL);
203         if (!msg)
204                 return -ENOMEM;
205
206         msg->MessageType = ChannelMessageUnload;
207
208         ret = VmbusPostMessage(msg,
209                                sizeof(struct vmbus_channel_message_header));
210         if (ret != 0)
211                 goto Cleanup;
212
213         osd_PageFree(gVmbusConnection.InterruptPage, 1);
214
215         /* TODO: iterate thru the msg list and free up */
216         destroy_workqueue(gVmbusConnection.WorkQueue);
217
218         gVmbusConnection.ConnectState = Disconnected;
219
220         DPRINT_INFO(VMBUS, "Vmbus disconnected!!");
221
222 Cleanup:
223         kfree(msg);
224         DPRINT_EXIT(VMBUS);
225         return ret;
226 }
227
228 /*
229  * GetChannelFromRelId - Get the channel object given its child relative id (ie channel id)
230  */
231 struct vmbus_channel *GetChannelFromRelId(u32 relId)
232 {
233         struct vmbus_channel *channel;
234         struct vmbus_channel *foundChannel  = NULL;
235         unsigned long flags;
236
237         spin_lock_irqsave(&gVmbusConnection.channel_lock, flags);
238         list_for_each_entry(channel, &gVmbusConnection.ChannelList, ListEntry) {
239                 if (channel->OfferMsg.ChildRelId == relId) {
240                         foundChannel = channel;
241                         break;
242                 }
243         }
244         spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags);
245
246         return foundChannel;
247 }
248
249 /*
250  * VmbusProcessChannelEvent - Process a channel event notification
251  */
252 static void VmbusProcessChannelEvent(void *context)
253 {
254         struct vmbus_channel *channel;
255         u32 relId = (u32)(unsigned long)context;
256
257         /* ASSERT(relId > 0); */
258
259         /*
260          * Find the channel based on this relid and invokes the
261          * channel callback to process the event
262          */
263         channel = GetChannelFromRelId(relId);
264
265         if (channel) {
266                 VmbusChannelOnChannelEvent(channel);
267                 /*
268                  * WorkQueueQueueWorkItem(channel->dataWorkQueue,
269                  *                        VmbusChannelOnChannelEvent,
270                  *                        (void*)channel);
271                  */
272         } else {
273                 DPRINT_ERR(VMBUS, "channel not found for relid - %d.", relId);
274         }
275 }
276
277 /*
278  * VmbusOnEvents - Handler for events
279  */
280 void VmbusOnEvents(void)
281 {
282         int dword;
283         int maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
284         int bit;
285         int relid;
286         u32 *recvInterruptPage = gVmbusConnection.RecvInterruptPage;
287
288         DPRINT_ENTER(VMBUS);
289
290         /* Check events */
291         if (recvInterruptPage) {
292                 for (dword = 0; dword < maxdword; dword++) {
293                         if (recvInterruptPage[dword]) {
294                                 for (bit = 0; bit < 32; bit++) {
295                                         if (test_and_clear_bit(bit, (unsigned long *)&recvInterruptPage[dword])) {
296                                                 relid = (dword << 5) + bit;
297                                                 DPRINT_DBG(VMBUS, "event detected for relid - %d", relid);
298
299                                                 if (relid == 0) {
300                                                         /* special case - vmbus channel protocol msg */
301                                                         DPRINT_DBG(VMBUS, "invalid relid - %d", relid);
302                                                         continue;
303                                                 } else {
304                                                         /* QueueWorkItem(VmbusProcessEvent, (void*)relid); */
305                                                         /* ret = WorkQueueQueueWorkItem(gVmbusConnection.workQueue, VmbusProcessChannelEvent, (void*)relid); */
306                                                         VmbusProcessChannelEvent((void *)(unsigned long)relid);
307                                                 }
308                                         }
309                                 }
310                         }
311                  }
312         }
313         DPRINT_EXIT(VMBUS);
314
315         return;
316 }
317
318 /*
319  * VmbusPostMessage - Send a msg on the vmbus's message connection
320  */
321 int VmbusPostMessage(void *buffer, size_t bufferLen)
322 {
323         union hv_connection_id connId;
324
325         connId.Asu32 = 0;
326         connId.u.Id = VMBUS_MESSAGE_CONNECTION_ID;
327         return HvPostMessage(connId, 1, buffer, bufferLen);
328 }
329
330 /*
331  * VmbusSetEvent - Send an event notification to the parent
332  */
333 int VmbusSetEvent(u32 childRelId)
334 {
335         int ret = 0;
336
337         DPRINT_ENTER(VMBUS);
338
339         /* Each u32 represents 32 channels */
340         set_bit(childRelId & 31,
341                 (unsigned long *)gVmbusConnection.SendInterruptPage +
342                 (childRelId >> 5));
343
344         ret = HvSignalEvent();
345
346         DPRINT_EXIT(VMBUS);
347
348         return ret;
349 }