--- /dev/null
+/*********************************************************
+ * Copyright (C) 2011 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
+ *
+ *********************************************************/
+
+/* Must come before any kernel header file */
+#include "driver-config.h"
+
+#define EXPORT_SYMTAB
+
+#include <asm/io.h>
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#if defined(__x86_64__) && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 12)
+# include <linux/ioctl32.h>
+/* Use weak: not all kernels export sys_ioctl for use by modules */
+asmlinkage __attribute__((weak)) long
+sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg);
+#endif
+#include <linux/miscdevice.h>
+#include <linux/moduleparam.h>
+#include <linux/poll.h>
+#include <linux/smp.h>
+#include <linux/smp_lock.h>
+
+#include "compat_highmem.h"
+#include "compat_init.h"
+#include "compat_interrupt.h"
+#include "compat_ioport.h"
+#include "compat_kernel.h"
+#include "compat_mm.h"
+#include "compat_module.h"
+#include "compat_mutex.h"
+#include "compat_page.h"
+#include "compat_pci.h"
+#include "compat_sched.h"
+#include "compat_slab.h"
+#include "compat_uaccess.h"
+#include "compat_version.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 9)
+# error "Linux kernels before 2.6.9 are not supported."
+#endif
+
+#include "vm_basic_types.h"
+#include "vm_device_version.h"
+
+#include "vmware.h"
+#include "driverLog.h"
+#include "pgtbl.h"
+#include "vmci_defs.h"
+#include "vmci_handle_array.h"
+#include "vmci_infrastructure.h"
+#include "vmci_iocontrols.h"
+#include "vmci_version.h"
+#include "vmci_kernel_if.h"
+#include "vmciCommonInt.h"
+#include "vmciContext.h"
+#include "vmciDatagram.h"
+#include "vmciDoorbell.h"
+#include "vmciDriver.h"
+#include "vmciEvent.h"
+#include "vmciKernelAPI.h"
+#include "vmciQueuePair.h"
+#include "vmciResource.h"
+
+#define LGPFX "VMCI: "
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * PCI Device interface --
+ *
+ * Declarations of types and functions related to the VMCI PCI
+ * device personality.
+ *
+ *
+ *----------------------------------------------------------------------
+ */
+
+/*
+ * VMCI PCI driver state
+ */
+
+typedef struct vmci_device {
+ compat_mutex_t lock;
+
+ unsigned int ioaddr;
+ unsigned int ioaddr_size;
+ unsigned int irq;
+ unsigned int intr_type;
+ Bool exclusive_vectors;
+ struct msix_entry msix_entries[VMCI_MAX_INTRS];
+
+ Bool enabled;
+ spinlock_t dev_spinlock;
+} vmci_device;
+
+static const struct pci_device_id vmci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_VMCI), },
+ { 0 },
+};
+
+static int vmci_probe_device(struct pci_dev *pdev,
+ const struct pci_device_id *id);
+static void vmci_remove_device(struct pci_dev* pdev);
+
+static struct pci_driver vmci_driver = {
+ .name = "vmci",
+ .id_table = vmci_ids,
+ .probe = vmci_probe_device,
+ .remove = __devexit_p(vmci_remove_device),
+};
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static compat_irqreturn_t vmci_interrupt(int irq, void *dev_id,
+ struct pt_regs * regs);
+static compat_irqreturn_t vmci_interrupt_bm(int irq, void *dev_id,
+ struct pt_regs * regs);
+#else
+static compat_irqreturn_t vmci_interrupt(int irq, void *dev_id);
+static compat_irqreturn_t vmci_interrupt_bm(int irq, void *dev_id);
+#endif
+static void dispatch_datagrams(unsigned long data);
+static void process_bitmap(unsigned long data);
+
+/* MSI-X has performance problems in < 2.6.19 */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+# define VMCI_DISABLE_MSIX 0
+#else
+# define VMCI_DISABLE_MSIX 1
+#endif
+
+static vmci_device vmci_dev;
+static int vmci_disable_msi;
+static int vmci_disable_msix = VMCI_DISABLE_MSIX;
+
+DECLARE_TASKLET(vmci_dg_tasklet, dispatch_datagrams,
+ (unsigned long)&vmci_dev);
+
+DECLARE_TASKLET(vmci_bm_tasklet, process_bitmap,
+ (unsigned long)&vmci_dev);
+
+/*
+ * Allocate a buffer for incoming datagrams globally to avoid repeated
+ * allocation in the interrupt handler's atomic context.
+ */
+
+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;
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Host device node interface --
+ *
+ * Implements VMCI by implementing open/close/ioctl functions
+ *
+ *
+ *----------------------------------------------------------------------
+ */
+
+/*
+ * Per-instance host state
+ */
+
+typedef struct VMCILinux {
+ VMCIContext *context;
+ int userVersion;
+ VMCIObjType ctType;
+#if defined(HAVE_COMPAT_IOCTL) || defined(HAVE_UNLOCKED_IOCTL)
+ compat_mutex_t lock;
+#endif
+} VMCILinux;
+
+/*
+ * Static driver state.
+ */
+
+#define VM_DEVICE_NAME_SIZE 32
+#define LINUXLOG_BUFFER_SIZE 1024
+
+typedef struct VMCILinuxState {
+ int major;
+ int minor;
+ struct miscdevice misc;
+ char deviceName[VM_DEVICE_NAME_SIZE];
+ char buf[LINUXLOG_BUFFER_SIZE];
+} VMCILinuxState;
+
+static struct VMCILinuxState linuxState;
+
+static int VMCISetupNotify(VMCIContext *context, VA notifyUVA);
+static void VMCIUnsetNotifyInt(VMCIContext *context, Bool useLock);
+
+static int LinuxDriver_Open(struct inode *inode, struct file *filp);
+
+static int LinuxDriver_Ioctl(struct inode *inode, struct file *filp,
+ u_int iocmd, unsigned long ioarg);
+
+#if defined(HAVE_COMPAT_IOCTL) || defined(HAVE_UNLOCKED_IOCTL)
+static long LinuxDriver_UnlockedIoctl(struct file *filp,
+ u_int iocmd, unsigned long ioarg);
+#endif
+
+static int LinuxDriver_Close(struct inode *inode, struct file *filp);
+static unsigned int LinuxDriverPoll(struct file *file, poll_table *wait);
+
+
+#if defined(HAVE_COMPAT_IOCTL) || defined(HAVE_UNLOCKED_IOCTL)
+#define LinuxDriverLockIoctlPerFD(mutex) compat_mutex_lock(mutex)
+#define LinuxDriverUnlockIoctlPerFD(mutex) compat_mutex_unlock(mutex)
+#else
+#define LinuxDriverLockIoctlPerFD(mutex) do {} while (0)
+#define LinuxDriverUnlockIoctlPerFD(mutex) do {} while (0)
+#endif
+
+static struct file_operations vmuser_fops;
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Shared VMCI device definitions --
+ *
+ * Types and variables shared by both host and guest personality
+ *
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Bool guestDeviceInit;
+static Bool hostDeviceInit;
+
+/*
+ * Until we have a single driver, that uses load or runtime
+ * configuration to enable guest and host personalities directly, we
+ * let the tools flag decide.
+ */
+
+#ifdef VMX86_TOOLS
+static Bool isGuestDriver = TRUE;
+#else
+static Bool isGuestDriver = FALSE;
+#endif
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * Host device support --
+ *
+ * The following functions implement the support for the VMCI
+ * host driver.
+ *
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+
+#ifdef VM_X86_64
+#ifndef HAVE_COMPAT_IOCTL
+static int
+LinuxDriver_Ioctl32_Handler(unsigned int fd, unsigned int iocmd,
+ unsigned long ioarg, struct file * filp)
+{
+ int ret;
+ ret = -ENOTTY;
+ if (filp && filp->f_op && filp->f_op->ioctl == LinuxDriver_Ioctl) {
+ ret = LinuxDriver_Ioctl(filp->f_dentry->d_inode, filp, iocmd, ioarg);
+ }
+ return ret;
+}
+#endif /* !HAVE_COMPAT_IOCTL */
+
+static int
+register_ioctl32_handlers(void)
+{
+#ifndef HAVE_COMPAT_IOCTL
+ {
+ int i;
+
+ for (i = IOCTL_VMCI_FIRST; i < IOCTL_VMCI_LAST; i++) {
+ int retval = register_ioctl32_conversion(i,
+ LinuxDriver_Ioctl32_Handler);
+
+ if (retval) {
+ Warning("Fail to register ioctl32 conversion for cmd %d\n", i);
+ return retval;
+ }
+ }
+
+ for (i = IOCTL_VMCI_FIRST2; i < IOCTL_VMCI_LAST2; i++) {
+ int retval = register_ioctl32_conversion(i,
+ LinuxDriver_Ioctl32_Handler);
+
+ if (retval) {
+ Warning("Fail to register ioctl32 conversion for cmd %d\n", i);
+ return retval;
+ }
+ }
+ }
+#endif /* !HAVE_COMPAT_IOCTL */
+ return 0;
+}
+
+static void
+unregister_ioctl32_handlers(void)
+{
+#ifndef HAVE_COMPAT_IOCTL
+ {
+ int i;
+
+ for (i = IOCTL_VMCI_FIRST; i < IOCTL_VMCI_LAST; i++) {
+ int retval = unregister_ioctl32_conversion(i);
+
+ if (retval) {
+ Warning("Fail to unregister ioctl32 conversion for cmd %d\n", i);
+ }
+ }
+
+ for (i = IOCTL_VMCI_FIRST2; i < IOCTL_VMCI_LAST2; i++) {
+ int retval = unregister_ioctl32_conversion(i);
+
+ if (retval) {
+ Warning("Fail to unregister ioctl32 conversion for cmd %d\n", i);
+ }
+ }
+ }
+#endif /* !HAVE_COMPAT_IOCTL */
+}
+#else /* VM_X86_64 */
+#define register_ioctl32_handlers() (0)
+#define unregister_ioctl32_handlers() do { } while (0)
+#endif /* VM_X86_64 */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_host_init --
+ *
+ * Initializes the VMCI host device driver.
+ *
+ * Results:
+ * 0 on success, other error codes on failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+vmci_host_init(void)
+{
+ int retval;
+
+ if (VMCI_HostInit() < VMCI_SUCCESS) {
+ return -ENOMEM;
+ }
+
+ /*
+ * Initialize the file_operations structure. Because this code is always
+ * compiled as a module, this is fine to do it here and not in a static
+ * initializer.
+ */
+
+ memset(&vmuser_fops, 0, sizeof vmuser_fops);
+ vmuser_fops.owner = THIS_MODULE;
+ vmuser_fops.poll = LinuxDriverPoll;
+#ifdef HAVE_UNLOCKED_IOCTL
+ vmuser_fops.unlocked_ioctl = LinuxDriver_UnlockedIoctl;
+#else
+ vmuser_fops.ioctl = LinuxDriver_Ioctl;
+#endif
+#ifdef HAVE_COMPAT_IOCTL
+ vmuser_fops.compat_ioctl = LinuxDriver_UnlockedIoctl;
+#endif
+ vmuser_fops.open = LinuxDriver_Open;
+ vmuser_fops.release = LinuxDriver_Close;
+
+ sprintf(linuxState.deviceName, "vmci");
+ linuxState.major = 10;
+ linuxState.misc.minor = MISC_DYNAMIC_MINOR;
+ linuxState.misc.name = linuxState.deviceName;
+ linuxState.misc.fops = &vmuser_fops;
+
+ retval = misc_register(&linuxState.misc);
+
+ if (retval) {
+ Warning("Module %s: error %u registering with major=%d minor=%d\n",
+ linuxState.deviceName, -retval, linuxState.major, linuxState.minor);
+ goto exit;
+ }
+ linuxState.minor = linuxState.misc.minor;
+ Log("Module %s: registered with major=%d minor=%d\n",
+ linuxState.deviceName, linuxState.major, linuxState.minor);
+
+ retval = register_ioctl32_handlers();
+ if (retval) {
+ misc_deregister(&linuxState.misc);
+ }
+
+exit:
+ if (retval) {
+ VMCI_HostCleanup();
+ }
+ return retval;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinuxDriver_Open --
+ *
+ * called on open of /dev/vmci. Use count used
+ * to determine eventual deallocation of the module
+ *
+ * Side effects:
+ * Increment use count used to determine eventual deallocation of
+ * the module
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+LinuxDriver_Open(struct inode *inode, // IN
+ struct file *filp) // IN
+{
+ VMCILinux *vmciLinux;
+
+ vmciLinux = kmalloc(sizeof *vmciLinux, GFP_KERNEL);
+ if (vmciLinux == NULL) {
+ return -ENOMEM;
+ }
+ memset(vmciLinux, 0, sizeof *vmciLinux);
+ vmciLinux->ctType = VMCIOBJ_NOT_SET;
+ vmciLinux->userVersion = 0;
+#if defined(HAVE_COMPAT_IOCTL) || defined(HAVE_UNLOCKED_IOCTL)
+ compat_mutex_init(&vmciLinux->lock);
+#endif
+
+ filp->private_data = vmciLinux;
+
+ return 0;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinuxDriver_Close --
+ *
+ * called on close of /dev/vmci, most often when the
+ * process exits. Decrement use count, allowing for possible uninstalling
+ * of the module.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+LinuxDriver_Close(struct inode *inode, // IN
+ struct file *filp) // IN
+{
+ VMCILinux *vmciLinux;
+
+ vmciLinux = (VMCILinux *)filp->private_data;
+ ASSERT(vmciLinux);
+
+ if (vmciLinux->ctType == VMCIOBJ_CONTEXT) {
+ VMCIId cid;
+
+ ASSERT(vmciLinux->context);
+ cid = VMCIContext_GetId(vmciLinux->context);
+
+ VMCIContext_ReleaseContext(vmciLinux->context);
+ vmciLinux->context = NULL;
+ }
+ vmciLinux->ctType = VMCIOBJ_NOT_SET;
+
+ kfree(vmciLinux);
+ filp->private_data = NULL;
+ return 0;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinuxDriverPoll --
+ *
+ * This is used to wake up the VMX when a VMCI call arrives, or
+ * to wake up select() or poll() at the next clock tick.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static unsigned int
+LinuxDriverPoll(struct file *filp,
+ poll_table *wait)
+{
+ VMCILockFlags flags;
+ VMCILinux *vmciLinux = (VMCILinux *) filp->private_data;
+ unsigned int mask = 0;
+
+ if (vmciLinux->ctType == VMCIOBJ_CONTEXT) {
+ ASSERT(vmciLinux->context != NULL);
+ /*
+ * Check for VMCI calls to this VM context.
+ */
+
+ if (wait != NULL) {
+ poll_wait(filp, &vmciLinux->context->hostContext.waitQueue, wait);
+ }
+
+ VMCI_GrabLock(&vmciLinux->context->lock, &flags);
+ if (vmciLinux->context->pendingDatagrams > 0 ||
+ VMCIHandleArray_GetSize(vmciLinux->context->pendingDoorbellArray) > 0) {
+ mask = POLLIN;
+ }
+ VMCI_ReleaseLock(&vmciLinux->context->lock, flags);
+ }
+ return mask;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * VMCICopyHandleArrayToUser --
+ *
+ * Copies the handles of a handle array into a user buffer, and
+ * returns the new length in userBufferSize. If the copy to the
+ * user buffer fails, the functions still returns VMCI_SUCCESS,
+ * but retval != 0.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+VMCICopyHandleArrayToUser(void *userBufUVA, // IN
+ uint64 *userBufSize, // IN/OUT
+ VMCIHandleArray *handleArray, // IN
+ int *retval) // IN
+{
+ uint32 arraySize;
+ VMCIHandle *handles;
+
+ if (handleArray) {
+ arraySize = VMCIHandleArray_GetSize(handleArray);
+ } else {
+ arraySize = 0;
+ }
+
+ if (arraySize * sizeof *handles > *userBufSize) {
+ return VMCI_ERROR_MORE_DATA;
+ }
+
+ *userBufSize = arraySize * sizeof *handles;
+ if (*userBufSize) {
+ *retval = copy_to_user(userBufUVA,
+ VMCIHandleArray_GetHandles(handleArray),
+ *userBufSize);
+ }
+
+ return VMCI_SUCCESS;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * LinuxDriver_Ioctl --
+ *
+ * Main path for UserRPC
+ *
+ * Results:
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int
+LinuxDriver_Ioctl(struct inode *inode,
+ struct file *filp,
+ u_int iocmd,
+ unsigned long ioarg)
+{
+ VMCILinux *vmciLinux = (VMCILinux *) filp->private_data;
+ int retval = 0;
+
+ switch (iocmd) {
+ case IOCTL_VMCI_VERSION2: {
+ int verFromUser;
+
+ if (copy_from_user(&verFromUser, (void *)ioarg, sizeof verFromUser)) {
+ retval = -EFAULT;
+ break;
+ }
+
+ vmciLinux->userVersion = verFromUser;
+ }
+ /* Fall through. */
+ case IOCTL_VMCI_VERSION:
+ /*
+ * The basic logic here is:
+ *
+ * If the user sends in a version of 0 tell it our version.
+ * If the user didn't send in a version, tell it our version.
+ * If the user sent in an old version, tell it -its- version.
+ * If the user sent in an newer version, tell it our version.
+ *
+ * The rationale behind telling the caller its version is that
+ * Workstation 6.5 required that VMX and VMCI kernel module were
+ * version sync'd. All new VMX users will be programmed to
+ * handle the VMCI kernel module version.
+ */
+
+ if (vmciLinux->userVersion > 0 &&
+ vmciLinux->userVersion < VMCI_VERSION_HOSTQP) {
+ retval = vmciLinux->userVersion;
+ } else {
+ retval = VMCI_VERSION;
+ }
+ break;
+
+ case IOCTL_VMCI_INIT_CONTEXT: {
+ VMCIInitBlock initBlock;
+ VMCIHostUser user;
+
+ retval = copy_from_user(&initBlock, (void *)ioarg, sizeof initBlock);
+ if (retval != 0) {
+ Log("VMCI: Error reading init block.\n");
+ retval = -EFAULT;
+ break;
+ }
+
+ LinuxDriverLockIoctlPerFD(&vmciLinux->lock);
+ if (vmciLinux->ctType != VMCIOBJ_NOT_SET) {
+ Log("VMCI: Received VMCI init on initialized handle\n");
+ retval = -EINVAL;
+ goto init_release;
+ }
+
+ if (initBlock.flags & ~VMCI_PRIVILEGE_FLAG_RESTRICTED) {
+ Log("VMCI: Unsupported VMCI restriction flag.\n");
+ retval = -EINVAL;
+ goto init_release;
+ }
+
+ user = current_uid();
+ retval = VMCIContext_InitContext(initBlock.cid, initBlock.flags,
+ 0 /* Unused */, vmciLinux->userVersion,
+ &user, &vmciLinux->context);
+ if (retval < VMCI_SUCCESS) {
+ Log("VMCI: Error initializing context.\n");
+ retval = retval == VMCI_ERROR_DUPLICATE_ENTRY ? -EEXIST : -EINVAL;
+ goto init_release;
+ }
+
+ /*
+ * Copy cid to userlevel, we do this to allow the VMX to enforce its
+ * policy on cid generation.
+ */
+ initBlock.cid = VMCIContext_GetId(vmciLinux->context);
+ retval = copy_to_user((void *)ioarg, &initBlock, sizeof initBlock);
+ if (retval != 0) {
+ VMCIContext_ReleaseContext(vmciLinux->context);
+ vmciLinux->context = NULL;
+ Log("VMCI: Error writing init block.\n");
+ retval = -EFAULT;
+ goto init_release;
+ }
+ ASSERT(initBlock.cid != VMCI_INVALID_ID);
+
+ vmciLinux->ctType = VMCIOBJ_CONTEXT;
+
+ init_release:
+ LinuxDriverUnlockIoctlPerFD(&vmciLinux->lock);
+ break;
+ }
+
+ case IOCTL_VMCI_DATAGRAM_SEND: {
+ VMCIDatagramSendRecvInfo sendInfo;
+ VMCIDatagram *dg = NULL;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Warning("VMCI: Ioctl %d only valid for context handle.\n", iocmd);
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&sendInfo, (void *) ioarg, sizeof sendInfo);
+ if (retval) {
+ Warning("VMCI: copy_from_user failed.\n");
+ retval = -EFAULT;
+ break;
+ }
+
+ if (sendInfo.len > VMCI_MAX_DG_SIZE) {
+ Warning("VMCI: datagram size too big.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (sendInfo.len < sizeof *dg) {
+ Warning("VMCI: datagram size too small.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ dg = VMCI_AllocKernelMem(sendInfo.len, VMCI_MEMORY_NORMAL);
+ if (dg == NULL) {
+ Log("VMCI: Cannot allocate memory to dispatch datagram.\n");
+ retval = -ENOMEM;
+ break;
+ }
+
+ retval = copy_from_user(dg, (char *)(VA)sendInfo.addr, sendInfo.len);
+ if (retval != 0) {
+ Log("VMCI: Error getting datagram: %d\n", retval);
+ VMCI_FreeKernelMem(dg, sendInfo.len);
+ retval = -EFAULT;
+ break;
+ }
+
+ VMCI_DEBUG_LOG(10, ("VMCI: Datagram dst handle 0x%"FMT64"x, "
+ "src handle 0x%"FMT64"x, payload size %"FMT64"u.\n",
+ VMCI_HANDLE_TO_UINT64(dg->dst),
+ VMCI_HANDLE_TO_UINT64(dg->src), dg->payloadSize));
+
+ /* Get source context id. */
+ ASSERT(vmciLinux->context);
+ cid = VMCIContext_GetId(vmciLinux->context);
+ ASSERT(cid != VMCI_INVALID_ID);
+ sendInfo.result = VMCIDatagram_Dispatch(cid, dg, TRUE);
+ VMCI_FreeKernelMem(dg, sendInfo.len);
+ retval = copy_to_user((void *)ioarg, &sendInfo, sizeof sendInfo);
+ break;
+ }
+
+ case IOCTL_VMCI_DATAGRAM_RECEIVE: {
+ VMCIDatagramSendRecvInfo recvInfo;
+ VMCIDatagram *dg = NULL;
+ size_t size;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Warning("VMCI: Ioctl %d only valid for context handle.\n", iocmd);
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&recvInfo, (void *) ioarg, sizeof recvInfo);
+ if (retval) {
+ Warning("VMCI: copy_from_user failed.\n");
+ retval = -EFAULT;
+ break;
+ }
+
+ ASSERT(vmciLinux->ctType == VMCIOBJ_CONTEXT);
+
+ size = recvInfo.len;
+ ASSERT(vmciLinux->context);
+ recvInfo.result = VMCIContext_DequeueDatagram(vmciLinux->context,
+ &size, &dg);
+
+ if (recvInfo.result >= VMCI_SUCCESS) {
+ ASSERT(dg);
+ retval = copy_to_user((void *) ((uintptr_t) recvInfo.addr), dg,
+ VMCI_DG_SIZE(dg));
+ VMCI_FreeKernelMem(dg, VMCI_DG_SIZE(dg));
+ if (retval != 0) {
+ break;
+ }
+ }
+ retval = copy_to_user((void *)ioarg, &recvInfo, sizeof recvInfo);
+ break;
+ }
+
+ case IOCTL_VMCI_QUEUEPAIR_ALLOC: {
+ VMCIQueuePairAllocInfo queuePairAllocInfo;
+ VMCIQueuePairAllocInfo *info = (VMCIQueuePairAllocInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_QUEUEPAIR_ALLOC only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&queuePairAllocInfo, (void *)ioarg,
+ sizeof queuePairAllocInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ VMCIQPBroker_Lock();
+
+ {
+ QueuePairPageStore pageStore = { TRUE,
+ queuePairAllocInfo.producePageFile,
+ queuePairAllocInfo.consumePageFile,
+ queuePairAllocInfo.producePageFileSize,
+ queuePairAllocInfo.consumePageFileSize,
+ 0,
+ 0 };
+
+ result = VMCIQPBroker_Alloc(queuePairAllocInfo.handle,
+ queuePairAllocInfo.peer,
+ queuePairAllocInfo.flags,
+ VMCI_NO_PRIVILEGE_FLAGS,
+ queuePairAllocInfo.produceSize,
+ queuePairAllocInfo.consumeSize,
+ &pageStore,
+ vmciLinux->context);
+ }
+ Log("VMCI: IOCTL_VMCI_QUEUEPAIR_ALLOC cid = %u result = %d.\n", cid,
+ result);
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval) {
+ retval = -EFAULT;
+ if (result >= VMCI_SUCCESS) {
+ result = VMCIQPBroker_Detach(queuePairAllocInfo.handle,
+ vmciLinux->context, TRUE);
+ ASSERT(result >= VMCI_SUCCESS);
+ }
+ }
+
+ VMCIQPBroker_Unlock();
+ break;
+ }
+
+ case IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE: {
+ VMCIQueuePairPageFileInfo pageFileInfo;
+ VMCIQueuePairPageFileInfo *info = (VMCIQueuePairPageFileInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+ Bool useUVA;
+ int size;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE only valid for "
+ "contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (VMCIContext_SupportsHostQP(vmciLinux->context)) {
+ useUVA = TRUE;
+ size = sizeof *info;
+ } else {
+ /*
+ * An older VMX version won't supply the UVA of the page
+ * files backing the queue pair contents (and headers)
+ */
+
+ useUVA = FALSE;
+ size = sizeof(VMCIQueuePairPageFileInfo_NoHostQP);
+ }
+
+ retval = copy_from_user(&pageFileInfo, (void *)ioarg, size);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ /*
+ * Communicate success pre-emptively to the caller. Note that
+ * the basic premise is that it is incumbent upon the caller not
+ * to look at the info.result field until after the ioctl()
+ * returns. And then, only if the ioctl() result indicates no
+ * error. We send up the SUCCESS status before calling
+ * SetPageStore() store because failing to copy up the result
+ * code means unwinding the SetPageStore().
+ *
+ * It turns out the logic to unwind a SetPageStore() opens a can
+ * of worms. For example, if a host had created the QueuePair
+ * and a guest attaches and SetPageStore() is successful but
+ * writing success fails, then ... the host has to be stopped
+ * from writing (anymore) data into the QueuePair. That means
+ * an additional test in the VMCI_Enqueue() code path. Ugh.
+ */
+
+ result = VMCI_SUCCESS;
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval == 0) {
+ cid = VMCIContext_GetId(vmciLinux->context);
+ VMCIQPBroker_Lock();
+
+ {
+ QueuePairPageStore pageStore = { TRUE,
+ pageFileInfo.producePageFile,
+ pageFileInfo.consumePageFile,
+ pageFileInfo.producePageFileSize,
+ pageFileInfo.consumePageFileSize,
+ useUVA ? pageFileInfo.produceVA : 0,
+ useUVA ? pageFileInfo.consumeVA : 0 };
+
+ result = VMCIQPBroker_SetPageStore(pageFileInfo.handle,
+ &pageStore,
+ vmciLinux->context);
+ }
+ VMCIQPBroker_Unlock();
+
+ if (result < VMCI_SUCCESS) {
+ Log("VMCI: IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE cid = %u result = %d.\n",
+ cid, result);
+
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval != 0) {
+ /*
+ * Note that in this case the SetPageStore() call
+ * failed but we were unable to communicate that to the
+ * caller (because the copy_to_user() call failed).
+ * So, if we simply return an error (in this case
+ * -EFAULT) then the caller will know that the
+ * SetPageStore failed even though we couldn't put the
+ * result code in the result field and indicate exactly
+ * why it failed.
+ *
+ * That says nothing about the issue where we were once
+ * able to write to the caller's info memory and now
+ * can't. Something more serious is probably going on
+ * than the fact that SetPageStore() didn't work.
+ */
+ retval = -EFAULT;
+ }
+ }
+
+ } else {
+ /*
+ * In this case, we can't write a result field of the
+ * caller's info block. So, we don't even try to
+ * SetPageStore().
+ */
+ retval = -EFAULT;
+ }
+
+ break;
+ }
+
+ case IOCTL_VMCI_QUEUEPAIR_DETACH: {
+ VMCIQueuePairDetachInfo detachInfo;
+ VMCIQueuePairDetachInfo *info = (VMCIQueuePairDetachInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_QUEUEPAIR_DETACH only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&detachInfo, (void *)ioarg, sizeof detachInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ VMCIQPBroker_Lock();
+ result = VMCIQPBroker_Detach(detachInfo.handle, vmciLinux->context,
+ FALSE); /* Probe detach operation. */
+ Log("VMCI: IOCTL_VMCI_QUEUEPAIR_DETACH cid = %u result = %d.\n",
+ cid, result);
+
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval) {
+ /* Could not copy to userland, don't perform the actual detach. */
+ retval = -EFAULT;
+ } else {
+ if (result >= VMCI_SUCCESS) {
+ /* Now perform the actual detach. */
+ int32 result2 = VMCIQPBroker_Detach(detachInfo.handle,
+ vmciLinux->context, TRUE);
+ if (UNLIKELY(result != result2)) {
+ /*
+ * This should never happen. But it's better to log a warning
+ * than to crash the host.
+ */
+ Warning("VMCIQPBroker_Detach returned different results: "
+ "previous = %d, current = %d.\n", result, result2);
+ }
+ }
+ }
+
+ VMCIQPBroker_Unlock();
+ break;
+ }
+
+ case IOCTL_VMCI_DATAGRAM_REQUEST_MAP: {
+ VMCIDatagramMapInfo mapInfo;
+ VMCIDatagramMapInfo *info = (VMCIDatagramMapInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_REQUEST_MAP only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&mapInfo, (void *)ioarg, sizeof mapInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ result = VMCIDatagramRequestWellKnownMap(mapInfo.wellKnownID, cid,
+ VMCIContext_GetPrivFlags(cid));
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case IOCTL_VMCI_DATAGRAM_REMOVE_MAP: {
+ VMCIDatagramMapInfo mapInfo;
+ VMCIDatagramMapInfo *info = (VMCIDatagramMapInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_REMOVE_MAP only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&mapInfo, (void *)ioarg, sizeof mapInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ result = VMCIDatagramRemoveWellKnownMap(mapInfo.wellKnownID, cid);
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case IOCTL_VMCI_CTX_ADD_NOTIFICATION: {
+ VMCINotifyAddRemoveInfo arInfo;
+ VMCINotifyAddRemoveInfo *info = (VMCINotifyAddRemoveInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_CTX_ADD_NOTIFICATION only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&arInfo, (void *)ioarg, sizeof arInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ result = VMCIContext_AddNotification(cid, arInfo.remoteCID);
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case IOCTL_VMCI_CTX_REMOVE_NOTIFICATION: {
+ VMCINotifyAddRemoveInfo arInfo;
+ VMCINotifyAddRemoveInfo *info = (VMCINotifyAddRemoveInfo *)ioarg;
+ int32 result;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_CTX_REMOVE_NOTIFICATION only valid for "
+ "contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&arInfo, (void *)ioarg, sizeof arInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ result = VMCIContext_RemoveNotification(cid, arInfo.remoteCID);
+ retval = copy_to_user(&info->result, &result, sizeof result);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case IOCTL_VMCI_CTX_GET_CPT_STATE: {
+ VMCICptBufInfo getInfo;
+ VMCIId cid;
+ char *cptBuf;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_CTX_GET_CPT_STATE only valid for "
+ "contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&getInfo, (void *)ioarg, sizeof getInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ getInfo.result = VMCIContext_GetCheckpointState(cid, getInfo.cptType,
+ &getInfo.bufSize,
+ &cptBuf);
+ if (getInfo.result == VMCI_SUCCESS && getInfo.bufSize) {
+ retval = copy_to_user((void *)(VA)getInfo.cptBuf, cptBuf,
+ getInfo.bufSize);
+ VMCI_FreeKernelMem(cptBuf, getInfo.bufSize);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ }
+ retval = copy_to_user((void *)ioarg, &getInfo, sizeof getInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case IOCTL_VMCI_CTX_SET_CPT_STATE: {
+ VMCICptBufInfo setInfo;
+ VMCIId cid;
+ char *cptBuf;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_CTX_SET_CPT_STATE only valid for "
+ "contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&setInfo, (void *)ioarg, sizeof setInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cptBuf = VMCI_AllocKernelMem(setInfo.bufSize, VMCI_MEMORY_NORMAL);
+ if (cptBuf == NULL) {
+ Log("VMCI: Cannot allocate memory to set cpt state of type %d.\n",
+ setInfo.cptType);
+ retval = -ENOMEM;
+ break;
+ }
+ retval = copy_from_user(cptBuf, (void *)(VA)setInfo.cptBuf,
+ setInfo.bufSize);
+ if (retval) {
+ VMCI_FreeKernelMem(cptBuf, setInfo.bufSize);
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ setInfo.result = VMCIContext_SetCheckpointState(cid, setInfo.cptType,
+ setInfo.bufSize, cptBuf);
+ VMCI_FreeKernelMem(cptBuf, setInfo.bufSize);
+ retval = copy_to_user((void *)ioarg, &setInfo, sizeof setInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+
+ case IOCTL_VMCI_GET_CONTEXT_ID: {
+ VMCIId cid = VMCI_HOST_CONTEXT_ID;
+
+ retval = copy_to_user((void *)ioarg, &cid, sizeof cid);
+ break;
+ }
+
+ case IOCTL_VMCI_SET_NOTIFY: {
+ VMCISetNotifyInfo notifyInfo;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_SET_NOTIFY only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(¬ifyInfo, (void *)ioarg, sizeof notifyInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if ((VA)notifyInfo.notifyUVA != (VA)NULL) {
+ notifyInfo.result = VMCISetupNotify(vmciLinux->context,
+ (VA)notifyInfo.notifyUVA);
+ } else {
+ VMCIUnsetNotifyInt(vmciLinux->context, TRUE);
+ notifyInfo.result = VMCI_SUCCESS;
+ }
+
+ retval = copy_to_user((void *)ioarg, ¬ifyInfo, sizeof notifyInfo);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ break;
+ }
+
+ case IOCTL_VMCI_NOTIFY_RESOURCE: {
+ VMCINotifyResourceInfo info;
+ VMCIId cid;
+
+ if (vmciLinux->userVersion < VMCI_VERSION_NOTIFY) {
+ Log("VMCI: IOCTL_VMCI_NOTIFY_RESOURCE is invalid for current"
+ " VMX versions.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_NOTIFY_RESOURCE is only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&info, (void *)ioarg, sizeof info);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ switch (info.action) {
+ case VMCI_NOTIFY_RESOURCE_ACTION_NOTIFY:
+ if (info.resource == VMCI_NOTIFY_RESOURCE_DOOR_BELL) {
+ info.result = VMCIContext_NotifyDoorbell(cid, info.handle,
+ VMCI_NO_PRIVILEGE_FLAGS);
+ } else {
+ info.result = VMCI_ERROR_UNAVAILABLE;
+ }
+ break;
+ case VMCI_NOTIFY_RESOURCE_ACTION_CREATE:
+ info.result = VMCIContext_DoorbellCreate(cid, info.handle);
+ break;
+ case VMCI_NOTIFY_RESOURCE_ACTION_DESTROY:
+ info.result = VMCIContext_DoorbellDestroy(cid, info.handle);
+ break;
+ default:
+ Log("VMCI: IOCTL_VMCI_NOTIFY_RESOURCE got unknown action %d.\n",
+ info.action);
+ info.result = VMCI_ERROR_INVALID_ARGS;
+ }
+ retval = copy_to_user((void *)ioarg, &info,
+ sizeof info);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ break;
+ }
+
+ case IOCTL_VMCI_NOTIFICATIONS_RECEIVE: {
+ VMCINotificationReceiveInfo info;
+ VMCIHandleArray *dbHandleArray;
+ VMCIHandleArray *qpHandleArray;
+ VMCIId cid;
+
+ if (vmciLinux->ctType != VMCIOBJ_CONTEXT) {
+ Log("VMCI: IOCTL_VMCI_NOTIFICATIONS_RECEIVE is only valid for contexts.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (vmciLinux->userVersion < VMCI_VERSION_NOTIFY) {
+ Log("VMCI: IOCTL_VMCI_NOTIFICATIONS_RECEIVE is not supported for the"
+ " current vmx version.\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ retval = copy_from_user(&info, (void *)ioarg, sizeof info);
+ if (retval) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if ((info.dbHandleBufSize && !info.dbHandleBufUVA) ||
+ (info.qpHandleBufSize && !info.qpHandleBufUVA)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ cid = VMCIContext_GetId(vmciLinux->context);
+ info.result = VMCIContext_ReceiveNotificationsGet(cid,
+ &dbHandleArray,
+ &qpHandleArray);
+ if (info.result == VMCI_SUCCESS) {
+ info.result =
+ VMCICopyHandleArrayToUser((void *)(VA)info.dbHandleBufUVA,
+ &info.dbHandleBufSize,
+ dbHandleArray,
+ &retval);
+ if (info.result == VMCI_SUCCESS && !retval) {
+ info.result =
+ VMCICopyHandleArrayToUser((void *)(VA)info.qpHandleBufUVA,
+ &info.qpHandleBufSize,
+ qpHandleArray,
+ &retval);
+ }
+ if (!retval) {
+ retval = copy_to_user((void *)ioarg, &info, sizeof info);
+ }
+ VMCIContext_ReceiveNotificationsRelease(cid, dbHandleArray, qpHandleArray,
+ info.result == VMCI_SUCCESS && !retval);
+ } else {
+ retval = copy_to_user((void *)ioarg, &info, sizeof info);
+ }
+ break;
+ }
+
+ default:
+ Warning("Unknown ioctl %d\n", iocmd);
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+
+#if defined(HAVE_COMPAT_IOCTL) || defined(HAVE_UNLOCKED_IOCTL)
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * LinuxDriver_UnlockedIoctl --
+ *
+ * Wrapper for LinuxDriver_Ioctl supporting the compat_ioctl and
+ * unlocked_ioctl methods that have signatures different from the
+ * old ioctl. Used as compat_ioctl method for 32bit apps running
+ * on 64bit kernel and for unlocked_ioctl on systems supporting
+ * those. LinuxDriver_Ioctl may safely be called without holding
+ * the BKL.
+ *
+ * Results:
+ * Same as LinuxDriver_Ioctl.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static long
+LinuxDriver_UnlockedIoctl(struct file *filp,
+ u_int iocmd,
+ unsigned long ioarg)
+{
+ return LinuxDriver_Ioctl(NULL, filp, iocmd, ioarg);
+}
+#endif
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIUserVAInvalidPointer --
+ *
+ * Checks if a given user VA is valid or not. Copied from
+ * bora/modules/vmnet/linux/hostif.c:VNetUserIfInvalidPointer(). TODO
+ * libify the common code.
+ *
+ * Results:
+ * TRUE iff invalid.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static INLINE Bool
+VMCIUserVAInvalidPointer(VA uva, // IN:
+ size_t size) // IN:
+{
+ return !access_ok(VERIFY_WRITE, (void *)uva, size);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIUserVALockPage --
+ *
+ * Lock physical page backing a given user VA. Copied from
+ * bora/modules/vmnet/linux/userif.c:UserIfLockPage(). TODO libify the
+ * common code.
+ *
+ * Results:
+ * Pointer to struct page on success, NULL otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static INLINE struct page *
+VMCIUserVALockPage(VA addr) // IN:
+{
+ struct page *page = NULL;
+ int retval;
+
+ down_read(¤t->mm->mmap_sem);
+ retval = get_user_pages(current, current->mm, addr,
+ 1, 1, 0, &page, NULL);
+ up_read(¤t->mm->mmap_sem);
+
+ if (retval != 1) {
+ return NULL;
+ }
+
+ return page;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIMapBoolPtr --
+ *
+ * Lock physical page backing a given user VA and maps it to kernel
+ * address space. The range of the mapped memory should be within a
+ * single page otherwise an error is returned. Copied from
+ * bora/modules/vmnet/linux/userif.c:VNetUserIfMapUint32Ptr(). TODO
+ * libify the common code.
+ *
+ * Results:
+ * 0 on success, negative error code otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static INLINE int
+VMCIMapBoolPtr(VA notifyUVA, // IN:
+ struct page **p, // OUT:
+ Bool **notifyPtr) // OUT:
+{
+ if (VMCIUserVAInvalidPointer(notifyUVA, sizeof **notifyPtr) ||
+ (((notifyUVA + sizeof **notifyPtr - 1) & ~(PAGE_SIZE - 1)) !=
+ (notifyUVA & ~(PAGE_SIZE - 1)))) {
+ return -EINVAL;
+ }
+
+ *p = VMCIUserVALockPage(notifyUVA);
+ if (*p == NULL) {
+ return -EAGAIN;
+ }
+
+ *notifyPtr = (Bool *)((uint8 *)kmap(*p) + (notifyUVA & (PAGE_SIZE - 1)));
+ return 0;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCISetupNotify --
+ *
+ * Sets up a given context for notify to work. Calls VMCIMapBoolPtr()
+ * which maps the notify boolean in user VA in kernel space.
+ *
+ * Results:
+ * VMCI_SUCCESS on success, error code otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int
+VMCISetupNotify(VMCIContext *context, // IN:
+ VA notifyUVA) // IN:
+{
+ int retval;
+
+ if (context->notify) {
+ Warning("VMCI: Notify mechanism is already set up.\n");
+ return VMCI_ERROR_DUPLICATE_ENTRY;
+ }
+
+ retval =
+ VMCIMapBoolPtr(notifyUVA, &context->notifyPage, &context->notify) == 0 ?
+ VMCI_SUCCESS : VMCI_ERROR_GENERIC;
+ if (retval == VMCI_SUCCESS) {
+ VMCIContext_CheckAndSignalNotify(context);
+ }
+
+ return retval;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIUnsetNotifyInt --
+ *
+ * Internal version of VMCIUnsetNotify, that allows for locking
+ * the context before unsetting the notify pointer. If useLock is
+ * TRUE, the context lock is grabbed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+VMCIUnsetNotifyInt(VMCIContext *context, // IN
+ Bool useLock) // IN
+{
+ VMCILockFlags flags;
+
+ if (useLock) {
+ VMCI_GrabLock(&context->lock, &flags);
+ }
+
+ if (context->notifyPage) {
+ struct page *notifyPage = context->notifyPage;
+
+ context->notify = NULL;
+ context->notifyPage = NULL;
+
+ if (useLock) {
+ VMCI_ReleaseLock(&context->lock, flags);
+ }
+
+ kunmap(notifyPage);
+ put_page(notifyPage);
+ } else {
+ if (useLock) {
+ VMCI_ReleaseLock(&context->lock, flags);
+ }
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCIUnsetNotify --
+ *
+ * Reverts actions set up by VMCISetupNotify(). Unmaps and unlocks the
+ * page mapped/locked by VMCISetupNotify().
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+VMCIUnsetNotify(VMCIContext *context) // IN:
+{
+ VMCIUnsetNotifyInt(context, FALSE);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * PCI device support --
+ *
+ * The following functions implement the support for the VMCI
+ * guest device. This includes initializing the device and
+ * interrupt handling.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_guest_init --
+ *
+ * Initializes the VMCI PCI device. The initialization might fail
+ * if there is no VMCI PCI device.
+ *
+ * Results:
+ * 0 on success, other error codes on failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int
+vmci_guest_init(void)
+{
+ int retval;
+
+ /* Initialize guest device data. */
+ compat_mutex_init(&vmci_dev.lock);
+ vmci_dev.intr_type = VMCI_INTR_TYPE_INTX;
+ vmci_dev.exclusive_vectors = FALSE;
+ spin_lock_init(&vmci_dev.dev_spinlock);
+ vmci_dev.enabled = FALSE;
+
+ data_buffer = vmalloc(data_buffer_size);
+ if (!data_buffer) {
+ return -ENOMEM;
+ }
+
+ /* This should be last to make sure we are done initializing. */
+ retval = pci_register_driver(&vmci_driver);
+ if (retval < 0) {
+ vfree(data_buffer);
+ data_buffer = NULL;
+ return retval;
+ }
+
+ return 0;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_enable_msix --
+ *
+ * Enable MSI-X. Try exclusive vectors first, then shared vectors.
+ *
+ * Results:
+ * 0 on success, other error codes on failure.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int
+vmci_enable_msix(struct pci_dev *pdev) // IN
+{
+ int i;
+ int result;
+
+ for (i = 0; i < VMCI_MAX_INTRS; ++i) {
+ vmci_dev.msix_entries[i].entry = i;
+ vmci_dev.msix_entries[i].vector = i;
+ }
+
+ result = pci_enable_msix(pdev, vmci_dev.msix_entries, VMCI_MAX_INTRS);
+ if (!result) {
+ vmci_dev.exclusive_vectors = TRUE;
+ } else if (result > 0) {
+ result = pci_enable_msix(pdev, vmci_dev.msix_entries, 1);
+ }
+ return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_probe_device --
+ *
+ * Most of the initialization at module load time is done here.
+ *
+ * Results:
+ * Returns 0 for success, an error otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int __devinit
+vmci_probe_device(struct pci_dev *pdev, // IN: vmci PCI device
+ const struct pci_device_id *id) // IN: matching device ID
+{
+ unsigned int ioaddr;
+ unsigned int ioaddr_size;
+ unsigned int capabilities;
+ int result;
+
+ printk(KERN_INFO "Probing for vmci/PCI.\n");
+
+ result = pci_enable_device(pdev);
+ if (result) {
+ printk(KERN_ERR "Cannot VMCI device %s: error %d\n",
+ pci_name(pdev), result);
+ return result;
+ }
+ pci_set_master(pdev); /* To enable QueuePair functionality. */
+ ioaddr = pci_resource_start(pdev, 0);
+ ioaddr_size = pci_resource_len(pdev, 0);
+
+ /*
+ * Request I/O region with adjusted base address and size. The adjusted
+ * values are needed and used if we release the region in case of failure.
+ */
+
+ if (!compat_request_region(ioaddr, ioaddr_size, "vmci")) {
+ printk(KERN_INFO "vmci: Another driver already loaded "
+ "for device in slot %s.\n", pci_name(pdev));
+ goto pci_disable;
+ }
+
+ printk(KERN_INFO "Found vmci/PCI at %#x, irq %u.\n", ioaddr, pdev->irq);
+
+ /*
+ * Verify that the VMCI Device supports the capabilities that
+ * we need. If the device is missing capabilities that we would
+ * like to use, check for fallback capabilities and use those
+ * instead (so we can run a new VM on old hosts). Fail the load if
+ * a required capability is missing and there is no fallback.
+ *
+ * Right now, we need datagrams. There are no fallbacks.
+ */
+ capabilities = inl(ioaddr + VMCI_CAPS_ADDR);
+
+ if ((capabilities & VMCI_CAPS_DATAGRAM) == 0) {
+ printk(KERN_ERR "VMCI device does not support datagrams.\n");
+ 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(capabilities, ioaddr + VMCI_CAPS_ADDR);
+
+ /* Device struct initialization. */
+ compat_mutex_lock(&vmci_dev.lock);
+ if (vmci_dev.enabled) {
+ printk(KERN_ERR "VMCI device already enabled.\n");
+ goto unlock;
+ }
+
+ vmci_dev.ioaddr = ioaddr;
+ vmci_dev.ioaddr_size = ioaddr_size;
+
+ /*
+ * 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 remove_bitmap;
+ }
+
+ /* Enable device. */
+ vmci_dev.enabled = TRUE;
+ pci_set_drvdata(pdev, &vmci_dev);
+
+ /*
+ * We do global initialization here because we need datagrams
+ * during VMCIUtil_Init, since it registers for VMCI events. If we
+ * ever support more than one VMCI device we will have to create
+ * seperate LateInit/EarlyExit functions that can be used to do
+ * initialization/cleanup that depends on the device being
+ * accessible. We need to initialize VMCI components before
+ * requesting an irq - the VMCI interrupt handler uses these
+ * components, and it may be invoked once request_irq() has
+ * registered the handler (as the irq line may be shared).
+ */
+ VMCIUtil_Init();
+ VMCIQPGuestEndpoints_Init();
+
+ /*
+ * Enable interrupts. Try MSI-X first, then MSI, and then fallback on
+ * legacy interrupts.
+ */
+ if (!vmci_disable_msix && !vmci_enable_msix(pdev)) {
+ vmci_dev.intr_type = VMCI_INTR_TYPE_MSIX;
+ vmci_dev.irq = vmci_dev.msix_entries[0].vector;
+ } else if (!vmci_disable_msi && !pci_enable_msi(pdev)) {
+ vmci_dev.intr_type = VMCI_INTR_TYPE_MSI;
+ vmci_dev.irq = pdev->irq;
+ } else {
+ vmci_dev.intr_type = VMCI_INTR_TYPE_INTX;
+ vmci_dev.irq = pdev->irq;
+ }
+
+ /* Request IRQ for legacy or MSI interrupts, or for first MSI-X vector. */
+ result = request_irq(vmci_dev.irq, vmci_interrupt, COMPAT_IRQF_SHARED,
+ "vmci", &vmci_dev);
+ if (result) {
+ printk(KERN_ERR "vmci: irq %u in use: %d\n", vmci_dev.irq, result);
+ goto components_exit;
+ }
+
+ /*
+ * For MSI-X with exclusive vectors we need to request an interrupt for each
+ * vector so that we get a separate interrupt handler routine. This allows
+ * us to distinguish between the vectors.
+ */
+
+ if (vmci_dev.exclusive_vectors) {
+ ASSERT(vmci_dev.intr_type == VMCI_INTR_TYPE_MSIX);
+ result = request_irq(vmci_dev.msix_entries[1].vector, vmci_interrupt_bm,
+ 0, "vmci", &vmci_dev);
+ if (result) {
+ printk(KERN_ERR "vmci: irq %u in use: %d\n",
+ vmci_dev.msix_entries[1].vector, result);
+ free_irq(vmci_dev.irq, &vmci_dev);
+ goto components_exit;
+ }
+ }
+
+ printk(KERN_INFO "Registered vmci device.\n");
+
+ compat_mutex_unlock(&vmci_dev.lock);
+
+ /* Enable specific interrupt bits. */
+ 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);
+
+ return 0;
+
+ components_exit:
+ VMCIQPGuestEndpoints_Exit();
+ VMCIUtil_Exit();
+ if (vmci_dev.intr_type == VMCI_INTR_TYPE_MSIX) {
+ pci_disable_msix(pdev);
+ } else if (vmci_dev.intr_type == VMCI_INTR_TYPE_MSI) {
+ pci_disable_msi(pdev);
+ }
+ 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);
+ notification_bitmap = NULL;
+ }
+ release_region(ioaddr, ioaddr_size);
+ pci_disable:
+ pci_disable_device(pdev);
+ return -EBUSY;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_remove_device --
+ *
+ * Cleanup, called for each device on unload.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void __devexit
+vmci_remove_device(struct pci_dev* pdev)
+{
+ struct vmci_device *dev = pci_get_drvdata(pdev);
+
+ printk(KERN_INFO "Removing vmci device\n");
+
+ VMCIQPGuestEndpoints_Exit();
+ VMCIUtil_Exit();
+
+ compat_mutex_lock(&dev->lock);
+ printk(KERN_INFO "Resetting vmci device\n");
+ outl(VMCI_CONTROL_RESET, vmci_dev.ioaddr + VMCI_CONTROL_ADDR);
+
+ /*
+ * Free IRQ and then disable MSI/MSI-X as appropriate. For MSI-X, we might
+ * have multiple vectors, each with their own IRQ, which we must free too.
+ */
+
+ free_irq(dev->irq, dev);
+ if (dev->intr_type == VMCI_INTR_TYPE_MSIX) {
+ if (dev->exclusive_vectors) {
+ free_irq(dev->msix_entries[1].vector, dev);
+ }
+ pci_disable_msix(pdev);
+ } else if (dev->intr_type == VMCI_INTR_TYPE_MSI) {
+ pci_disable_msi(pdev);
+ }
+ dev->exclusive_vectors = FALSE;
+ dev->intr_type = VMCI_INTR_TYPE_INTX;
+
+ release_region(dev->ioaddr, dev->ioaddr_size);
+ dev->enabled = FALSE;
+ if (notification_bitmap) {
+ /*
+ * The device reset above cleared the bitmap state of the
+ * device, so we can safely free it here.
+ */
+
+ vfree(notification_bitmap);
+ notification_bitmap = NULL;
+ }
+
+ printk(KERN_INFO "Unregistered vmci device.\n");
+ compat_mutex_unlock(&dev->lock);
+
+ pci_disable_device(pdev);
+}
+
+
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_interrupt --
+ *
+ * Interrupt handler for legacy or MSI interrupt, or for first MSI-X
+ * interrupt (vector VMCI_INTR_DATAGRAM).
+ *
+ * Results:
+ * COMPAT_IRQ_HANDLED if the interrupt is handled, COMPAT_IRQ_NONE if
+ * not an interrupt.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static compat_irqreturn_t
+vmci_interrupt(int irq, // IN
+ void *clientdata, // IN
+ struct pt_regs *regs) // IN
+#else
+static compat_irqreturn_t
+vmci_interrupt(int irq, // IN
+ void *clientdata) // IN
+#endif
+{
+ vmci_device *dev = clientdata;
+
+ if (dev == NULL) {
+ printk(KERN_DEBUG "vmci_interrupt(): irq %d for unknown device.\n", irq);
+ return COMPAT_IRQ_NONE;
+ }
+
+ /*
+ * If we are using MSI-X with exclusive vectors then we simply schedule
+ * the datagram tasklet, since we know the interrupt was meant for us.
+ * Otherwise we must read the ICR to determine what to do.
+ */
+
+ if (dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors) {
+ tasklet_schedule(&vmci_dg_tasklet);
+ } else {
+ unsigned int icr;
+
+ ASSERT(dev->intr_type == VMCI_INTR_TYPE_INTX ||
+ dev->intr_type == VMCI_INTR_TYPE_MSI);
+
+ /* Acknowledge interrupt and determine what needs doing. */
+ icr = inl(dev->ioaddr + VMCI_ICR_ADDR);
+ if (icr == 0 || icr == 0xffffffff) {
+ return COMPAT_IRQ_NONE;
+ }
+
+ if (icr & VMCI_ICR_DATAGRAM) {
+ 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);
+ }
+ }
+
+ return COMPAT_IRQ_HANDLED;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * vmci_interrupt_bm --
+ *
+ * Interrupt handler for MSI-X interrupt vector VMCI_INTR_NOTIFICATION,
+ * which is for the notification bitmap. Will only get called if we are
+ * using MSI-X with exclusive vectors.
+ *
+ * Results:
+ * COMPAT_IRQ_HANDLED if the interrupt is handled, COMPAT_IRQ_NONE if
+ * not an interrupt.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static compat_irqreturn_t
+vmci_interrupt_bm(int irq, // IN
+ void *clientdata, // IN
+ struct pt_regs *regs) // IN
+#else
+static compat_irqreturn_t
+vmci_interrupt_bm(int irq, // IN
+ void *clientdata) // IN
+#endif
+{
+ vmci_device *dev = clientdata;
+
+ if (dev == NULL) {
+ printk(KERN_DEBUG "vmci_interrupt_bm(): irq %d for unknown device.\n", irq);
+ return COMPAT_IRQ_NONE;
+ }
+
+ /* For MSI-X we can just assume it was meant for us. */
+ ASSERT(dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors);
+ tasklet_schedule(&vmci_bm_tasklet);
+
+ return COMPAT_IRQ_HANDLED;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCI_DeviceEnabled --
+ *
+ * Checks whether the VMCI device is enabled.
+ *
+ * Results:
+ * TRUE if device is enabled, FALSE otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+VMCI_DeviceEnabled(void)
+{
+ Bool retval;
+
+ if (!guestDeviceInit) {
+ return FALSE;
+ }
+
+ compat_mutex_lock(&vmci_dev.lock);
+ retval = vmci_dev.enabled;
+ compat_mutex_unlock(&vmci_dev.lock);
+
+ return retval;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCI_SendDatagram --
+ *
+ * VM to hypervisor call mechanism. We use the standard VMware naming
+ * convention since shared code is calling this function as well.
+ *
+ * Results:
+ * The result of the hypercall.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+VMCI_SendDatagram(VMCIDatagram *dg)
+{
+ unsigned long flags;
+ int result;
+
+ if (!isGuestDriver) {
+ return VMCI_ERROR_UNAVAILABLE;
+ }
+
+ /* Check args. */
+ if (dg == NULL) {
+ return VMCI_ERROR_INVALID_ARGS;
+ }
+
+ /*
+ * Need to acquire spinlock on the device because
+ * the datagram data may be spread over multiple pages and the monitor may
+ * interleave device user rpc calls from multiple VCPUs. Acquiring the
+ * spinlock precludes that possibility. Disabling interrupts to avoid
+ * incoming datagrams during a "rep out" and possibly landing up in this
+ * function.
+ */
+ spin_lock_irqsave(&vmci_dev.dev_spinlock, flags);
+
+ /*
+ * Send the datagram and retrieve the return value from the result register.
+ */
+ __asm__ __volatile__(
+ "cld\n\t"
+ "rep outsb\n\t"
+ : /* No output. */
+ : "d"(vmci_dev.ioaddr + VMCI_DATA_OUT_ADDR),
+ "c"(VMCI_DG_SIZE(dg)), "S"(dg)
+ );
+
+ /*
+ * XXX Should read result high port as well when updating handlers to
+ * return 64bit.
+ */
+ result = inl(vmci_dev.ioaddr + VMCI_RESULT_LOW_ADDR);
+ spin_unlock_irqrestore(&vmci_dev.dev_spinlock, flags);
+
+ return result;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * dispatch_datagrams --
+ *
+ * Reads and dispatches incoming datagrams.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Reads data from the device.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+dispatch_datagrams(unsigned long data)
+{
+ vmci_device *dev = (vmci_device *)data;
+
+ if (dev == NULL) {
+ printk(KERN_DEBUG "vmci: dispatch_datagrams(): no vmci device"
+ "present.\n");
+ return;
+ }
+
+ if (data_buffer == NULL) {
+ printk(KERN_DEBUG "vmci: dispatch_datagrams(): no buffer present.\n");
+ return;
+ }
+
+
+ VMCI_ReadDatagramsFromPort((VMCIIoHandle) 0, dev->ioaddr + VMCI_DATA_IN_ADDR,
+ data_buffer, data_buffer_size);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * 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);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Shared functions --
+ *
+ * Functions shared between host and guest personality.
+ *
+ *----------------------------------------------------------------------
+ */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCI_HasGuestDevice --
+ *
+ * Determines whether the VMCI PCI device has been successfully
+ * initialized.
+ *
+ * Results:
+ * TRUE, if VMCI guest device is operational, FALSE otherwise.
+ *
+ * Side effects:
+ * Reads data from the device.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+VMCI_HasGuestDevice(void)
+{
+ return VMCI_DeviceEnabled();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * VMCI_HasHostDevice --
+ *
+ * Determines whether the VMCI host driver has been successfully
+ * initialized.
+ *
+ * Results:
+ * TRUE, if VMCI host driver is operational, FALSE otherwise.
+ *
+ * Side effects:
+ * Reads data from the device.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+VMCI_HasHostDevice(void)
+{
+ return hostDeviceInit;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Module definitions --
+ *
+ * Implements support for module load/unload.
+ *
+ *----------------------------------------------------------------------
+ */
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * vmci_init --
+ *
+ * linux module entry point. Called by /sbin/insmod command
+ *
+ * Results:
+ * registers a device driver for a major # that depends
+ * on the uid. Add yourself to that list. List is now in
+ * private/driver-private.c.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int __init
+vmci_init(void)
+{
+ int retval;
+
+ DriverLog_Init("/dev/vmci");
+
+ retval = VMCI_SharedInit();
+ if (retval != VMCI_SUCCESS) {
+ Warning(LGPFX"Failed to initialize VMCI common components (err=%d).\n",
+ retval);
+ return -ENOMEM;
+ }
+
+ if (isGuestDriver) {
+ retval = vmci_guest_init();
+ if (retval != 0) {
+ Warning(LGPFX"VMCI PCI device not initialized (err=%d).\n", retval);
+ }
+ guestDeviceInit = (retval == 0);
+ if (guestDeviceInit) {
+ Log(LGPFX"Using guest personality\n");
+ }
+ } else {
+ retval = vmci_host_init();
+ if (retval != 0) {
+ Warning(LGPFX"Unable to initialize host personality (err=%d).\n", retval);
+ }
+ hostDeviceInit = (retval == 0);
+ if (hostDeviceInit) {
+ Log(LGPFX"Using host personality\n");
+ }
+ }
+
+ if (!guestDeviceInit && !hostDeviceInit) {
+ VMCI_SharedCleanup();
+ return -ENODEV;
+ }
+
+ Log("Module %s: initialized\n", linuxState.deviceName);
+
+ return 0;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * vmci_exit --
+ *
+ * Called by /sbin/rmmod
+ *
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void __exit
+vmci_exit(void)
+{
+ int retval;
+
+ if (guestDeviceInit) {
+ pci_unregister_driver(&vmci_driver);
+ vfree(data_buffer);
+ guestDeviceInit = FALSE;
+ }
+
+ if (hostDeviceInit) {
+ unregister_ioctl32_handlers();
+
+ VMCI_HostCleanup();
+
+ retval = misc_deregister(&linuxState.misc);
+ if (retval) {
+ Warning("Module %s: error unregistering\n", linuxState.deviceName);
+ } else {
+ Log("Module %s: unloaded\n", linuxState.deviceName);
+ }
+
+ hostDeviceInit = FALSE;
+ }
+
+ VMCI_SharedCleanup();
+}
+
+
+module_init(vmci_init);
+module_exit(vmci_exit);
+MODULE_DEVICE_TABLE(pci, vmci_ids);
+
+module_param_named(disable_msi, vmci_disable_msi, bool, 0);
+MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
+
+module_param_named(disable_msix, vmci_disable_msix, bool, 0);
+MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default="
+ __stringify(VMCI_DISABLE_MSIX) ")");
+
+
+MODULE_AUTHOR("VMware, Inc.");
+MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface (VMCI).");
+MODULE_VERSION(VMCI_DRIVER_VERSION_STRING);
+MODULE_LICENSE("GPL v2");
+/*
+ * Starting with SLE10sp2, Novell requires that IHVs sign a support agreement
+ * with them and mark their kernel modules as externally supported via a
+ * change to the module header. If this isn't done, the module will not load
+ * by default (i.e., neither mkinitrd nor modprobe will accept it).
+ */
+MODULE_INFO(supported, "external");
+++ /dev/null
-/*********************************************************
- * Copyright (C) 2005 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
- *
- *********************************************************/
-
-/*
- * vmci.c --
- *
- * Linux guest driver for the VMCI device.
- */
-
-#include "driver-config.h"
-
-#include <linux/moduleparam.h>
-#include <linux/poll.h>
-
-#include "compat_init.h"
-#include "compat_interrupt.h"
-#include "compat_ioport.h"
-#include "compat_kernel.h"
-#include "compat_module.h"
-#include "compat_mutex.h"
-#include "compat_page.h"
-#include "compat_pci.h"
-
-#include "driverLog.h"
-
-#include "vm_basic_types.h"
-#include "vm_device_version.h"
-
-#include "vmci_defs.h"
-#include "vmci_infrastructure.h"
-#include "vmci_iocontrols.h"
-#include "vmci_version.h"
-#include "vmciContext.h"
-#include "vmciDatagram.h"
-#include "vmciDoorbell.h"
-#include "vmciDriver.h"
-#include "vmciEvent.h"
-#include "vmciQueuePair.h"
-#include "vmciResource.h"
-
-#define LGPFX "VMCI: "
-#define VMCI_DEVICE_MINOR_NUM 0
-
-/* MSI-X has performance problems in < 2.6.19 */
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
-# define VMCI_DISABLE_MSIX 0
-#else
-# define VMCI_DISABLE_MSIX 1
-#endif
-
-typedef struct vmci_device {
- compat_mutex_t lock;
-
- unsigned int ioaddr;
- unsigned int ioaddr_size;
- unsigned int irq;
- unsigned int intr_type;
- Bool exclusive_vectors;
- struct msix_entry msix_entries[VMCI_MAX_INTRS];
-
- Bool enabled;
- spinlock_t dev_spinlock;
-} vmci_device;
-
-static int vmci_probe_device(struct pci_dev *pdev,
- const struct pci_device_id *id);
-static void vmci_remove_device(struct pci_dev* pdev);
-static int vmci_open(struct inode *inode, struct file *file);
-static int vmci_close(struct inode *inode, struct file *file);
-static long vmci_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
-#if !defined(HAVE_UNLOCKED_IOCTL)
-static int vmci_legacy_ioctl(struct inode *dummy, struct file *file,
- unsigned int cmd, unsigned long arg);
-#endif
-static unsigned int vmci_poll(struct file *file, poll_table *wait);
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
-static compat_irqreturn_t vmci_interrupt(int irq, void *dev_id,
- struct pt_regs * regs);
-static compat_irqreturn_t vmci_interrupt_bm(int irq, void *dev_id,
- struct pt_regs * regs);
-#else
-static compat_irqreturn_t vmci_interrupt(int irq, void *dev_id);
-static compat_irqreturn_t vmci_interrupt_bm(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), },
- { 0 },
-};
-
-static struct file_operations vmci_ops = {
- .owner = THIS_MODULE,
- .open = vmci_open,
- .release = vmci_close,
-#ifdef HAVE_UNLOCKED_IOCTL
- .unlocked_ioctl = vmci_ioctl,
-#else
- .ioctl = vmci_legacy_ioctl,
-#endif
-#ifdef HAVE_COMPAT_IOCTL
- .compat_ioctl = vmci_ioctl,
-#endif
- .poll = vmci_poll,
-};
-
-static struct pci_driver vmci_driver = {
- .name = "vmci",
- .id_table = vmci_ids,
- .probe = vmci_probe_device,
- .remove = __devexit_p(vmci_remove_device),
-};
-
-static vmci_device vmci_dev;
-
-/* We dynamically request the device major number at init time. */
-static int device_major_nr = 0;
-
-static int vmci_disable_msi;
-static int vmci_disable_msix = VMCI_DISABLE_MSIX;
-
-DECLARE_TASKLET(vmci_dg_tasklet, dispatch_datagrams,
- (unsigned long)&vmci_dev);
-
-DECLARE_TASKLET(vmci_bm_tasklet, process_bitmap,
- (unsigned long)&vmci_dev);
-
-/*
- * Allocate a buffer for incoming datagrams globally to avoid repeated
- * allocation in the interrupt handler's atomic context.
- */
-
-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;
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_init --
- *
- * Initialization, called by Linux when the module is loaded.
- *
- * Results:
- * Returns 0 for success, negative errno value otherwise.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static int __init
-vmci_init(void)
-{
- int err;
-
- DriverLog_Init("/dev/vmci");
-
- /* Initialize device data. */
- compat_mutex_init(&vmci_dev.lock);
- vmci_dev.intr_type = VMCI_INTR_TYPE_INTX;
- vmci_dev.exclusive_vectors = FALSE;
- spin_lock_init(&vmci_dev.dev_spinlock);
- vmci_dev.enabled = FALSE;
-
- data_buffer = vmalloc(data_buffer_size);
- if (!data_buffer) {
- return -ENOMEM;
- }
-
- /* Register device node ops. */
- err = register_chrdev(0, "vmci", &vmci_ops);
- if (err < 0) {
- printk(KERN_ERR "Unable to register vmci device\n");
- goto err_free_mem;
- }
-
- device_major_nr = err;
-
- printk("VMCI: Major device number is: %d\n", device_major_nr);
-
- /* This should be last to make sure we are done initializing. */
- err = pci_register_driver(&vmci_driver);
- if (err < 0) {
- goto err_unregister_chrdev;
- }
-
- return 0;
-
-err_unregister_chrdev:
- unregister_chrdev(device_major_nr, "vmci");
-err_free_mem:
- vfree(data_buffer);
- return err;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_exit --
- *
- * Cleanup, called by Linux when the module is unloaded.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static void __exit
-vmci_exit(void)
-{
- pci_unregister_driver(&vmci_driver);
-
- unregister_chrdev(device_major_nr, "vmci");
-
- vfree(data_buffer);
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_enable_msix --
- *
- * Enable MSI-X. Try exclusive vectors first, then shared vectors.
- *
- * Results:
- * 0 on success, other error codes on failure.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static int
-vmci_enable_msix(struct pci_dev *pdev) // IN
-{
- int i;
- int result;
-
- for (i = 0; i < VMCI_MAX_INTRS; ++i) {
- vmci_dev.msix_entries[i].entry = i;
- vmci_dev.msix_entries[i].vector = i;
- }
-
- result = pci_enable_msix(pdev, vmci_dev.msix_entries, VMCI_MAX_INTRS);
- if (!result) {
- vmci_dev.exclusive_vectors = TRUE;
- } else if (result > 0) {
- result = pci_enable_msix(pdev, vmci_dev.msix_entries, 1);
- }
- return result;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_probe_device --
- *
- * Most of the initialization at module load time is done here.
- *
- * Results:
- * Returns 0 for success, an error otherwise.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static int __devinit
-vmci_probe_device(struct pci_dev *pdev, // IN: vmci PCI device
- const struct pci_device_id *id) // IN: matching device ID
-{
- unsigned int ioaddr;
- unsigned int ioaddr_size;
- unsigned int capabilities;
- int result;
-
- printk(KERN_INFO "Probing for vmci/PCI.\n");
-
- result = pci_enable_device(pdev);
- if (result) {
- printk(KERN_ERR "Cannot VMCI device %s: error %d\n",
- pci_name(pdev), result);
- return result;
- }
- pci_set_master(pdev); /* To enable QueuePair functionality. */
- ioaddr = pci_resource_start(pdev, 0);
- ioaddr_size = pci_resource_len(pdev, 0);
-
- /*
- * Request I/O region with adjusted base address and size. The adjusted
- * values are needed and used if we release the region in case of failure.
- */
-
- if (!compat_request_region(ioaddr, ioaddr_size, "vmci")) {
- printk(KERN_INFO "vmci: Another driver already loaded "
- "for device in slot %s.\n", pci_name(pdev));
- goto pci_disable;
- }
-
- printk(KERN_INFO "Found vmci/PCI at %#x, irq %u.\n", ioaddr, pdev->irq);
-
- /*
- * Verify that the VMCI Device supports the capabilities that
- * we need. If the device is missing capabilities that we would
- * like to use, check for fallback capabilities and use those
- * instead (so we can run a new VM on old hosts). Fail the load if
- * a required capability is missing and there is no fallback.
- *
- * Right now, we need datagrams. There are no fallbacks.
- */
- capabilities = inl(ioaddr + VMCI_CAPS_ADDR);
-
- if ((capabilities & VMCI_CAPS_DATAGRAM) == 0) {
- printk(KERN_ERR "VMCI device does not support datagrams.\n");
- 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(capabilities, ioaddr + VMCI_CAPS_ADDR);
-
- /* Device struct initialization. */
- compat_mutex_lock(&vmci_dev.lock);
- if (vmci_dev.enabled) {
- printk(KERN_ERR "VMCI device already enabled.\n");
- goto unlock;
- }
-
- vmci_dev.ioaddr = ioaddr;
- vmci_dev.ioaddr_size = ioaddr_size;
-
- /*
- * 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 remove_bitmap;
- }
-
- /* Enable device. */
- vmci_dev.enabled = TRUE;
- pci_set_drvdata(pdev, &vmci_dev);
-
- /*
- * We do global initialization here because we need datagrams for
- * event init. If we ever support more than one VMCI device we will
- * have to create seperate LateInit/EarlyExit functions that can be
- * used to do initialization/cleanup that depends on the device
- * being accessible. We need to initialize VMCI components before
- * requesting an irq - the VMCI interrupt handler uses these
- * components, and it may be invoked once request_irq() has
- * registered the handler (as the irq line may be shared).
- */
- VMCIResource_Init();
- VMCIContext_Init();
- VMCIDatagram_Init();
- VMCIEvent_Init();
- VMCIUtil_Init();
- VMCIDoorbell_Init();
- VMCIQPGuestEndpoints_Init();
-
- /*
- * Enable interrupts. Try MSI-X first, then MSI, and then fallback on
- * legacy interrupts.
- */
- if (!vmci_disable_msix && !vmci_enable_msix(pdev)) {
- vmci_dev.intr_type = VMCI_INTR_TYPE_MSIX;
- vmci_dev.irq = vmci_dev.msix_entries[0].vector;
- } else if (!vmci_disable_msi && !pci_enable_msi(pdev)) {
- vmci_dev.intr_type = VMCI_INTR_TYPE_MSI;
- vmci_dev.irq = pdev->irq;
- } else {
- vmci_dev.intr_type = VMCI_INTR_TYPE_INTX;
- vmci_dev.irq = pdev->irq;
- }
-
- /* Request IRQ for legacy or MSI interrupts, or for first MSI-X vector. */
- result = request_irq(vmci_dev.irq, vmci_interrupt, COMPAT_IRQF_SHARED,
- "vmci", &vmci_dev);
- if (result) {
- printk(KERN_ERR "vmci: irq %u in use: %d\n", vmci_dev.irq, result);
- goto components_exit;
- }
-
- /*
- * For MSI-X with exclusive vectors we need to request an interrupt for each
- * vector so that we get a separate interrupt handler routine. This allows
- * us to distinguish between the vectors.
- */
-
- if (vmci_dev.exclusive_vectors) {
- ASSERT(vmci_dev.intr_type == VMCI_INTR_TYPE_MSIX);
- result = request_irq(vmci_dev.msix_entries[1].vector, vmci_interrupt_bm,
- 0, "vmci", &vmci_dev);
- if (result) {
- printk(KERN_ERR "vmci: irq %u in use: %d\n",
- vmci_dev.msix_entries[1].vector, result);
- free_irq(vmci_dev.irq, &vmci_dev);
- goto components_exit;
- }
- }
-
- printk(KERN_INFO "Registered vmci device.\n");
-
- compat_mutex_unlock(&vmci_dev.lock);
-
- /* Enable specific interrupt bits. */
- 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);
-
- return 0;
-
- components_exit:
- VMCIQPGuestEndpoints_Exit();
- VMCIDoorbell_Exit();
- VMCIUtil_Exit();
- VMCIEvent_Exit();
- VMCIDatagram_Exit();
- VMCIContext_Exit();
- VMCIResource_Exit();
- if (vmci_dev.intr_type == VMCI_INTR_TYPE_MSIX) {
- pci_disable_msix(pdev);
- } else if (vmci_dev.intr_type == VMCI_INTR_TYPE_MSI) {
- pci_disable_msi(pdev);
- }
- 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:
- pci_disable_device(pdev);
- return -EBUSY;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_remove_device --
- *
- * Cleanup, called for each device on unload.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static void __devexit
-vmci_remove_device(struct pci_dev* pdev)
-{
- struct vmci_device *dev = pci_get_drvdata(pdev);
-
- printk(KERN_INFO "Removing vmci device\n");
-
- VMCIQPGuestEndpoints_Exit();
-
- // XXX Todo add exit/cleanup functions for util, sm, dg, and resource apis.
- VMCIUtil_Exit();
- VMCIEvent_Exit();
-
- compat_mutex_lock(&dev->lock);
- printk(KERN_INFO "Resetting vmci device\n");
- outl(VMCI_CONTROL_RESET, vmci_dev.ioaddr + VMCI_CONTROL_ADDR);
-
- /*
- * Free IRQ and then disable MSI/MSI-X as appropriate. For MSI-X, we might
- * have multiple vectors, each with their own IRQ, which we must free too.
- */
-
- free_irq(dev->irq, dev);
- if (dev->intr_type == VMCI_INTR_TYPE_MSIX) {
- if (dev->exclusive_vectors) {
- free_irq(dev->msix_entries[1].vector, dev);
- }
- pci_disable_msix(pdev);
- } else if (dev->intr_type == VMCI_INTR_TYPE_MSI) {
- pci_disable_msi(pdev);
- }
- dev->exclusive_vectors = FALSE;
- dev->intr_type = VMCI_INTR_TYPE_INTX;
-
- release_region(dev->ioaddr, dev->ioaddr_size);
- dev->enabled = FALSE;
- VMCIDoorbell_Exit();
- if (notification_bitmap) {
- vfree(notification_bitmap);
- }
-
- VMCIDatagram_Exit();
- VMCIContext_Exit();
- VMCIResource_Exit();
-
- printk(KERN_INFO "Unregistered vmci device.\n");
- compat_mutex_unlock(&dev->lock);
-
- pci_disable_device(pdev);
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_open --
- *
- * Open device.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static int
-vmci_open(struct inode *inode, // IN
- struct file *file) // IN
-{
- VMCIObj *devHndl;
- int errcode;
-
- printk(KERN_INFO "Opening vmci device\n");
-
- if (MINOR(inode->i_rdev) != VMCI_DEVICE_MINOR_NUM) {
- return -ENODEV;
- }
-
- compat_mutex_lock(&vmci_dev.lock);
- if (!vmci_dev.enabled) {
- printk(KERN_INFO "Received open on uninitialized vmci device.\n");
- errcode = -ENODEV;
- goto unlock;
- }
-
- /* Do open ... */
- devHndl = VMCI_AllocKernelMem(sizeof *devHndl, VMCI_MEMORY_NORMAL);
- if (!devHndl) {
- printk(KERN_INFO "Failed to create device obj when opening device.\n");
- errcode = -ENOMEM;
- goto unlock;
- }
- devHndl->ptr = NULL;
- devHndl->type = VMCIOBJ_NOT_SET;
- file->private_data = devHndl;
-
- compat_mutex_unlock(&vmci_dev.lock);
-
- return 0;
-
- unlock:
- compat_mutex_unlock(&vmci_dev.lock);
- return errcode;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_close --
- *
- * Close device.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static int
-vmci_close(struct inode *inode, // IN
- struct file *file) // IN
-{
- VMCIObj *devHndl = (VMCIObj *)file->private_data;
-
- if (devHndl) {
- VMCI_FreeKernelMem(devHndl, sizeof *devHndl);
- file->private_data = NULL;
- }
- return 0;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_ioctl --
- *
- * IOCTL interface to device.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static long
-vmci_ioctl(struct file *file, // IN
- unsigned int cmd, // IN
- unsigned long arg) // IN
-{
- return -ENOTTY;
-}
-
-
-#if !defined(HAVE_UNLOCKED_IOCTL)
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_legacy_ioctl --
- *
- * IOCTL interface for kernels that do not have unlocked_ioctl.
- *
- * Results:
- * Negative error code, or per-ioctl result.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static int
-vmci_legacy_ioctl(struct inode *inode, // IN: unused
- struct file *filp, // IN:
- u_int iocmd, // IN:
- unsigned long ioarg) // IN:
-{
- return vmci_ioctl(filp, iocmd, ioarg);
-}
-#endif
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_poll --
- *
- * vmci poll function
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-static unsigned int
-vmci_poll(struct file *file, // IN
- poll_table *wait) // IN
-{
- return 0;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_interrupt --
- *
- * Interrupt handler for legacy or MSI interrupt, or for first MSI-X
- * interrupt (vector VMCI_INTR_DATAGRAM).
- *
- * Results:
- * COMPAT_IRQ_HANDLED if the interrupt is handled, COMPAT_IRQ_NONE if
- * not an interrupt.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
-static compat_irqreturn_t
-vmci_interrupt(int irq, // IN
- void *clientdata, // IN
- struct pt_regs *regs) // IN
-#else
-static compat_irqreturn_t
-vmci_interrupt(int irq, // IN
- void *clientdata) // IN
-#endif
-{
- vmci_device *dev = clientdata;
-
- if (dev == NULL) {
- printk(KERN_DEBUG "vmci_interrupt(): irq %d for unknown device.\n", irq);
- return COMPAT_IRQ_NONE;
- }
-
- /*
- * If we are using MSI-X with exclusive vectors then we simply schedule
- * the datagram tasklet, since we know the interrupt was meant for us.
- * Otherwise we must read the ICR to determine what to do.
- */
-
- if (dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors) {
- tasklet_schedule(&vmci_dg_tasklet);
- } else {
- unsigned int icr;
-
- ASSERT(dev->intr_type == VMCI_INTR_TYPE_INTX ||
- dev->intr_type == VMCI_INTR_TYPE_MSI);
-
- /* Acknowledge interrupt and determine what needs doing. */
- icr = inl(dev->ioaddr + VMCI_ICR_ADDR);
- if (icr == 0 || icr == 0xffffffff) {
- return COMPAT_IRQ_NONE;
- }
-
- if (icr & VMCI_ICR_DATAGRAM) {
- 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);
- }
- }
-
- return COMPAT_IRQ_HANDLED;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * vmci_interrupt_bm --
- *
- * Interrupt handler for MSI-X interrupt vector VMCI_INTR_NOTIFICATION,
- * which is for the notification bitmap. Will only get called if we are
- * using MSI-X with exclusive vectors.
- *
- * Results:
- * COMPAT_IRQ_HANDLED if the interrupt is handled, COMPAT_IRQ_NONE if
- * not an interrupt.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
-static compat_irqreturn_t
-vmci_interrupt_bm(int irq, // IN
- void *clientdata, // IN
- struct pt_regs *regs) // IN
-#else
-static compat_irqreturn_t
-vmci_interrupt_bm(int irq, // IN
- void *clientdata) // IN
-#endif
-{
- vmci_device *dev = clientdata;
-
- if (dev == NULL) {
- printk(KERN_DEBUG "vmci_interrupt_bm(): irq %d for unknown device.\n", irq);
- return COMPAT_IRQ_NONE;
- }
-
- /* For MSI-X we can just assume it was meant for us. */
- ASSERT(dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors);
- tasklet_schedule(&vmci_bm_tasklet);
-
- return COMPAT_IRQ_HANDLED;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * VMCI_DeviceEnabled --
- *
- * Checks whether the VMCI device is enabled.
- *
- * Results:
- * TRUE if device is enabled, FALSE otherwise.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-Bool
-VMCI_DeviceEnabled(void)
-{
- Bool retval;
-
- compat_mutex_lock(&vmci_dev.lock);
- retval = vmci_dev.enabled;
- compat_mutex_unlock(&vmci_dev.lock);
-
- return retval;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * VMCI_SendDatagram --
- *
- * VM to hypervisor call mechanism. We use the standard VMware naming
- * convention since shared code is calling this function as well.
- *
- * Results:
- * The result of the hypercall.
- *
- * Side effects:
- * None.
- *
- *-----------------------------------------------------------------------------
- */
-
-int
-VMCI_SendDatagram(VMCIDatagram *dg)
-{
- unsigned long flags;
- int result;
-
- /* Check args. */
- if (dg == NULL) {
- return VMCI_ERROR_INVALID_ARGS;
- }
-
- /*
- * Need to acquire spinlock on the device because
- * the datagram data may be spread over multiple pages and the monitor may
- * interleave device user rpc calls from multiple VCPUs. Acquiring the
- * spinlock precludes that possibility. Disabling interrupts to avoid
- * incoming datagrams during a "rep out" and possibly landing up in this
- * function.
- */
- spin_lock_irqsave(&vmci_dev.dev_spinlock, flags);
-
- /*
- * Send the datagram and retrieve the return value from the result register.
- */
- __asm__ __volatile__(
- "cld\n\t"
- "rep outsb\n\t"
- : /* No output. */
- : "d"(vmci_dev.ioaddr + VMCI_DATA_OUT_ADDR),
- "c"(VMCI_DG_SIZE(dg)), "S"(dg)
- );
-
- /*
- * XXX Should read result high port as well when updating handlers to
- * return 64bit.
- */
- result = inl(vmci_dev.ioaddr + VMCI_RESULT_LOW_ADDR);
- spin_unlock_irqrestore(&vmci_dev.dev_spinlock, flags);
-
- return result;
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * dispatch_datagrams --
- *
- * Reads and dispatches incoming datagrams.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Reads data from the device.
- *
- *-----------------------------------------------------------------------------
- */
-
-void
-dispatch_datagrams(unsigned long data)
-{
- vmci_device *dev = (vmci_device *)data;
-
- if (dev == NULL) {
- printk(KERN_DEBUG "vmci: dispatch_datagrams(): no vmci device"
- "present.\n");
- return;
- }
-
- if (data_buffer == NULL) {
- printk(KERN_DEBUG "vmci: dispatch_datagrams(): no buffer present.\n");
- return;
- }
-
-
- VMCI_ReadDatagramsFromPort((VMCIIoHandle) 0, dev->ioaddr + VMCI_DATA_IN_ADDR,
- data_buffer, data_buffer_size);
-}
-
-
-/*
- *-----------------------------------------------------------------------------
- *
- * 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);
-}
-
-
-/*
- *------------------------------------------------------------------------------
- *
- * VMCIUnsetNotify --
- *
- * Stub. Not called in the guest driver (yet).
- *
- * Result:
- * Always VMCI_ERROR_GENERIC.
- *
- *------------------------------------------------------------------------------
- */
-
-void
-VMCIUnsetNotify(VMCIContext *context)
-{
-}
-
-
-module_init(vmci_init);
-module_exit(vmci_exit);
-MODULE_DEVICE_TABLE(pci, vmci_ids);
-
-module_param_named(disable_msi, vmci_disable_msi, bool, 0);
-MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
-
-module_param_named(disable_msix, vmci_disable_msix, bool, 0);
-MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default="
- __stringify(VMCI_DISABLE_MSIX) ")");
-
-/* Module information. */
-MODULE_AUTHOR("VMware, Inc.");
-MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface");
-MODULE_VERSION(VMCI_DRIVER_VERSION_STRING);
-MODULE_LICENSE("GPL v2");
-/*
- * Starting with SLE10sp2, Novell requires that IHVs sign a support agreement
- * with them and mark their kernel modules as externally supported via a
- * change to the module header. If this isn't done, the module will not load
- * by default (i.e., neither mkinitrd nor modprobe will accept it).
- */
-MODULE_INFO(supported, "external");