Added patch headers.
[linux-flexiantxendom0-3.2.10.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 "VmbusPrivate.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 = -1;
97                 goto Cleanup;
98         }
99
100         msgInfo->WaitEvent = osd_WaitEventCreate();
101         msg = (struct vmbus_channel_initiate_contact *)msgInfo->Msg;
102
103         msg->Header.MessageType = ChannelMessageInitiateContact;
104         msg->VMBusVersionRequested = VMBUS_REVISION_NUMBER;
105         msg->InterruptPage = virt_to_phys(gVmbusConnection.InterruptPage);
106         msg->MonitorPage1 = virt_to_phys(gVmbusConnection.MonitorPages);
107         msg->MonitorPage2 = virt_to_phys(
108                         (void *)((unsigned long)gVmbusConnection.MonitorPages +
109                                  PAGE_SIZE));
110
111         /*
112          * Add to list before we send the request since we may
113          * receive the response before returning from this routine
114          */
115         spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags);
116         list_add_tail(&msgInfo->MsgListEntry,
117                       &gVmbusConnection.ChannelMsgList);
118
119         spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags);
120
121         DPRINT_DBG(VMBUS, "Vmbus connection - interrupt pfn %llx, "
122                    "monitor1 pfn %llx,, monitor2 pfn %llx",
123                    msg->InterruptPage, msg->MonitorPage1, msg->MonitorPage2);
124
125         DPRINT_DBG(VMBUS, "Sending channel initiate msg...");
126         ret = VmbusPostMessage(msg,
127                                sizeof(struct vmbus_channel_initiate_contact));
128         if (ret != 0) {
129                 list_del(&msgInfo->MsgListEntry);
130                 goto Cleanup;
131         }
132
133         /* Wait for the connection response */
134         osd_WaitEventWait(msgInfo->WaitEvent);
135
136         list_del(&msgInfo->MsgListEntry);
137
138         /* Check if successful */
139         if (msgInfo->Response.VersionResponse.VersionSupported) {
140                 DPRINT_INFO(VMBUS, "Vmbus connected!!");
141                 gVmbusConnection.ConnectState = Connected;
142
143         } else {
144                 DPRINT_ERR(VMBUS, "Vmbus connection failed!!..."
145                            "current version (%d) not supported",
146                            VMBUS_REVISION_NUMBER);
147                 ret = -1;
148                 goto Cleanup;
149         }
150
151         kfree(msgInfo->WaitEvent);
152         kfree(msgInfo);
153         DPRINT_EXIT(VMBUS);
154
155         return 0;
156
157 Cleanup:
158         gVmbusConnection.ConnectState = Disconnected;
159
160         if (gVmbusConnection.WorkQueue)
161                 destroy_workqueue(gVmbusConnection.WorkQueue);
162
163         if (gVmbusConnection.InterruptPage) {
164                 osd_PageFree(gVmbusConnection.InterruptPage, 1);
165                 gVmbusConnection.InterruptPage = NULL;
166         }
167
168         if (gVmbusConnection.MonitorPages) {
169                 osd_PageFree(gVmbusConnection.MonitorPages, 2);
170                 gVmbusConnection.MonitorPages = NULL;
171         }
172
173         if (msgInfo) {
174                 kfree(msgInfo->WaitEvent);
175                 kfree(msgInfo);
176         }
177
178         DPRINT_EXIT(VMBUS);
179
180         return ret;
181 }
182
183 /**
184  * VmbusDisconnect - Sends a disconnect request on the partition service connection
185  */
186 int VmbusDisconnect(void)
187 {
188         int ret = 0;
189         struct vmbus_channel_message_header *msg;
190
191         DPRINT_ENTER(VMBUS);
192
193         /* Make sure we are connected */
194         if (gVmbusConnection.ConnectState != Connected)
195                 return -1;
196
197         msg = kzalloc(sizeof(struct vmbus_channel_message_header), GFP_KERNEL);
198
199         msg->MessageType = ChannelMessageUnload;
200
201         ret = VmbusPostMessage(msg,
202                                sizeof(struct vmbus_channel_message_header));
203         if (ret != 0)
204                 goto Cleanup;
205
206         osd_PageFree(gVmbusConnection.InterruptPage, 1);
207
208         /* TODO: iterate thru the msg list and free up */
209         destroy_workqueue(gVmbusConnection.WorkQueue);
210
211         gVmbusConnection.ConnectState = Disconnected;
212
213         DPRINT_INFO(VMBUS, "Vmbus disconnected!!");
214
215 Cleanup:
216         kfree(msg);
217         DPRINT_EXIT(VMBUS);
218         return ret;
219 }
220
221 /**
222  * GetChannelFromRelId - Get the channel object given its child relative id (ie channel id)
223  */
224 struct vmbus_channel *GetChannelFromRelId(u32 relId)
225 {
226         struct vmbus_channel *channel;
227         struct vmbus_channel *foundChannel  = NULL;
228         unsigned long flags;
229
230         spin_lock_irqsave(&gVmbusConnection.channel_lock, flags);
231         list_for_each_entry(channel, &gVmbusConnection.ChannelList, ListEntry) {
232                 if (channel->OfferMsg.ChildRelId == relId) {
233                         foundChannel = channel;
234                         break;
235                 }
236         }
237         spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags);
238
239         return foundChannel;
240 }
241
242 /**
243  * VmbusProcessChannelEvent - Process a channel event notification
244  */
245 static void VmbusProcessChannelEvent(void *context)
246 {
247         struct vmbus_channel *channel;
248         u32 relId = (u32)(unsigned long)context;
249
250         ASSERT(relId > 0);
251
252         /*
253          * Find the channel based on this relid and invokes the
254          * channel callback to process the event
255          */
256         channel = GetChannelFromRelId(relId);
257
258         if (channel) {
259                 VmbusChannelOnChannelEvent(channel);
260                 /*
261                  * WorkQueueQueueWorkItem(channel->dataWorkQueue,
262                  *                        VmbusChannelOnChannelEvent,
263                  *                        (void*)channel);
264                  */
265         } else {
266                 DPRINT_ERR(VMBUS, "channel not found for relid - %d.", relId);
267         }
268 }
269
270 /**
271  * VmbusOnEvents - Handler for events
272  */
273 void VmbusOnEvents(void)
274 {
275         int dword;
276         int maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5;
277         int bit;
278         int relid;
279         u32 *recvInterruptPage = gVmbusConnection.RecvInterruptPage;
280
281         DPRINT_ENTER(VMBUS);
282
283         /* Check events */
284         if (recvInterruptPage) {
285                 for (dword = 0; dword < maxdword; dword++) {
286                         if (recvInterruptPage[dword]) {
287                                 for (bit = 0; bit < 32; bit++) {
288                                         if (test_and_clear_bit(bit, (unsigned long *)&recvInterruptPage[dword])) {
289                                                 relid = (dword << 5) + bit;
290                                                 DPRINT_DBG(VMBUS, "event detected for relid - %d", relid);
291
292                                                 if (relid == 0) {
293                                                         /* special case - vmbus channel protocol msg */
294                                                         DPRINT_DBG(VMBUS, "invalid relid - %d", relid);
295                                                         continue;
296                                                 } else {
297                                                         /* QueueWorkItem(VmbusProcessEvent, (void*)relid); */
298                                                         /* ret = WorkQueueQueueWorkItem(gVmbusConnection.workQueue, VmbusProcessChannelEvent, (void*)relid); */
299                                                         VmbusProcessChannelEvent((void *)(unsigned long)relid);
300                                                 }
301                                         }
302                                 }
303                         }
304                  }
305         }
306         DPRINT_EXIT(VMBUS);
307
308         return;
309 }
310
311 /**
312  * VmbusPostMessage - Send a msg on the vmbus's message connection
313  */
314 int VmbusPostMessage(void *buffer, size_t bufferLen)
315 {
316         union hv_connection_id connId;
317
318         connId.Asu32 = 0;
319         connId.u.Id = VMBUS_MESSAGE_CONNECTION_ID;
320         return HvPostMessage(connId, 1, buffer, bufferLen);
321 }
322
323 /**
324  * VmbusSetEvent - Send an event notification to the parent
325  */
326 int VmbusSetEvent(u32 childRelId)
327 {
328         int ret = 0;
329
330         DPRINT_ENTER(VMBUS);
331
332         /* Each u32 represents 32 channels */
333         set_bit(childRelId & 31,
334                 (unsigned long *)gVmbusConnection.SendInterruptPage +
335                 (childRelId >> 5));
336
337         ret = HvSignalEvent();
338
339         DPRINT_EXIT(VMBUS);
340
341         return ret;
342 }