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