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