]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Linux Guest driver support for VMCI doorbell notifications
authorVMware, Inc <>
Mon, 26 Apr 2010 18:17:02 +0000 (11:17 -0700)
committerMarcelo Vanzin <mvanzin@vmware.com>
Tue, 27 Apr 2010 03:48:52 +0000 (20:48 -0700)
This change introduces three new functions to the VMCI guest
kernel API dealing with the HW v8 support for doorbell
notifications. The actual implementation of these doorbell
calls are embedded in a new notification module, that handles
allocating notification bitmap indexes,
registration/unregistration of callbacks for a given index,
and firing the handlers for a given index.

The Linux device initialization now opts in to use
notification, if the hardware supports it.

Signed-off-by: Marcelo Vanzin <mvanzin@vmware.com>
open-vm-tools/modules/linux/shared/vmciGuestKernelAPI.h
open-vm-tools/modules/linux/vmci/vmciNotifications.c [new file with mode: 0644]
open-vm-tools/modules/linux/vmci/vmciNotifications.h [new file with mode: 0644]
open-vm-tools/modules/linux/vmci/vmci_drv.c
open-vm-tools/modules/linux/vmci/vmci_version.h

index f005eca99398b2af625e097c8a86addaa98ab68e..352724d0315eba69cb8db6a33ccef122a756ae7b 100644 (file)
@@ -83,6 +83,22 @@ VMCIPrivilegeFlags VMCIContext_GetPrivFlags(VMCIId contextID); /* Compat */
 
 int VMCIDs_Lookup(const char *name, VMCIHandle *out);
 
+/* VMCI Doorbell API. */
+
+#if !defined(SOLARIS) && !defined(__APPLE__)
+
+#define VMCI_FLAG_DELAYED_CB    0x01
+
+typedef void (*VMCICallback)(void *clientData);
+
+int VMCIDoorbell_Create(VMCIHandle *handle, uint32 flags,
+                        VMCIPrivilegeFlags privFlags,
+                        VMCICallback notifyCB, void *clientData);
+int VMCIDoorbell_Destroy(VMCIHandle handle);
+int VMCIDoorbell_Notify(VMCIHandle handle,
+                        VMCIPrivilegeFlags privFlags);
+
+#endif // !defined(SOLARIS) && !defined(__APPLE__)
 
 #endif /* !__VMCI_GUESTKERNELAPI_H__ */
 
diff --git a/open-vm-tools/modules/linux/vmci/vmciNotifications.c b/open-vm-tools/modules/linux/vmci/vmciNotifications.c
new file mode 100644 (file)
index 0000000..c122881
--- /dev/null
@@ -0,0 +1,1044 @@
+/*********************************************************
+ * Copyright (C) 2010 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation version 2 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ *********************************************************/
+
+/*
+ * vmciNotifications.c --
+ *
+ *      Implementation of the VMCI notifications registration and
+ *      delivery, and the related doorbell API for the guest driver.
+ */
+
+#ifdef __linux__
+#  include "driver-config.h"
+#  include "compat_kernel.h"
+#  include "compat_module.h"
+#endif // linux
+
+#include "vmci_kernel_if.h"
+#include "vm_basic_types.h"
+#include "vm_assert.h"
+#include "vmciGuestKernelAPI.h"
+#include "vmci_defs.h"
+#include "vmci_call_defs.h"
+#include "vmciInt.h"
+#include "vmciUtil.h"
+#include "circList.h"
+
+/*
+ * The VMCI Notify hash table provides two mappings:
+ * 1) one maps a given notification index in the bitmap to the
+ *    entries, giving the set of handlers registered for that
+ *    index. This is mainly used for firing handlers for a given
+ *    bitmap index.
+ * 2) the other maps a handle and a resource (doorbell/queuepair)
+ *    to the entry (used to check for duplicates and delete the
+ *    entry)
+ */
+
+#define HASH_TABLE_SIZE 64
+#define VMCI_NOTIF_HASH(val) VMCI_HashId(val, HASH_TABLE_SIZE)
+
+typedef struct VMCINotifyHashEntry {
+   uint32         idx; // Bitmap index
+   VMCIHandle     handle;
+   Bool           doorbell;
+   Bool           runDelayed;
+   VMCICallback   notifyCB;
+   void          *callbackData;
+   VMCIEvent      destroyEvent;
+   int            refCount;
+   ListItem       handleListItem;
+   ListItem       idxListItem;
+} VMCINotifyHashEntry;
+
+typedef struct VMCINotifyHashTable {
+   VMCILock lock;
+   ListItem *entriesByIdx[HASH_TABLE_SIZE];
+   ListItem *entriesByHandle[HASH_TABLE_SIZE];
+} VMCINotifyHashTable;
+
+
+static int VMCINotifyHashAddEntry(VMCINotifyHashEntry *entry);
+static void VMCINotifyHashSetEntryCallback(VMCINotifyHashEntry *entry,
+                                           VMCICallback notifyCB);
+static VMCINotifyHashEntry *VMCINotifyHashRemoveEntry(VMCIHandle handle,
+                                                      Bool doorbell);
+static int VMCINotifyReleaseCB(void *clientData);
+static void VMCINotifyHashReleaseEntry(VMCINotifyHashEntry *entry);
+static VMCINotifyHashEntry *VMCINotifyHashFindByIdx(uint32 idx,
+                                                    uint32 *hashBucket);
+static VMCINotifyHashEntry *VMCINotifyHashFindByHandle(VMCIHandle handle,
+                                                       Bool doorbell,
+                                                       uint32 *hashBucket);
+
+static void VMCINotifyDelayedDispatchCB(void *data);
+static void VMCINotifyHashFireEntries(uint32 notifyIdx);
+
+static int LinkNotificationHypercall(VMCIHandle handle, Bool doorbell,
+                                     uint32 notifyIdx);
+static int UnlinkNotificationHypercall(VMCIHandle handle, Bool doorbell);
+
+
+/*
+ * The VMCI notify hash table keeps track of currently registered
+ * notifications.
+ */
+
+static VMCINotifyHashTable vmciNotifyHT;
+
+/*
+ * The maxNotifyIdx is one larger than the currently known bitmap
+ * index in use, and is used to determine how much of the bitmap needs
+ * to be scanned.
+ */
+
+static uint32 maxNotifyIdx;
+
+/*
+ * the notifyIdxCount is used for determining whether there are free
+ * entries within the bitmap (if notifyIdxCount + 1 < maxNotifyIdx).
+ */
+
+static uint32 notifyIdxCount;
+
+/*
+ * The lastNotifyIdxReserved is used to track the last index handed
+ * out - in the case where multiple handles share a notification
+ * index, we hand out indexes round robin based on
+ * lastNotifyIdxReserved.
+ */
+
+static uint32 lastNotifyIdxReserved;
+
+/*
+ * lastNotifyIdxReleased is a one entry cache used to by the index
+ * allocation.
+ */
+
+static uint32 lastNotifyIdxReleased = PAGE_SIZE;
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * VMCINotifications_Init --
+ *
+ *      General init code.
+ *
+ * Results:
+ *      None.
+ *
+ * Side effects:
+ *      None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+VMCINotifications_Init(void)
+{
+   memset(vmciNotifyHT.entriesByIdx, 0, ARRAY_SIZE(vmciNotifyHT.entriesByIdx));
+   memset(vmciNotifyHT.entriesByHandle, 0, ARRAY_SIZE(vmciNotifyHT.entriesByHandle));
+   VMCI_InitLock(&vmciNotifyHT.lock, "VMCINotifyHashLock",
+                 VMCI_LOCK_RANK_HIGHER_BH);
+   return;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * VMCINotifications_Exit --
+ *
+ *    General exit code.
+ *
+ * Results:
+ *    None.
+ *
+ * Side effects:
+ *    None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+VMCINotifications_Exit(void)
+{
+   uint32 bucket;
+   ListItem *iter, *iter2;
+
+   for (bucket = 0; bucket < HASH_TABLE_SIZE; bucket++) {
+      LIST_SCAN_SAFE(iter, iter2, vmciNotifyHT.entriesByIdx[bucket]) {
+         VMCINotifyHashEntry *cur;
+
+         /*
+          * We should never get here because all notifications should have been
+          * unregistered before we try to unload the driver module.
+          * Also, delayed callbacks could still be firing so this cleanup
+          * would not be safe.
+          * Still it is better to free the memory than not ... so we
+          * leave this code in just in case....
+          *
+          */
+         ASSERT(FALSE);
+
+         cur = LIST_CONTAINER(iter, VMCINotifyHashEntry, idxListItem);
+         VMCI_FreeKernelMem(cur, sizeof *cur);
+      }
+   }
+   VMCI_CleanupLock(&vmciNotifyHT.lock);
+
+}
+
+
+/*
+ *-------------------------------------------------------------------------
+ *
+ * VMCINotifyHashAddEntry --
+ *
+ *     Given a notification entry, adds it to the hashtable of
+ *     notifications.
+ *
+ * Result:
+ *     VMCI_SUCCESS on success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *     None.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static int
+VMCINotifyHashAddEntry(VMCINotifyHashEntry *entry) // IN
+{
+   VMCILockFlags flags;
+   uint32 bucket;
+   uint32 newNotifyIdx;
+   int result;
+   static VMCIId notifyRID = 0;
+
+   ASSERT(entry);
+
+   VMCI_GrabLock_BH(&vmciNotifyHT.lock, &flags);
+
+   if (VMCI_HANDLE_INVALID(entry->handle)) {
+      VMCIHandle newHandle;
+      VMCIId oldRID = notifyRID;
+      Bool found;
+
+      do {
+         newHandle = VMCI_MAKE_HANDLE(VMCI_GetContextID(), notifyRID);
+         notifyRID++;
+         found = VMCINotifyHashFindByHandle(newHandle, entry->doorbell, NULL) != NULL;
+      } while(found && oldRID != notifyRID);
+      if (UNLIKELY(found)) {
+         /*
+          * We went full circle and still didn't find a free handle.
+          */
+
+         result = VMCI_ERROR_NO_HANDLE;
+         goto out;
+      }
+      entry->handle = newHandle;
+   }
+
+   if (VMCINotifyHashFindByHandle(entry->handle, entry->doorbell, &bucket)) {
+      result = VMCI_ERROR_ALREADY_EXISTS;
+      goto out;
+   }
+   LIST_QUEUE(&entry->handleListItem, &vmciNotifyHT.entriesByHandle[bucket]);
+
+   /*
+    * Below we try to allocate an index in the notification bitmap
+    * with "not too much" sharing between resources. If we use less
+    * that the full bitmap, we either add to the end if there are no
+    * unused flags withing the currently used area, or we search for
+    * unused ones. If we use the full bitmap, we allocate the index
+    * round robin.
+    */
+
+   if (maxNotifyIdx < PAGE_SIZE || notifyIdxCount < PAGE_SIZE) {
+      if (lastNotifyIdxReleased < maxNotifyIdx &&
+          !VMCINotifyHashFindByIdx(lastNotifyIdxReleased, NULL)) {
+         newNotifyIdx = lastNotifyIdxReleased;
+         lastNotifyIdxReleased = PAGE_SIZE;
+      } else {
+         Bool reused = FALSE;
+         newNotifyIdx = lastNotifyIdxReserved;
+         if (notifyIdxCount + 1 < maxNotifyIdx) {
+            do {
+               if (!VMCINotifyHashFindByIdx(newNotifyIdx, NULL)) {
+                  reused = TRUE;
+                  break;
+               }
+               newNotifyIdx = (newNotifyIdx + 1) % maxNotifyIdx;
+            } while(newNotifyIdx != lastNotifyIdxReleased);
+         }
+         if (!reused) {
+            newNotifyIdx = maxNotifyIdx;
+            maxNotifyIdx++;
+         }
+      }
+   } else {
+      newNotifyIdx = (lastNotifyIdxReserved + 1) % PAGE_SIZE;
+   }
+   lastNotifyIdxReserved = newNotifyIdx;
+   notifyIdxCount++;
+
+   bucket = VMCI_NOTIF_HASH(newNotifyIdx);
+   entry->refCount++;
+   entry->idx = newNotifyIdx;
+   LIST_QUEUE(&entry->idxListItem, &vmciNotifyHT.entriesByIdx[bucket]);
+   result = VMCI_SUCCESS;
+out:
+   VMCI_ReleaseLock_BH(&vmciNotifyHT.lock, flags);
+   return result;
+}
+
+
+/*
+ *-------------------------------------------------------------------------
+ *
+ * VMCINotifyHashSetEntryCallback --
+ *
+ *     Sets the notify callback of the given entry. Once the callback
+ *     has been set, it may start firing.
+ *
+ * Result:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static void
+VMCINotifyHashSetEntryCallback(VMCINotifyHashEntry *entry, // IN
+                               VMCICallback notifyCB)      // IN
+{
+   VMCILockFlags flags;
+
+   ASSERT(entry && notifyCB);
+
+   VMCI_GrabLock_BH(&vmciNotifyHT.lock, &flags);
+   entry->notifyCB = notifyCB;
+   VMCI_ReleaseLock_BH(&vmciNotifyHT.lock, flags);
+}
+
+
+/*
+ *-------------------------------------------------------------------------
+ *
+ * VMCINotifyHashRemoveEntry --
+ *
+ *     Removes the entry identified by the handle of the given
+ *     resource type from the hash table.
+ *
+ * Result:
+ *     Pointer to entry if removed, NULL if not found.
+ *
+ * Side effects:
+ *     None.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static VMCINotifyHashEntry *
+VMCINotifyHashRemoveEntry(VMCIHandle handle, // IN
+                          Bool doorbell)     // IN
+{
+   VMCILockFlags flags;
+   VMCINotifyHashEntry *entry;
+   uint32 bucket;
+
+   VMCI_GrabLock_BH(&vmciNotifyHT.lock, &flags);
+   entry = VMCINotifyHashFindByHandle(handle, doorbell, &bucket);
+   if (entry) {
+      ASSERT(entry->refCount > 0);
+      LIST_DEL(&entry->handleListItem, &vmciNotifyHT.entriesByHandle[bucket]);
+      bucket = VMCI_NOTIF_HASH(entry->idx);
+      LIST_DEL(&entry->idxListItem, &vmciNotifyHT.entriesByIdx[bucket]);
+      notifyIdxCount--;
+      if (entry->idx == maxNotifyIdx - 1) {
+         /*
+          * If we delete an entry with the maximum known notification
+          * index, we take the opportunity to prune the current
+          * max. As there might be other unused indices immediately
+          * below, we lower the maximum until we hit an index in use.
+          */
+
+         while (maxNotifyIdx > 0 &&
+                !VMCINotifyHashFindByIdx(maxNotifyIdx - 1, NULL)) {
+            maxNotifyIdx--;
+         }
+      }
+      lastNotifyIdxReleased = entry->idx;
+   }
+   VMCI_ReleaseLock_BH(&vmciNotifyHT.lock, flags);
+
+   return entry;
+}
+
+
+/*
+ *------------------------------------------------------------------------------
+ *
+ * VMCINotifyReleaseCB --
+ *
+ *     Callback to release the notification entry reference. It is
+ *     called by the VMCI_WaitOnEvent function before it blocks.
+ *
+ * Result:
+ *     0.
+ *
+ * Side effects:
+ *     Releases hash entry (see below).
+ *
+ *------------------------------------------------------------------------------
+ */
+
+static int
+VMCINotifyReleaseCB(void *clientData) // IN
+{
+   VMCINotifyHashEntry *entry = (VMCINotifyHashEntry *)clientData;
+   ASSERT(entry);
+   VMCINotifyHashReleaseEntry(entry);
+
+   return 0;
+}
+
+
+/*
+ *-------------------------------------------------------------------------
+ *
+ * VMCINotifyHashReleaseEntry --
+ *
+ *     Drops a reference to the current hash entry. If this is the last
+ *     reference then the entry is freed.
+ *
+ * Result:
+ *     None.
+ *
+ * Side effects:
+ *     May signal event.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static void
+VMCINotifyHashReleaseEntry(VMCINotifyHashEntry *entry) // IN
+{
+   VMCILockFlags flags;
+
+   VMCI_GrabLock_BH(&vmciNotifyHT.lock, &flags);
+   entry->refCount--;
+
+   /*
+    * Check if this is last reference and signal the destroy event if
+    * so.
+    */
+
+   if (entry->refCount == 0) {
+      VMCI_SignalEvent(&entry->destroyEvent);
+   }
+   VMCI_ReleaseLock_BH(&vmciNotifyHT.lock, flags);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCINotifyHashFindByIdx --
+ *
+ *    Find hash entry by bitmap index. Assumes lock is
+ *    held. Regardless of whether an entry was found, the bucket that
+ *    the entry would have been in is returned in hashBucket (if
+ *    valid).
+ *
+ * Results:
+ *    Entry if found, NULL if not.
+ *
+ * Side effects:
+ *    None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static VMCINotifyHashEntry *
+VMCINotifyHashFindByIdx(uint32 idx,         // IN
+                        uint32 *hashBucket) // IN/OUT: hash value for idx
+{
+   ListItem *iter;
+   uint32 bucket = VMCI_NOTIF_HASH(idx);
+
+   if (hashBucket) {
+      *hashBucket = bucket;
+   }
+
+   LIST_SCAN(iter, vmciNotifyHT.entriesByIdx[bucket]) {
+      VMCINotifyHashEntry *cur =
+         LIST_CONTAINER(iter, VMCINotifyHashEntry, idxListItem);
+      if (cur->idx == idx) {
+         return cur;
+      }
+   }
+   return NULL;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCINotifyHashFindByHandle --
+ *
+ *    Find hash entry by handle and resoruce. Assumes lock is
+ *    held. Regardless of whether an entry was found, the bucket that
+ *    the entry would have been in is returned in hashBucket (if
+ *    valid).
+ *
+ * Results:
+ *    Entry if found, NULL if not.
+ *
+ * Side effects:
+ *    None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static VMCINotifyHashEntry *
+VMCINotifyHashFindByHandle(VMCIHandle handle,  // IN
+                           Bool doorbell,      // IN
+                           uint32 *hashBucket) // IN/OUT: hash value for handle
+{
+   ListItem *iter;
+   uint32 bucket = VMCI_NOTIF_HASH(handle.resource);
+
+   if (hashBucket) {
+      *hashBucket = bucket;
+   }
+
+   LIST_SCAN(iter, vmciNotifyHT.entriesByHandle[bucket]) {
+      VMCINotifyHashEntry *cur =
+         LIST_CONTAINER(iter, VMCINotifyHashEntry, handleListItem);
+      if (VMCI_HANDLE_EQUAL(cur->handle, handle) && cur->doorbell == doorbell) {
+         return cur;
+      }
+   }
+   return NULL;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCINotifyDelayedDispatchCB --
+ *
+ *      Calls the specified callback in a delayed context.
+ *
+ * Results:
+ *      None.
+ *
+ * Side effects:
+ *      None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+VMCINotifyDelayedDispatchCB(void *data) // IN
+{
+   VMCINotifyHashEntry *entry = (VMCINotifyHashEntry *)data;
+
+   ASSERT(data);
+
+   entry->notifyCB(entry->callbackData);
+
+   VMCINotifyHashReleaseEntry(entry);
+}
+
+
+/*
+ *-------------------------------------------------------------------------
+ *
+ * VMCINotifyHashFireEntries --
+ *
+ *     Executes or schedules the handlers for a given notify index.
+ *
+ * Result:
+ *     Notification hash entry if found. NULL otherwise.
+ *
+ * Side effects:
+ *     Whatever the side effects of the handlers are.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+static void
+VMCINotifyHashFireEntries(uint32 notifyIdx) // IN
+{
+   VMCILockFlags flags;
+   ListItem *iter;
+   int bucket = VMCI_NOTIF_HASH(notifyIdx);
+
+   VMCI_GrabLock_BH(&vmciNotifyHT.lock, &flags);
+
+   LIST_SCAN(iter, vmciNotifyHT.entriesByIdx[bucket]) {
+      VMCINotifyHashEntry *cur = LIST_CONTAINER(iter, VMCINotifyHashEntry,
+                                                idxListItem);
+      if (cur->idx == notifyIdx && cur->notifyCB) {
+         if (cur->runDelayed) {
+            int err;
+
+            cur->refCount++;
+            err = VMCI_ScheduleDelayedWork(VMCINotifyDelayedDispatchCB, cur);
+            if (err != VMCI_SUCCESS) {
+               cur->refCount--;
+               goto out;
+            }
+         } else {
+            cur->notifyCB(cur->callbackData);
+         }
+      }
+   }
+out:
+   VMCI_ReleaseLock_BH(&vmciNotifyHT.lock, flags);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinkNotificationHypercall --
+ *
+ *    Creates a link between the given doorbell handle and the given
+ *    index in the bitmap in the device backend.
+ *
+ * Results:
+ *    VMCI_SUCCESS if success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *    Notification state is created in hypervisor.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+LinkNotificationHypercall(VMCIHandle handle,  // IN
+                          Bool doorbell,      // IN
+                          uint32 notifyIdx)   // IN
+{
+   VMCIDoorbellLinkMsg linkMsg;
+   int result;
+   VMCIId resourceID;
+
+   ASSERT(!VMCI_HANDLE_INVALID(handle));
+
+   if (doorbell) {
+      resourceID = VMCI_DOORBELL_LINK;
+   } else {
+      ASSERT(FALSE);
+      return VMCI_ERROR_UNAVAILABLE;
+   }
+
+   linkMsg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, resourceID);
+   linkMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
+   linkMsg.hdr.payloadSize = sizeof linkMsg - VMCI_DG_HEADERSIZE;
+   linkMsg.handle = handle;
+   linkMsg.notifyIdx = notifyIdx;
+
+   result = VMCI_SendDatagram((VMCIDatagram *)&linkMsg);
+
+   return result;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UnlinkNotificationHypercall --
+ *
+ *    Unlinks the given doorbell handle from an index in the bitmap in
+ *    the device backend.
+ *
+ * Results:
+ *      VMCI_SUCCESS if success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *      Notification state is destroyed in hypervisor.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+UnlinkNotificationHypercall(VMCIHandle handle, // IN
+                            Bool doorbell)     // IN
+{
+   VMCIDoorbellUnlinkMsg unlinkMsg;
+   int result;
+   VMCIId resourceID;
+
+   ASSERT(!VMCI_HANDLE_INVALID(handle));
+
+   if (doorbell) {
+      resourceID = VMCI_DOORBELL_UNLINK;
+   } else {
+      ASSERT(FALSE);
+      return VMCI_ERROR_UNAVAILABLE;
+   }
+
+   unlinkMsg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, resourceID);
+   unlinkMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
+   unlinkMsg.hdr.payloadSize = sizeof unlinkMsg - VMCI_DG_HEADERSIZE;
+   unlinkMsg.handle = handle;
+
+   result = VMCI_SendDatagram((VMCIDatagram *)&unlinkMsg);
+
+   return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCINotificationRegister --
+ *
+ *      Links a resource with an index in the notification bitmap.
+ *
+ * Results:
+ *      VMCI_SUCCESS on success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *      Notification state is created both in guest and on host.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCINotificationRegister(VMCIHandle *handle,     // IN
+                         Bool doorbell,          // IN
+                         uint32 flags,           // IN
+                         VMCICallback notifyCB,  // IN
+                         void *callbackData)     // IN
+{
+   int result;
+   VMCINotifyHashEntry *entry;
+
+   if (!notifyCB || !handle) {
+      return VMCI_ERROR_INVALID_ARGS;
+   }
+
+   entry = VMCI_AllocKernelMem(sizeof *entry, VMCI_MEMORY_NONPAGED);
+   if (entry == NULL) {
+      return VMCI_ERROR_NO_MEM;
+   }
+
+   entry->runDelayed = (flags & VMCI_FLAG_DELAYED_CB) ? TRUE : FALSE;
+   if (entry->runDelayed && !VMCI_CanScheduleDelayedWork()) {
+      VMCI_FreeKernelMem(entry, sizeof *entry);
+      return VMCI_ERROR_INVALID_ARGS;
+   }
+
+   /*
+    * Reserve an index in the notification bitmap.
+    */
+
+   entry->handle = *handle;
+   entry->doorbell = doorbell;
+   entry->notifyCB = NULL; // Wait with this until link is established in hypervisor
+   entry->callbackData = callbackData;
+   entry->refCount = 0;
+   result = VMCINotifyHashAddEntry(entry);
+   if (result != VMCI_SUCCESS) {
+      VMCI_FreeKernelMem(entry, sizeof *entry);
+      return result;
+   }
+
+   VMCI_CreateEvent(&entry->destroyEvent);
+
+   result = LinkNotificationHypercall(entry->handle, doorbell, entry->idx);
+   if (result != VMCI_SUCCESS) {
+      VMCI_LOG(("Failed to link handle 0x%x:0x%x of resource %s to index, "
+                "err 0x%x.\n", entry->handle.context, entry->handle.resource,
+                entry->doorbell ? "doorbell" : "queue pair", result));
+      VMCINotifyHashRemoveEntry(entry->handle, entry->doorbell);
+      VMCI_DestroyEvent(&entry->destroyEvent);
+      VMCI_FreeKernelMem(entry, sizeof *entry);
+   } else {
+      /*
+       * When the handle is set, the notification callback may start
+       * to fire. Since flags in the notification bitmap can be
+       * shared, a given callback may fire immediately.
+       */
+
+      VMCINotifyHashSetEntryCallback(entry, notifyCB);
+      *handle = entry->handle;
+   }
+
+   return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCINotificationUnregister --
+ *
+ *      Unregisters a notification previously created through
+ *      VMCINotificationRegister. This function may block. The call
+ *      always succeeds if the notification exists.
+ *
+ * Results:
+ *      VMCI_SUCCESS if success, VMCI_ERROR_NOT_FOUND otherwise.
+ *
+ * Side effects:
+ *      Notification state is destroyed both in guest and hypervisor.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCINotificationUnregister(VMCIHandle handle, // IN
+                           Bool doorbell)     // IN
+{
+   VMCINotifyHashEntry *entry;
+   int result;
+
+   entry = VMCINotifyHashRemoveEntry(handle, doorbell);
+   if (!entry) {
+      ASSERT(FALSE);
+      return VMCI_ERROR_NOT_FOUND;
+   }
+
+   VMCI_WaitOnEvent(&entry->destroyEvent, VMCINotifyReleaseCB, entry);
+   VMCI_DestroyEvent(&entry->destroyEvent);
+   VMCI_FreeKernelMem(entry, sizeof *entry);
+
+   result = UnlinkNotificationHypercall(handle, doorbell);
+   if (result != VMCI_SUCCESS) {
+      /*
+       * The only reason this should fail would be an inconsistency
+       * between guest and hypervisor state, where the guest believes
+       * it has an active registration whereas the hypervisor
+       * doesn't. Since the handle has now been removed in the guest,
+       * we just print a warning and return success.
+       */
+
+      ASSERT(FALSE);
+
+      VMCI_LOG(("Unlink of %s  handle 0x%x:0x%x unknown by hypervisor.\n",
+                doorbell ? "doorbell" : "queuepair",
+                handle.context, handle.resource));
+   }
+   return VMCI_SUCCESS;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCI_RegisterNotificationBitmap --
+ *
+ *      Verify that the host supports the hypercalls we need. If it does not,
+ *      try to find fallback hypercalls and use those instead.
+ *
+ * Results:
+ *      TRUE if the bitmap is registered successfully with the device, FALSE
+ *      otherwise.
+ *
+ * Side effects:
+ *      None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+VMCI_RegisterNotificationBitmap(PPN bitmapPPN) // IN
+{
+   VMCINotifyBitmapSetMsg bitmapSetMsg;
+   int result;
+
+   bitmapSetMsg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID,
+                                           VMCI_SET_NOTIFY_BITMAP);
+   bitmapSetMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
+   bitmapSetMsg.hdr.payloadSize = sizeof bitmapSetMsg - VMCI_DG_HEADERSIZE;
+   bitmapSetMsg.bitmapPPN = bitmapPPN;
+
+   result = VMCI_SendDatagram((VMCIDatagram *)&bitmapSetMsg);
+   if (result != VMCI_SUCCESS) {
+      VMCI_LOG(("VMCINotifications: Failed to register PPN %u as notification "
+                "bitmap (error : %d).\n", bitmapPPN, result));
+      return FALSE;
+   }
+   return TRUE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCI_ScanNotificationBitmap --
+ *
+ *      Scans the notification bitmap, collects pending notifications,
+ *      resets the bitmap and invokes appropriate callbacks.
+ *
+ * Results:
+ *      None.
+ *
+ * Side effects:
+ *      May schedule tasks, allocate memory and run callbacks.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+VMCI_ScanNotificationBitmap(uint8 *bitmap) // IN
+{
+   size_t idx;
+
+   for (idx = 0; idx < maxNotifyIdx; idx++) {
+      if (bitmap[idx] & 0x1) {
+         bitmap[idx] &= ~1;
+         VMCINotifyHashFireEntries(idx);
+      }
+   }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIDoorbell_Create --
+ *
+ *      Creates a doorbell with the given callback. If the handle is
+ *      VMCI_INVALID_HANDLE, a free handle will be assigned, if
+ *      possible. The callback can be run in interrupt context (the
+ *      default) or delayed (in a kernel thread) by specifying the
+ *      flag VMCI_FLAG_DELAYED_CB. If delayed execution is selected, a
+ *      given callback may not be run if the kernel is unable to
+ *      allocate memory for the delayed execution (highly unlikely).
+ *
+ * Results:
+ *      VMCI_SUCCESS on success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *      None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIDoorbell_Create(VMCIHandle *handle,            // IN
+                    uint32 flags,                  // IN
+                    VMCIPrivilegeFlags privFlags,  // IN: Unused in guest
+                    VMCICallback notifyCB,         // IN
+                    void *clientData)              // IN
+{
+   if (!handle || !notifyCB || flags & ~VMCI_FLAG_DELAYED_CB) {
+      return VMCI_ERROR_INVALID_ARGS;
+   }
+   if (privFlags & ~VMCI_LEAST_PRIVILEGE_FLAGS) {
+      return VMCI_ERROR_NO_ACCESS;
+   }
+
+   return VMCINotificationRegister(handle, TRUE, flags,
+                                   notifyCB, clientData);
+}
+
+#if defined(__linux__)
+EXPORT_SYMBOL(VMCIDoorbell_Create);
+#endif
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIDoorbell_Destroy --
+ *
+ *      Destroys a doorbell previously created with
+ *      VMCIDoorbell_Create. This operation may block waiting for a
+ *      callback to finish.
+ *
+ * Results:
+ *      VMCI_SUCCESS on success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *      May block.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIDoorbell_Destroy(VMCIHandle handle) // IN
+{
+   if (VMCI_HANDLE_INVALID(handle)) {
+      return VMCI_ERROR_INVALID_ARGS;
+   }
+   return VMCINotificationUnregister(handle, TRUE);
+}
+
+#if defined(__linux__)
+EXPORT_SYMBOL(VMCIDoorbell_Destroy);
+#endif
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIDoorbell_Notify --
+ *
+ *      Generates a notification on the doorbell identified by the
+ *      handle.
+ *
+ * Results:
+ *      VMCI_SUCCESS on success, appropriate error code otherwise.
+ *
+ * Side effects:
+ *      May do a hypercall.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCIDoorbell_Notify(VMCIHandle handle,             // IN
+                    VMCIPrivilegeFlags privFlags)  // IN: Unused in guest
+{
+   VMCIDoorbellNotifyMsg notifyMsg;
+   int result;
+
+   if (VMCI_HANDLE_INVALID(handle)) {
+      return VMCI_ERROR_INVALID_ARGS;
+   }
+
+   if (privFlags & ~VMCI_LEAST_PRIVILEGE_FLAGS) {
+      return VMCI_ERROR_NO_ACCESS;
+   }
+
+   notifyMsg.hdr.dst = VMCI_MAKE_HANDLE(VMCI_HYPERVISOR_CONTEXT_ID, VMCI_DOORBELL_NOTIFY);
+   notifyMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
+   notifyMsg.hdr.payloadSize = sizeof notifyMsg - VMCI_DG_HEADERSIZE;
+   notifyMsg.handle = handle;
+
+   result = VMCI_SendDatagram((VMCIDatagram *)&notifyMsg);
+
+   return result;
+}
+
+#if defined(__linux__)
+EXPORT_SYMBOL(VMCIDoorbell_Notify);
+#endif
diff --git a/open-vm-tools/modules/linux/vmci/vmciNotifications.h b/open-vm-tools/modules/linux/vmci/vmciNotifications.h
new file mode 100644 (file)
index 0000000..940ec55
--- /dev/null
@@ -0,0 +1,39 @@
+/*********************************************************
+ * Copyright (C) 2010 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation version 2 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ *********************************************************/
+
+/*
+ * vmciNotifications.h --
+ *
+ *     VMCI notifications API for OS device drivers.
+ */
+
+#ifndef _VMCI_NOTIFICATIONS_H_
+#define _VMCI_NOTIFICATIONS_H_
+
+void VMCINotifications_Init(void);
+void VMCINotifications_Exit(void);
+
+Bool VMCI_RegisterNotificationBitmap(PPN bitmapPPN);
+void VMCI_ScanNotificationBitmap(uint8 *bitmap);
+
+int VMCINotificationRegister(VMCIHandle *handle, Bool doorbell, uint32 flags,
+                             VMCICallback notifyCB, void *callbackData);
+int VMCINotificationUnregister(VMCIHandle handle, Bool doorbell);
+
+#endif /* !_VMCI_NOTIFICATIONS_H_ */
+
index 18adac0a8c2d9213c738c197d8bbd18b3e96f9d0..b6e49bcfca0b623577050f0d71c290b3ced97b4b 100644 (file)
@@ -46,6 +46,7 @@
 #include "vmciProcess.h"
 #include "vmciUtil.h"
 #include "vmciEvent.h"
+#include "vmciNotifications.h"
 #include "vmciQueuePairInt.h"
 #include "vmci_version.h"
 #include "vmciCommonInt.h"
@@ -79,6 +80,7 @@ static compat_irqreturn_t vmci_interrupt(int irq, void *dev_id,
 static compat_irqreturn_t vmci_interrupt(int irq, void *dev_id);
 #endif
 static void dispatch_datagrams(unsigned long data);
+static void process_bitmap(unsigned long data);
 
 static const struct pci_device_id vmci_ids[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_VMCI), },
@@ -105,7 +107,10 @@ static vmci_device vmci_dev;
 /* We dynamically request the device major number at init time. */
 static int device_major_nr = 0;
 
-DECLARE_TASKLET(vmci_tasklet, dispatch_datagrams,
+DECLARE_TASKLET(vmci_dg_tasklet, dispatch_datagrams,
+                (unsigned long)&vmci_dev);
+
+DECLARE_TASKLET(vmci_bm_tasklet, process_bitmap,
                 (unsigned long)&vmci_dev);
 
 /*
@@ -116,6 +121,13 @@ DECLARE_TASKLET(vmci_tasklet, dispatch_datagrams,
 static uint8 *data_buffer = NULL;
 static uint32 data_buffer_size = VMCI_MAX_DG_SIZE;
 
+/*
+ * If the VMCI hardware supports the notification bitmap, we allocate
+ * and register a page with the device.
+ */
+
+static uint8 *notification_bitmap = NULL;
+
 
 /*
  *-----------------------------------------------------------------------------
@@ -266,8 +278,26 @@ vmci_probe_device(struct pci_dev *pdev,           // IN: vmci PCI device
       goto release;
    }
 
+   /*
+    * If the hardware supports notifications, we will use that as
+    * well.
+    */
+   if (capabilities & VMCI_CAPS_NOTIFICATIONS) {
+      capabilities = VMCI_CAPS_DATAGRAM;
+      notification_bitmap = vmalloc(PAGE_SIZE);
+      if (notification_bitmap == NULL) {
+         printk(KERN_ERR "VMCI device unable to allocate notification bitmap.\n");
+      } else {
+         memset(notification_bitmap, 0, PAGE_SIZE);
+         capabilities |= VMCI_CAPS_NOTIFICATIONS;
+      }
+   } else {
+      capabilities = VMCI_CAPS_DATAGRAM;
+   }
+   printk(KERN_INFO "VMCI: using capabilities 0x%x.\n", capabilities);
+
    /* Let the host know which capabilities we intend to use. */
-   outl(VMCI_CAPS_DATAGRAM, ioaddr + VMCI_CAPS_ADDR);
+   outl(capabilities, ioaddr + VMCI_CAPS_ADDR);
 
    /* Device struct initialization. */
    compat_mutex_lock(&vmci_dev.lock);
@@ -280,9 +310,23 @@ vmci_probe_device(struct pci_dev *pdev,           // IN: vmci PCI device
    vmci_dev.ioaddr_size = ioaddr_size;
    vmci_dev.irq = pdev->irq;
 
+   /*
+    * Register notification bitmap with device if that capability is
+    * used
+    */
+   if (capabilities & VMCI_CAPS_NOTIFICATIONS) {
+      unsigned long bitmapPPN;
+      bitmapPPN = page_to_pfn(vmalloc_to_page(notification_bitmap));
+      if (!VMCI_RegisterNotificationBitmap(bitmapPPN)) {
+         printk(KERN_ERR "VMCI device unable to register notification bitmap "
+                "with PPN 0x%x.\n", (uint32)bitmapPPN);
+         goto unlock;
+      }
+   }
+
    /* Check host capabilities. */
    if (!VMCI_CheckHostCapabilities()) {
-      goto unlock;
+      goto remove_bitmap;
    }
 
    /* Enable device. */
@@ -303,6 +347,7 @@ vmci_probe_device(struct pci_dev *pdev,           // IN: vmci PCI device
    VMCIDatagram_Init();
    VMCIEvent_Init();
    VMCIUtil_Init();
+   VMCINotifications_Init();
    VMCIQueuePair_Init();
 
    if (request_irq(vmci_dev.irq, vmci_interrupt, COMPAT_IRQF_SHARED,
@@ -316,7 +361,12 @@ vmci_probe_device(struct pci_dev *pdev,           // IN: vmci PCI device
    compat_mutex_unlock(&vmci_dev.lock);
 
    /* Enable specific interrupt bits. */
-   outl(VMCI_IMR_DATAGRAM, vmci_dev.ioaddr + VMCI_IMR_ADDR);
+   if (capabilities & VMCI_CAPS_NOTIFICATIONS) {
+      outl(VMCI_IMR_DATAGRAM | VMCI_IMR_NOTIFICATION,
+           vmci_dev.ioaddr + VMCI_IMR_ADDR);
+   } else {
+      outl(VMCI_IMR_DATAGRAM, vmci_dev.ioaddr + VMCI_IMR_ADDR);
+   }
 
    /* Enable interrupts. */
    outl(VMCI_CONTROL_INT_ENABLE, vmci_dev.ioaddr + VMCI_CONTROL_ADDR);
@@ -325,12 +375,20 @@ vmci_probe_device(struct pci_dev *pdev,           // IN: vmci PCI device
 
  components_exit:
    VMCIQueuePair_Exit();
+   VMCINotifications_Exit();
    VMCIUtil_Exit();
    VMCIEvent_Exit();
    VMCIProcess_Exit();
+ remove_bitmap:
+   if (notification_bitmap) {
+      outl(VMCI_CONTROL_RESET, vmci_dev.ioaddr + VMCI_CONTROL_ADDR);
+   }
  unlock:
    compat_mutex_unlock(&vmci_dev.lock);
  release:
+   if (notification_bitmap) {
+      vfree(notification_bitmap);
+   }
    release_region(ioaddr, ioaddr_size);
  pci_disable:
    compat_pci_disable_device(pdev);
@@ -375,6 +433,10 @@ vmci_remove_device(struct pci_dev* pdev)
    free_irq(dev->irq, dev);
    release_region(dev->ioaddr, dev->ioaddr_size);
    dev->enabled = FALSE;
+   VMCINotifications_Exit();
+   if (notification_bitmap) {
+      vfree(notification_bitmap);
+   }
 
    printk(KERN_INFO "Unregistered vmci device.\n");
    compat_mutex_unlock(&dev->lock);
@@ -658,7 +720,7 @@ vmci_ioctl(struct inode *inode,  // IN
       retval = copy_to_user((void *)arg, &cid, sizeof cid);
       break;
    }
-
    default:
       printk(KERN_DEBUG "vmci_ioctl(): unknown ioctl 0x%x.\n", cmd);
       retval = -EINVAL;
@@ -759,14 +821,18 @@ vmci_interrupt(int irq,               // IN
 
    /* Acknowledge interrupt and determine what needs doing. */
    icr = inl(dev->ioaddr + VMCI_ICR_ADDR);
-   if (icr == 0) {
+   if (icr == 0 || icr == 0xffffffff) {
       return COMPAT_IRQ_NONE;
    }
 
    if (icr & VMCI_ICR_DATAGRAM) {
-      tasklet_schedule(&vmci_tasklet);
+      tasklet_schedule(&vmci_dg_tasklet);
       icr &= ~VMCI_ICR_DATAGRAM;
    }
+   if (icr & VMCI_ICR_NOTIFICATION) {
+      tasklet_schedule(&vmci_bm_tasklet);
+      icr &= ~VMCI_ICR_NOTIFICATION;
+   }
    if (icr != 0) {
       printk(KERN_INFO LGPFX"Ignoring unknown interrupt cause (%d).\n", icr);
    }
@@ -902,6 +968,44 @@ dispatch_datagrams(unsigned long data)
 }
 
 
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * process_bitmap --
+ *
+ *      Scans the notification bitmap for raised flags, clears them
+ *      and handles the notifications.
+ *
+ * Results:
+ *      None.
+ *
+ * Side effects:
+ *      None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+process_bitmap(unsigned long data)
+{
+   vmci_device *dev = (vmci_device *)data;
+
+   if (dev == NULL) {
+      printk(KERN_DEBUG "vmci: process_bitmaps(): no vmci device"
+            "present.\n");
+      return;
+   }
+
+   if (notification_bitmap == NULL) {
+      printk(KERN_DEBUG "vmci: process_bitmaps(): no bitmap present.\n");
+      return;
+   }
+
+
+   VMCI_ScanNotificationBitmap(notification_bitmap);
+}
+
+
 module_init(vmci_init);
 module_exit(vmci_exit);
 MODULE_DEVICE_TABLE(pci, vmci_ids);
index 8bdbf474e3ba3fad57e14231cc565b02dc4c0c8d..7778494003e7358b61bb1e40135df40c7c560120 100644 (file)
@@ -25,8 +25,8 @@
 #ifndef _VMCI_VERSION_H_
 #define _VMCI_VERSION_H_
 
-#define VMCI_DRIVER_VERSION          1.0.20.0
-#define VMCI_DRIVER_VERSION_COMMAS   1,0,20,0
-#define VMCI_DRIVER_VERSION_STRING   "1.0.20.0"
+#define VMCI_DRIVER_VERSION          1.0.21.0
+#define VMCI_DRIVER_VERSION_COMMAS   1,0,21,0
+#define VMCI_DRIVER_VERSION_STRING   "1.0.21.0"
 
 #endif /* _VMCI_VERSION_H_ */