linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 03/11] vmci_doorbell.patch: VMCI doorbell notification handling.
@ 2012-08-30 16:40 George Zhang
  0 siblings, 0 replies; only message in thread
From: George Zhang @ 2012-08-30 16:40 UTC (permalink / raw)
  To: linux-kernel, virtualization; +Cc: gregkh



Signed-off-by: George Zhang <georgezhang@vmware.com>
---
 drivers/misc/vmw_vmci/vmci_doorbell.c |  749 +++++++++++++++++++++++++++++++++
 drivers/misc/vmw_vmci/vmci_doorbell.h |   54 ++
 2 files changed, 803 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/vmw_vmci/vmci_doorbell.c
 create mode 100644 drivers/misc/vmw_vmci/vmci_doorbell.h

diff --git a/drivers/misc/vmw_vmci/vmci_doorbell.c b/drivers/misc/vmw_vmci/vmci_doorbell.c
new file mode 100644
index 0000000..d67bfc7
--- /dev/null
+++ b/drivers/misc/vmw_vmci/vmci_doorbell.c
@@ -0,0 +1,749 @@
+/*
+ * VMware VMCI Driver
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#include <linux/vmw_vmci_defs.h>
+#include <linux/vmw_vmci_api.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "vmci_common_int.h"
+#include "vmci_datagram.h"
+#include "vmci_doorbell.h"
+#include "vmci_resource.h"
+#include "vmci_driver.h"
+#include "vmci_route.h"
+
+#define VMCI_DOORBELL_INDEX_TABLE_SIZE (1 << 6)
+#define VMCI_DOORBELL_HASH(_idx)                               \
+       vmci_hash_calc((_idx), VMCI_DOORBELL_INDEX_TABLE_SIZE)
+
+/*
+ * DoorbellEntry describes the a doorbell notification handle allocated by the
+ * host.
+ */
+struct dbell_entry {
+       struct vmci_resource resource;
+       uint32_t idx;
+       struct list_head idxListItem;
+       uint32_t privFlags;
+       bool runDelayed;
+       vmci_callback notifyCB;
+       void *clientData;
+       wait_queue_head_t destroyEvent;
+       atomic_t active;        /* Only used by guest personality */
+};
+
+/* The VMCI index table keeps track of currently registered doorbells. */
+static struct dbell_index_table {
+       spinlock_t lock; /* Index table lock */
+       struct list_head entries[VMCI_DOORBELL_INDEX_TABLE_SIZE];
+} vmciDoorbellIT;
+
+/*
+ * 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_t maxNotifyIdx;
+
+/*
+ * The notifyIdxCount is used for determining whether there are free entries
+ * within the bitmap (if notifyIdxCount + 1 < maxNotifyIdx).
+ */
+static uint32_t 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_t lastNotifyIdxReserved;
+
+/* This is a one entry cache used to by the index allocation. */
+static uint32_t lastNotifyIdxReleased = PAGE_SIZE;
+
+/*
+ * General init code.
+ */
+int __init vmci_dbell_init(void)
+{
+       uint32_t bucket;
+
+       for (bucket = 0; bucket < ARRAY_SIZE(vmciDoorbellIT.entries); ++bucket)
+               INIT_LIST_HEAD(&vmciDoorbellIT.entries[bucket]);
+
+       spin_lock_init(&vmciDoorbellIT.lock);
+       return VMCI_SUCCESS;
+}
+
+/*
+ * Callback to free doorbell entry structure when resource is no longer used,
+ * The entry is freed in VMCIDoorbell_Destroy(), which is waiting on the
+ * signal that gets fired here.
+ */
+static void dbell_free_cb(void *clientData)
+{
+       struct dbell_entry *entry = (struct dbell_entry *)clientData;
+       ASSERT(entry);
+       wake_up(&entry->destroyEvent);
+}
+
+static int dbell_release_cb(void *clientData)
+{
+       struct dbell_entry *entry = (struct dbell_entry *)clientData;
+       ASSERT(entry);
+       vmci_resource_release(&entry->resource);
+       return 0;
+}
+
+/*
+ * Utility function that retrieves the privilege flags associated
+ * with a given doorbell handle. For guest endpoints, the
+ * privileges are determined by the context ID, but for host
+ * endpoints privileges are associated with the complete
+ * handle. Hypervisor endpoints are not yet supported.
+ */
+int vmci_dbell_get_priv_flags(struct vmci_handle handle,
+                             uint32_t *privFlags)
+{
+       if (privFlags == NULL || handle.context == VMCI_INVALID_ID)
+               return VMCI_ERROR_INVALID_ARGS;
+
+       if (handle.context == VMCI_HOST_CONTEXT_ID) {
+               struct dbell_entry *entry;
+               struct vmci_resource *resource;
+
+               resource =
+                       vmci_resource_get(handle, VMCI_RESOURCE_TYPE_DOORBELL);
+               if (resource == NULL)
+                       return VMCI_ERROR_NOT_FOUND;
+
+               entry =
+                       container_of(resource, struct dbell_entry,
+                                    resource);
+               *privFlags = entry->privFlags;
+               vmci_resource_release(resource);
+       } else if (handle.context == VMCI_HYPERVISOR_CONTEXT_ID) {
+               /*
+                * Hypervisor endpoints for notifications are not
+                * supported (yet).
+                */
+               return VMCI_ERROR_INVALID_ARGS;
+       } else {
+               *privFlags = vmci_context_get_priv_flags(handle.context);
+       }
+
+       return VMCI_SUCCESS;
+}
+
+/*
+ * Find doorbell entry by bitmap index.
+ */
+static struct dbell_entry *dbell_index_table_find(uint32_t idx)
+{
+       uint32_t bucket = VMCI_DOORBELL_HASH(idx);
+       struct dbell_entry *cur;
+
+       ASSERT(vmci_guest_code_active());
+
+       list_for_each_entry(cur, &vmciDoorbellIT.entries[bucket], idxListItem) {
+               if (idx == cur->idx)
+                       return cur;
+       }
+
+       return NULL;
+}
+
+/*
+ * Add the given entry to the index table.  This will hold() the entry's
+ * resource so that the entry is not deleted before it is removed from the
+ * table.
+ */
+static void dbell_index_table_add(struct dbell_entry *entry)
+{
+       uint32_t bucket;
+       uint32_t newNotifyIdx;
+
+       ASSERT(entry);
+       ASSERT(vmci_guest_code_active());
+
+       vmci_resource_hold(&entry->resource);
+
+       spin_lock_bh(&vmciDoorbellIT.lock);
+
+       /*
+        * 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 within 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 &&
+                   !dbell_index_table_find(lastNotifyIdxReleased)) {
+                       newNotifyIdx = lastNotifyIdxReleased;
+                       lastNotifyIdxReleased = PAGE_SIZE;
+               } else {
+                       bool reused = false;
+                       newNotifyIdx = lastNotifyIdxReserved;
+                       if (notifyIdxCount + 1 < maxNotifyIdx) {
+                               do {
+                                       if (!dbell_index_table_find
+                                           (newNotifyIdx)) {
+                                               reused = true;
+                                               break;
+                                       }
+                                       newNotifyIdx = (newNotifyIdx + 1) %
+                                               maxNotifyIdx;
+                               } while (newNotifyIdx != lastNotifyIdxReleased);
+                       }
+                       if (!reused) {
+                               newNotifyIdx = maxNotifyIdx;
+                               maxNotifyIdx++;
+                       }
+               }
+       } else {
+               newNotifyIdx = (lastNotifyIdxReserved + 1) % PAGE_SIZE;
+       }
+
+       lastNotifyIdxReserved = newNotifyIdx;
+       notifyIdxCount++;
+
+       entry->idx = newNotifyIdx;
+       bucket = VMCI_DOORBELL_HASH(entry->idx);
+       list_add(&entry->idxListItem, &vmciDoorbellIT.entries[bucket]);
+
+       spin_unlock_bh(&vmciDoorbellIT.lock);
+}
+
+/*
+ * Remove the given entry from the index table.  This will release() the
+ * entry's resource.
+ */
+static void dbell_index_table_remove(struct dbell_entry *entry)
+{
+       ASSERT(entry);
+       ASSERT(vmci_guest_code_active());
+
+       spin_lock_bh(&vmciDoorbellIT.lock);
+
+       list_del(&entry->idxListItem);
+
+       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 &&
+                      !dbell_index_table_find(maxNotifyIdx - 1)) {
+                       maxNotifyIdx--;
+               }
+       }
+
+       lastNotifyIdxReleased = entry->idx;
+
+       spin_unlock_bh(&vmciDoorbellIT.lock);
+
+       vmci_resource_release(&entry->resource);
+}
+
+/*
+ * Creates a link between the given doorbell handle and the given
+ * index in the bitmap in the device backend. A notification state
+ * is created in hypervisor.
+ */
+static int dbell_link(struct vmci_handle handle,
+                     uint32_t notifyIdx)
+{
+       struct vmci_doorbell_link_msg linkMsg;
+
+       ASSERT(!VMCI_HANDLE_INVALID(handle));
+       ASSERT(vmci_guest_code_active());
+
+       linkMsg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
+                                          VMCI_DOORBELL_LINK);
+       linkMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
+       linkMsg.hdr.payloadSize = sizeof(linkMsg) - VMCI_DG_HEADERSIZE;
+       linkMsg.handle = handle;
+       linkMsg.notifyIdx = notifyIdx;
+
+       return vmci_send_datagram((struct vmci_datagram *)&linkMsg);
+}
+
+/*
+ * Unlinks the given doorbell handle from an index in the bitmap in
+ * the device backend. The notification state is destroyed in hypervisor.
+ */
+static int dbell_unlink(struct vmci_handle handle)
+{
+       struct vmci_doorbell_unlink_msg unlinkMsg;
+
+       ASSERT(!VMCI_HANDLE_INVALID(handle));
+       ASSERT(vmci_guest_code_active());
+
+       unlinkMsg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID,
+                                            VMCI_DOORBELL_UNLINK);
+       unlinkMsg.hdr.src = VMCI_ANON_SRC_HANDLE;
+       unlinkMsg.hdr.payloadSize = sizeof(unlinkMsg) - VMCI_DG_HEADERSIZE;
+       unlinkMsg.handle = handle;
+
+       return vmci_send_datagram((struct vmci_datagram *)&unlinkMsg);
+}
+
+/*
+ * Notify another guest or the host.  We send a datagram down to the
+ * host via the hypervisor with the notification info.
+ */
+static int dbell_notify_as_guest(struct vmci_handle handle,
+                                uint32_t privFlags)
+{
+       struct vmci_doorbell_notify_msg notifyMsg;
+
+       ASSERT(vmci_guest_code_active());
+
+       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;
+
+       return vmci_send_datagram((struct vmci_datagram *)&notifyMsg);
+}
+
+/*
+ * Calls the specified callback in a delayed context.
+ */
+static void dbell_delayed_dispatch_cb(void *data)
+{
+       struct dbell_entry *entry = (struct dbell_entry *)data;
+
+       ASSERT(data);
+
+       entry->notifyCB(entry->clientData);
+       vmci_resource_release(&entry->resource);
+}
+
+/*
+ * Dispatches a doorbell notification to the host context.
+ */
+int vmci_dbell_host_context_notify(uint32_t srcCID,
+                                  struct vmci_handle handle)
+{
+       struct dbell_entry *entry;
+       struct vmci_resource *resource;
+       int result;
+
+       ASSERT(vmci_host_code_active());
+
+       if (VMCI_HANDLE_INVALID(handle)) {
+               pr_devel("Notifying an invalid doorbell " \
+                        "(handle=0x%x:0x%x).", handle.context,
+                        handle.resource);
+               return VMCI_ERROR_INVALID_ARGS;
+       }
+
+       resource = vmci_resource_get(handle, VMCI_RESOURCE_TYPE_DOORBELL);
+       if (resource == NULL) {
+               pr_devel("Notifying an unknown doorbell " \
+                        "(handle=0x%x:0x%x).", handle.context,
+                        handle.resource);
+               return VMCI_ERROR_NOT_FOUND;
+       }
+       entry = container_of(resource, struct dbell_entry, resource);
+
+       if (entry->runDelayed) {
+               result = vmci_drv_schedule_delayed_work(
+                       dbell_delayed_dispatch_cb,
+                       entry);
+
+               if (result < VMCI_SUCCESS) {
+                       /*
+                        * If we failed to schedule the delayed work,
+                        * we need to release the resource
+                        * immediately. Otherwise, the resource will
+                        * be released once the delayed callback has
+                        * been completed.
+                        */
+                       pr_devel("Failed to schedule delayed doorbell " \
+                                "notification (result=%d).", result);
+                       vmci_resource_release(resource);
+               }
+       } else {
+               entry->notifyCB(entry->clientData);
+               vmci_resource_release(resource);
+               result = VMCI_SUCCESS;
+       }
+       return result;
+}
+
+/*
+ * When a guest leaves hibernation, the device driver state is out of sync
+ * with the device state, since the driver state has doorbells registered
+ * that aren't known to the device.  This function takes care of
+ * reregistering any doorbells. In case an error occurs during
+ * reregistration (this is highly unlikely since 1) it succeeded the first
+ * time 2) the device driver is the only source of doorbell registrations),
+ * we simply log the error.  The doorbell can still be destroyed using
+ * VMCIDoorbell_Destroy.
+ */
+void vmci_dbell_hibernate(bool enterHibernate)
+{
+       uint32_t bucket;
+       struct dbell_entry *cur;
+
+       if (!vmci_guest_code_active() || enterHibernate)
+               return;
+
+       spin_lock_bh(&vmciDoorbellIT.lock);
+
+       for (bucket = 0; bucket < ARRAY_SIZE(vmciDoorbellIT.entries);
+            bucket++) {
+               list_for_each_entry(cur, &vmciDoorbellIT.entries[bucket],
+                                   idxListItem) {
+                       int result;
+                       struct vmci_handle h;
+
+                       h = vmci_resource_handle(&cur->resource);
+                       result = dbell_link(h, cur->idx);
+                       if (result != VMCI_SUCCESS &&
+                           result != VMCI_ERROR_DUPLICATE_ENTRY) {
+                               pr_warn("Failed to reregister doorbell " \
+                                       "(handle=0x%x:0x%x) to index " \
+                                       "(error=%d).", h.context, h.resource,
+                                       result);
+                       }
+               }
+       }
+
+       spin_unlock_bh(&vmciDoorbellIT.lock);
+}
+
+/*
+ * Register the notification bitmap with the host.
+ */
+bool vmci_dbell_register_notification_bitmap(uint32_t bitmapPPN)
+{
+       int result;
+       struct vmci_notify_bm_set_msg bitmapSetMsg;
+
+       /*
+        * Do not ASSERT() on the guest device here.  This function
+        * can get called during device initialization, so the
+        * ASSERT() will fail even though the device is (almost) up.
+        */
+       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_send_datagram((struct vmci_datagram *)&bitmapSetMsg);
+       if (result != VMCI_SUCCESS) {
+               pr_devel("Failed to register (PPN=%u) as " \
+                        "notification bitmap (error=%d).",
+                        bitmapPPN, result);
+               return false;
+       }
+       return true;
+}
+
+/*
+ * Executes or schedules the handlers for a given notify index.
+ */
+static void dbell_fire_entries(uint32_t notifyIdx)
+{
+       uint32_t bucket = VMCI_DOORBELL_HASH(notifyIdx);
+       struct dbell_entry *cur;
+
+       ASSERT(vmci_guest_code_active());
+
+       spin_lock_bh(&vmciDoorbellIT.lock);
+
+       list_for_each_entry(cur, &vmciDoorbellIT.entries[bucket], idxListItem) {
+               if (cur->idx == notifyIdx && atomic_read(&cur->active) == 1) {
+                       ASSERT(cur->notifyCB);
+                       if (cur->runDelayed) {
+                               int err;
+
+                               vmci_resource_hold(&cur->resource);
+                               err =
+                                       vmci_drv_schedule_delayed_work
+                                       (dbell_delayed_dispatch_cb, cur);
+                               if (err != VMCI_SUCCESS) {
+                                       vmci_resource_release(&cur->resource);
+                                       goto out;
+                               }
+                       } else {
+                               cur->notifyCB(cur->clientData);
+                       }
+               }
+       }
+
+out:
+       spin_unlock_bh(&vmciDoorbellIT.lock);
+}
+
+/*
+ * Scans the notification bitmap, collects pending notifications,
+ * resets the bitmap and invokes appropriate callbacks.
+ */
+void vmci_dbell_scan_notification_entries(uint8_t *bitmap)
+{
+       uint32_t idx;
+
+       ASSERT(bitmap);
+       ASSERT(vmci_guest_code_active());
+
+       for (idx = 0; idx < maxNotifyIdx; idx++) {
+               if (bitmap[idx] & 0x1) {
+                       bitmap[idx] &= ~1;
+                       dbell_fire_entries(idx);
+               }
+       }
+}
+
+/**
+ * vmci_doorbell_create() - Creates a doorbell
+ * @handle:    A handle used to track the resource.  Can be invalid.
+ * @flags:     Flag that determines context of callback.
+ * @priv_flags:        Privileges flags.
+ * @notify_cb: The callback to be ivoked when the doorbell fires.
+ * @client_data:       A parameter to be passed to the callback.
+ *
+ * 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 immediately (potentially with
+ * locks held - 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).
+ */
+int vmci_doorbell_create(struct vmci_handle *handle,
+                        uint32_t flags,
+                        uint32_t priv_flags,
+                        vmci_callback notify_cb,
+                        void *client_data)
+{
+       struct dbell_entry *entry;
+       struct vmci_handle newHandle;
+       int result;
+
+       if (!handle || !notify_cb || flags & ~VMCI_FLAG_DELAYED_CB ||
+           priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS)
+               return VMCI_ERROR_INVALID_ARGS;
+
+       entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+       if (entry == NULL) {
+               pr_warn("Failed allocating memory for datagram entry.");
+               return VMCI_ERROR_NO_MEM;
+       }
+
+       if (VMCI_HANDLE_INVALID(*handle)) {
+               uint32_t contextID = vmci_get_context_id();
+               uint32_t resourceID = vmci_resource_get_id(contextID);
+               if (resourceID == VMCI_INVALID_ID) {
+                       result = VMCI_ERROR_NO_HANDLE;
+                       goto freeMem;
+               }
+               newHandle = vmci_make_handle(contextID, resourceID);
+       } else {
+               bool validContext = false;
+
+               /*
+                * Validate the handle.  We must do both of the checks below
+                * because we can be acting as both a host and a guest at the
+                * same time. We always allow the host context ID, since the
+                * host functionality is in practice always there with the
+                * unified driver.
+                */
+               if (VMCI_HOST_CONTEXT_ID == handle->context ||
+                   (vmci_guest_code_active() &&
+                    vmci_get_context_id() == handle->context))
+                       validContext = true;
+
+               if (!validContext || VMCI_INVALID_ID == handle->resource) {
+                       pr_devel("Invalid argument (handle=0x%x:0x%x).",
+                                handle->context, handle->resource);
+                       result = VMCI_ERROR_INVALID_ARGS;
+                       goto freeMem;
+               }
+
+               newHandle = *handle;
+       }
+
+       entry->idx = 0;
+       INIT_LIST_HEAD(&entry->idxListItem);
+       entry->privFlags = priv_flags;
+       entry->runDelayed = (flags & VMCI_FLAG_DELAYED_CB) ? true : false;
+       entry->notifyCB = notify_cb;
+       entry->clientData = client_data;
+       atomic_set(&entry->active, 0);
+       init_waitqueue_head(&entry->destroyEvent);
+
+       result =
+               vmci_resource_add(&entry->resource, VMCI_RESOURCE_TYPE_DOORBELL,
+                                 newHandle, dbell_free_cb, entry);
+       if (result != VMCI_SUCCESS) {
+               pr_warn("Failed to add new resource (handle=0x%x:0x%x).",
+                       newHandle.context, newHandle.resource);
+               if (result == VMCI_ERROR_DUPLICATE_ENTRY)
+                       result = VMCI_ERROR_ALREADY_EXISTS;
+
+               goto freeMem;
+       }
+
+       if (vmci_guest_code_active()) {
+               dbell_index_table_add(entry);
+               result = dbell_link(newHandle, entry->idx);
+               if (VMCI_SUCCESS != result)
+                       goto destroyResource;
+
+               atomic_set(&entry->active, 1);
+       }
+
+       if (VMCI_HANDLE_INVALID(*handle))
+               *handle = newHandle;
+
+       return result;
+
+destroyResource:
+       dbell_index_table_remove(entry);
+       vmci_resource_remove(newHandle, VMCI_RESOURCE_TYPE_DOORBELL);
+freeMem:
+       kfree(entry);
+       return result;
+}
+EXPORT_SYMBOL(vmci_doorbell_create);
+
+/**
+ * vmci_doorbell_destroy() - Destroy a doorbell.
+ * @handle:    The handle tracking the resource.
+ *
+ * Destroys a doorbell previously created with VMCIDoorbell_Create. This
+ * operation may block waiting for a callback to finish.
+ */
+int vmci_doorbell_destroy(struct vmci_handle handle)
+{
+       struct dbell_entry *entry;
+       struct vmci_resource *resource;
+
+       if (VMCI_HANDLE_INVALID(handle))
+               return VMCI_ERROR_INVALID_ARGS;
+
+       resource = vmci_resource_get(handle, VMCI_RESOURCE_TYPE_DOORBELL);
+       if (resource == NULL) {
+               pr_devel("Failed to destroy doorbell " \
+                        "(handle=0x%x:0x%x).", handle.context,
+                        handle.resource);
+               return VMCI_ERROR_NOT_FOUND;
+       }
+
+       entry = container_of(resource, struct dbell_entry, resource);
+
+       if (vmci_guest_code_active()) {
+               int result;
+
+               dbell_index_table_remove(entry);
+
+               result = dbell_unlink(handle);
+               if (VMCI_SUCCESS != result) {
+
+                       /*
+                        * 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. One case where this may
+                        * happen is if a doorbell is unregistered
+                        * following a hibernation at a time where the
+                        * doorbell state hasn't been restored on the
+                        * hypervisor side yet. Since the handle has
+                        * now been removed in the guest, we just
+                        * print a warning and return success.
+                        */
+                       pr_devel("Unlink of doorbell (handle=0x%x:0x%x) " \
+                                "unknown by hypervisor (error=%d).",
+                                handle.context, handle.resource,
+                                result);
+               }
+       }
+
+       /*
+        * Now remove the resource from the table.  It might still be in use
+        * after this, in a callback or still on the delayed work queue.
+        */
+       vmci_resource_remove(handle, VMCI_RESOURCE_TYPE_DOORBELL);
+
+       /*
+        * We now wait on the destroyEvent and release the reference we got
+        * above.
+        */
+       vmci_drv_wait_on_event_intr(&entry->destroyEvent,
+                                   dbell_release_cb, entry);
+
+       /*
+        * We know that we are now the only reference to the above entry so
+        * can safely free it.
+        */
+       kfree(entry);
+
+       return VMCI_SUCCESS;
+}
+EXPORT_SYMBOL(vmci_doorbell_destroy);
+
+/**
+ * vmci_doorbell_notify() - Ring the doorbell (and hide in the bushes).
+ * @dst:       The handlle identifying the doorbell resource
+ * @priv_flags:        Priviledge flags.
+ *
+ * Generates a notification on the doorbell identified by the
+ * handle. For host side generation of notifications, the caller
+ * can specify what the privilege of the calling side is.
+ */
+int vmci_doorbell_notify(struct vmci_handle dst,
+                        u32 priv_flags)
+{
+       int retval;
+       enum vmci_route route;
+       struct vmci_handle src;
+
+       if (VMCI_HANDLE_INVALID(dst) ||
+           (priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS))
+               return VMCI_ERROR_INVALID_ARGS;
+
+       src = VMCI_INVALID_HANDLE;
+       retval = vmci_route(&src, &dst, false, &route);
+       if (retval < VMCI_SUCCESS)
+               return retval;
+
+       if (VMCI_ROUTE_AS_HOST == route)
+               return vmci_ctx_notify_dbell(VMCI_HOST_CONTEXT_ID,
+                                            dst, priv_flags);
+
+       if (VMCI_ROUTE_AS_GUEST == route)
+               return dbell_notify_as_guest(dst, priv_flags);
+
+       pr_warn("Unknown route (%d) for doorbell.", route);
+       return VMCI_ERROR_DST_UNREACHABLE;
+}
+EXPORT_SYMBOL(vmci_doorbell_notify);
diff --git a/drivers/misc/vmw_vmci/vmci_doorbell.h b/drivers/misc/vmw_vmci/vmci_doorbell.h
new file mode 100644
index 0000000..47cc657
--- /dev/null
+++ b/drivers/misc/vmw_vmci/vmci_doorbell.h
@@ -0,0 +1,54 @@
+/*
+ * VMware VMCI Driver
+ *
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef VMCI_DOORBELL_H
+#define VMCI_DOORBELL_H
+
+#include <linux/vmw_vmci_defs.h>
+#include <linux/types.h>
+
+#include "vmci_driver.h"
+
+/*
+ * VMCINotifyResourceInfo: Used to create and destroy doorbells, and
+ * generate a notification for a doorbell or queue pair.
+ */
+struct vmci_dbell_notify_resource_info {
+       struct vmci_handle handle;
+       uint16_t resource;
+       uint16_t action;
+       int32_t result;
+};
+
+/*
+ * Structure used for checkpointing the doorbell mappings. It is
+ * written to the checkpoint as is, so changing this structure will
+ * break checkpoint compatibility.
+ */
+struct dbell_cpt_state {
+       struct vmci_handle handle;
+       uint64_t bitmapIdx;
+};
+
+int vmci_dbell_init(void);
+void vmci_dbell_hibernate(bool enterHibernation);
+
+int vmci_dbell_host_context_notify(uint32_t srcCID, struct vmci_handle handle);
+int vmci_dbell_get_priv_flags(struct vmci_handle handle, uint32_t *privFlags);
+
+bool vmci_dbell_register_notification_bitmap(uint32_t bitmapPPN);
+void vmci_dbell_scan_notification_entries(uint8_t *bitmap);
+
+#endif /* VMCI_DOORBELL_H */


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2012-08-30 16:40 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-08-30 16:40 [PATCH 03/11] vmci_doorbell.patch: VMCI doorbell notification handling George Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).