summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c')
-rw-r--r--src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c1186
1 files changed, 1186 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c b/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c
new file mode 100644
index 00000000..b5967a66
--- /dev/null
+++ b/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c
@@ -0,0 +1,1186 @@
+/* $Id: VBoxPci-linux.c $ */
+/** @file
+ * VBoxPci - PCI Driver (Host), Linux Specific Code.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "the-linux-kernel.h"
+#include "version-generated.h"
+#include "revision-generated.h"
+#include "product-generated.h"
+
+#define LOG_GROUP LOG_GROUP_DEV_PCI_RAW
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <iprt/process.h>
+#include <iprt/initterm.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+
+#include "../VBoxPciInternal.h"
+
+#ifdef VBOX_WITH_IOMMU
+# include <linux/dmar.h>
+# include <linux/intel-iommu.h>
+# include <linux/pci.h>
+# if RTLNX_VER_MAX(3,1,0) && \
+ (RTLNX_VER_MAX(2,6,41) || RTLNX_VER_MIN(3,0,0))
+# include <asm/amd_iommu.h>
+# else
+# include <linux/amd-iommu.h>
+# endif
+# if RTLNX_VER_MAX(3,2,0)
+# define IOMMU_PRESENT() iommu_found()
+# define IOMMU_DOMAIN_ALLOC() iommu_domain_alloc()
+# else
+# define IOMMU_PRESENT() iommu_present(&pci_bus_type)
+# define IOMMU_DOMAIN_ALLOC() iommu_domain_alloc(&pci_bus_type)
+# endif
+#endif /* VBOX_WITH_IOMMU */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int __init VBoxPciLinuxInit(void);
+static void __exit VBoxPciLinuxUnload(void);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static VBOXRAWPCIGLOBALS g_VBoxPciGlobals;
+
+module_init(VBoxPciLinuxInit);
+module_exit(VBoxPciLinuxUnload);
+
+MODULE_AUTHOR(VBOX_VENDOR);
+MODULE_DESCRIPTION(VBOX_PRODUCT " PCI access Driver");
+MODULE_LICENSE("GPL");
+#ifdef MODULE_VERSION
+MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV));
+#endif
+
+
+#if RTLNX_VER_MIN(2,6,20)
+# define PCI_DEV_GET(v,d,p) pci_get_device(v,d,p)
+# define PCI_DEV_PUT(x) pci_dev_put(x)
+#if RTLNX_VER_MIN(4,17,0)
+/* assume the domain number to be zero - exactly the same assumption of
+ * pci_get_bus_and_slot()
+ */
+# define PCI_DEV_GET_SLOT(bus, devfn) pci_get_domain_bus_and_slot(0, bus, devfn)
+#else
+# define PCI_DEV_GET_SLOT(bus, devfn) pci_get_bus_and_slot(bus, devfn)
+#endif
+#else
+# define PCI_DEV_GET(v,d,p) pci_find_device(v,d,p)
+# define PCI_DEV_PUT(x) do { } while (0)
+# define PCI_DEV_GET_SLOT(bus, devfn) pci_find_slot(bus, devfn)
+#endif
+
+/**
+ * Name of module used to attach to the host PCI device, when
+ * PCI device passthrough is used.
+ */
+#define PCI_STUB_MODULE "pci-stub"
+/* For some reasons my kernel names module for find_module() this way,
+ * while device name seems to be above one.
+ */
+#define PCI_STUB_MODULE_NAME "pci_stub"
+
+/**
+ * Our driver name.
+ */
+#define DRIVER_NAME "vboxpci"
+
+/*
+ * Currently we keep the device bound to pci stub driver, so
+ * dev_printk() &co would report that instead of our name. They also
+ * expect non-NULL dev pointer in older kernels.
+ */
+#define vbpci_printk(level, pdev, format, arg...) \
+ printk(level DRIVER_NAME "%s%s: " format, \
+ pdev ? " " : "", pdev ? pci_name(pdev) : "", \
+ ## arg)
+
+
+/**
+ * Initialize module.
+ *
+ * @returns appropriate status code.
+ */
+static int __init VBoxPciLinuxInit(void)
+{
+ int rc;
+ /*
+ * Initialize IPRT.
+ */
+ rc = RTR0Init(0);
+
+ if (RT_FAILURE(rc))
+ goto error;
+
+
+ LogRel(("VBoxPciLinuxInit\n"));
+
+ RT_ZERO(g_VBoxPciGlobals);
+
+ rc = vboxPciInit(&g_VBoxPciGlobals);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("cannot do VBoxPciInit: %Rc\n", rc));
+ goto error;
+ }
+
+#if defined(CONFIG_PCI_STUB)
+ /* nothing to do, pci_stub module part of the kernel */
+ g_VBoxPciGlobals.fPciStubModuleAvail = true;
+
+#elif defined(CONFIG_PCI_STUB_MODULE)
+ if (request_module(PCI_STUB_MODULE) == 0)
+ {
+# if RTLNX_VER_MIN(2,6,30)
+ /* find_module() is static before Linux 2.6.30 */
+ mutex_lock(&module_mutex);
+ g_VBoxPciGlobals.pciStubModule = find_module(PCI_STUB_MODULE_NAME);
+ mutex_unlock(&module_mutex);
+ if (g_VBoxPciGlobals.pciStubModule)
+ {
+ if (try_module_get(g_VBoxPciGlobals.pciStubModule))
+ g_VBoxPciGlobals.fPciStubModuleAvail = true;
+ }
+ else
+ printk(KERN_INFO "vboxpci: find_module %s failed\n", PCI_STUB_MODULE);
+# endif
+ }
+ else
+ printk(KERN_INFO "vboxpci: cannot load %s\n", PCI_STUB_MODULE);
+
+#else
+ printk(KERN_INFO "vboxpci: %s module not available, cannot detach PCI devices\n",
+ PCI_STUB_MODULE);
+#endif
+
+#ifdef VBOX_WITH_IOMMU
+ if (IOMMU_PRESENT())
+ printk(KERN_INFO "vboxpci: IOMMU found\n");
+ else
+ printk(KERN_INFO "vboxpci: IOMMU not found (not registered)\n");
+#else
+ printk(KERN_INFO "vboxpci: IOMMU not found (not compiled)\n");
+#endif
+
+ return 0;
+
+ error:
+ return -RTErrConvertToErrno(rc);
+}
+
+/**
+ * Unload the module.
+ */
+static void __exit VBoxPciLinuxUnload(void)
+{
+ LogRel(("VBoxPciLinuxLinuxUnload\n"));
+
+ /*
+ * Undo the work done during start (in reverse order).
+ */
+ vboxPciShutdown(&g_VBoxPciGlobals);
+
+ RTR0Term();
+
+ if (g_VBoxPciGlobals.pciStubModule)
+ {
+ module_put(g_VBoxPciGlobals.pciStubModule);
+ g_VBoxPciGlobals.pciStubModule = NULL;
+ }
+
+ Log(("VBoxPciLinuxUnload - done\n"));
+}
+
+static int vboxPciLinuxDevRegisterWithIommu(PVBOXRAWPCIINS pIns)
+{
+#ifdef VBOX_WITH_IOMMU
+ int rc = VINF_SUCCESS;
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ PVBOXRAWPCIDRVVM pData = VBOX_DRV_VMDATA(pIns);
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (RT_LIKELY(pData))
+ {
+ if (RT_LIKELY(pData->pIommuDomain))
+ {
+ /** @todo KVM checks IOMMU_CAP_CACHE_COHERENCY and sets
+ * flag IOMMU_CACHE later used when mapping physical
+ * addresses, which could improve performance.
+ */
+ int rcLnx = iommu_attach_device(pData->pIommuDomain, &pPciDev->dev);
+ if (!rcLnx)
+ {
+ vbpci_printk(KERN_DEBUG, pPciDev, "attached to IOMMU\n");
+ pIns->fIommuUsed = true;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ vbpci_printk(KERN_DEBUG, pPciDev, "failed to attach to IOMMU, error %d\n", rcLnx);
+ rc = VERR_INTERNAL_ERROR;
+ }
+ }
+ else
+ {
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "cannot attach to IOMMU, no domain\n");
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ vbpci_printk(KERN_DEBUG, pPciDev, "cannot attach to IOMMU, no VM data\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+static int vboxPciLinuxDevUnregisterWithIommu(PVBOXRAWPCIINS pIns)
+{
+#ifdef VBOX_WITH_IOMMU
+ int rc = VINF_SUCCESS;
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ PVBOXRAWPCIDRVVM pData = VBOX_DRV_VMDATA(pIns);
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (RT_LIKELY(pData))
+ {
+ if (RT_LIKELY(pData->pIommuDomain))
+ {
+ if (pIns->fIommuUsed)
+ {
+ iommu_detach_device(pData->pIommuDomain, &pIns->pPciDev->dev);
+ vbpci_printk(KERN_DEBUG, pPciDev, "detached from IOMMU\n");
+ pIns->fIommuUsed = false;
+ }
+ }
+ else
+ {
+ vbpci_printk(KERN_DEBUG, pPciDev,
+ "cannot detach from IOMMU, no domain\n");
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ vbpci_printk(KERN_DEBUG, pPciDev,
+ "cannot detach from IOMMU, no VM data\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+static int vboxPciLinuxDevReset(PVBOXRAWPCIINS pIns)
+{
+ int rc = VINF_SUCCESS;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (RT_LIKELY(pIns->pPciDev))
+ {
+#if RTLNX_VER_MIN(2,6,28)
+ if (pci_reset_function(pIns->pPciDev))
+ {
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev,
+ "pci_reset_function() failed\n");
+ rc = VERR_INTERNAL_ERROR;
+ }
+#else
+ rc = VERR_NOT_SUPPORTED;
+#endif
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+
+static struct file* vboxPciFileOpen(const char* path, int flags)
+{
+ struct file* filp = NULL;
+ int err = 0;
+
+ filp = filp_open(path, flags, 0);
+
+ if (IS_ERR(filp))
+ {
+ err = PTR_ERR(filp);
+ printk(KERN_DEBUG "vboxPciFileOpen: error %d\n", err);
+ return NULL;
+ }
+
+ if (!filp->f_op || !filp->f_op->write)
+ {
+ printk(KERN_DEBUG "Not writable FS\n");
+ filp_close(filp, NULL);
+ return NULL;
+ }
+
+ return filp;
+}
+
+static void vboxPciFileClose(struct file* file)
+{
+ filp_close(file, NULL);
+}
+
+static int vboxPciFileWrite(struct file* file, unsigned long long offset, unsigned char* data, unsigned int size)
+{
+ int ret;
+ mm_segment_t fs_save;
+
+ fs_save = get_fs();
+ set_fs(KERNEL_DS);
+#if RTLNX_VER_MIN(4,14,0)
+ ret = kernel_write(file, data, size, &offset);
+#else
+ ret = vfs_write(file, data, size, &offset);
+#endif
+ set_fs(fs_save);
+ if (ret < 0)
+ printk(KERN_DEBUG "vboxPciFileWrite: error %d\n", ret);
+
+ return ret;
+}
+
+static int vboxPciLinuxDevDetachHostDriver(PVBOXRAWPCIINS pIns)
+{
+ struct pci_dev *pPciDev = NULL;
+ uint8_t uBus = (pIns->HostPciAddress) >> 8;
+ uint8_t uDevFn = (pIns->HostPciAddress) & 0xff;
+ const char* currentDriver;
+ uint16_t uVendor, uDevice;
+ bool fDetach = 0;
+
+ if (!g_VBoxPciGlobals.fPciStubModuleAvail)
+ {
+ printk(KERN_INFO "vboxpci: stub module %s not detected: cannot detach\n",
+ PCI_STUB_MODULE);
+ return VERR_ACCESS_DENIED;
+ }
+
+ pPciDev = PCI_DEV_GET_SLOT(uBus, uDevFn);
+
+ if (!pPciDev)
+ {
+ printk(KERN_INFO "vboxpci: device at %02x:%02x.%d not found\n",
+ uBus, uDevFn>>3, uDevFn&7);
+ return VERR_NOT_FOUND;
+ }
+
+ uVendor = pPciDev->vendor;
+ uDevice = pPciDev->device;
+
+ currentDriver = pPciDev->driver ? pPciDev->driver->name : NULL;
+
+ printk(KERN_DEBUG "vboxpci: detected device: %04x:%04x at %02x:%02x.%d, driver %s\n",
+ uVendor, uDevice, uBus, uDevFn>>3, uDevFn&7,
+ currentDriver ? currentDriver : "<none>");
+
+ fDetach = (currentDriver == NULL || (strcmp(currentDriver, PCI_STUB_MODULE) != 0));
+
+ /* Init previous driver data. */
+ pIns->szPrevDriver[0] = '\0';
+
+ if (fDetach && currentDriver)
+ {
+ /* Dangerous: if device name for some reasons contains slashes - arbitrary file could be written to. */
+ if (strchr(currentDriver, '/') != 0)
+ {
+ printk(KERN_DEBUG "vboxpci: ERROR: %s contains invalid symbols\n", currentDriver);
+ return VERR_ACCESS_DENIED;
+ }
+ /** @todo RTStrCopy not exported. */
+ strncpy(pIns->szPrevDriver, currentDriver, sizeof(pIns->szPrevDriver) - 1);
+ pIns->szPrevDriver[sizeof(pIns->szPrevDriver) - 1] = '\0';
+ }
+
+ PCI_DEV_PUT(pPciDev);
+ pPciDev = NULL;
+
+ if (fDetach)
+ {
+ char* szCmdBuf;
+ char* szFileBuf;
+ struct file* pFile;
+ int iCmdLen;
+ const int cMaxBuf = 128;
+#if RTLNX_VER_MIN(2,6,29)
+ const struct cred *pOldCreds;
+ struct cred *pNewCreds;
+#endif
+
+ /*
+ * Now perform kernel analog of:
+ *
+ * echo -n "10de 040a" > /sys/bus/pci/drivers/pci-stub/new_id
+ * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/nvidia/unbind
+ * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/pci-stub/bind
+ *
+ * We do this way, as this interface is presumingly more stable than
+ * in-kernel ones.
+ */
+ szCmdBuf = kmalloc(cMaxBuf, GFP_KERNEL);
+ szFileBuf = kmalloc(cMaxBuf, GFP_KERNEL);
+ if (!szCmdBuf || !szFileBuf)
+ goto done;
+
+ /* Somewhat ugly hack - override current credentials */
+#if RTLNX_VER_MIN(2,6,29)
+ pNewCreds = prepare_creds();
+ if (!pNewCreds)
+ goto done;
+
+# if RTLNX_VER_MIN(3,5,0)
+ pNewCreds->fsuid = GLOBAL_ROOT_UID;
+# else
+ pNewCreds->fsuid = 0;
+# endif
+ pOldCreds = override_creds(pNewCreds);
+#endif
+
+ RTStrPrintf(szFileBuf, cMaxBuf,
+ "/sys/bus/pci/drivers/%s/new_id",
+ PCI_STUB_MODULE);
+ pFile = vboxPciFileOpen(szFileBuf, O_WRONLY);
+ if (pFile)
+ {
+ iCmdLen = RTStrPrintf(szCmdBuf, cMaxBuf,
+ "%04x %04x",
+ uVendor, uDevice);
+ /* Don't write trailing \0 */
+ vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen);
+ vboxPciFileClose(pFile);
+ }
+ else
+ printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf);
+
+ iCmdLen = RTStrPrintf(szCmdBuf, cMaxBuf,
+ "0000:%02x:%02x.%d",
+ uBus, uDevFn>>3, uDevFn&7);
+
+ /* Unbind if bound to smth */
+ if (pIns->szPrevDriver[0])
+ {
+ RTStrPrintf(szFileBuf, cMaxBuf,
+ "/sys/bus/pci/drivers/%s/unbind",
+ pIns->szPrevDriver);
+ pFile = vboxPciFileOpen(szFileBuf, O_WRONLY);
+ if (pFile)
+ {
+
+ /* Don't write trailing \0 */
+ vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen);
+ vboxPciFileClose(pFile);
+ }
+ else
+ printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf);
+ }
+
+ RTStrPrintf(szFileBuf, cMaxBuf,
+ "/sys/bus/pci/drivers/%s/bind",
+ PCI_STUB_MODULE);
+ pFile = vboxPciFileOpen(szFileBuf, O_WRONLY);
+ if (pFile)
+ {
+ /* Don't write trailing \0 */
+ vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen);
+ vboxPciFileClose(pFile);
+ }
+ else
+ printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf);
+
+#if RTLNX_VER_MIN(2,6,29)
+ revert_creds(pOldCreds);
+ put_cred(pNewCreds);
+#endif
+
+ done:
+ kfree(szCmdBuf);
+ kfree(szFileBuf);
+ }
+
+ return 0;
+}
+
+static int vboxPciLinuxDevReattachHostDriver(PVBOXRAWPCIINS pIns)
+{
+ struct pci_dev *pPciDev = pIns->pPciDev;
+
+ if (!pPciDev)
+ return VINF_SUCCESS;
+
+ if (pIns->szPrevDriver[0])
+ {
+ char* szCmdBuf;
+ char* szFileBuf;
+ struct file* pFile;
+ int iCmdLen;
+ const int cMaxBuf = 128;
+#if RTLNX_VER_MIN(2,6,29)
+ const struct cred *pOldCreds;
+ struct cred *pNewCreds;
+#endif
+ uint8_t uBus = (pIns->HostPciAddress) >> 8;
+ uint8_t uDevFn = (pIns->HostPciAddress) & 0xff;
+
+ vbpci_printk(KERN_DEBUG, pPciDev,
+ "reattaching old host driver %s\n", pIns->szPrevDriver);
+ /*
+ * Now perform kernel analog of:
+ *
+ * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/pci-stub/unbind
+ * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/nvidia/bind
+ */
+ szCmdBuf = kmalloc(cMaxBuf, GFP_KERNEL);
+ szFileBuf = kmalloc(cMaxBuf, GFP_KERNEL);
+
+ if (!szCmdBuf || !szFileBuf)
+ goto done;
+
+ iCmdLen = RTStrPrintf(szCmdBuf, cMaxBuf,
+ "0000:%02x:%02x.%d",
+ uBus, uDevFn>>3, uDevFn&7);
+
+ /* Somewhat ugly hack - override current credentials */
+#if RTLNX_VER_MIN(2,6,29)
+ pNewCreds = prepare_creds();
+ if (!pNewCreds)
+ goto done;
+
+# if RTLNX_VER_MIN(3,5,0)
+ pNewCreds->fsuid = GLOBAL_ROOT_UID;
+# else
+ pNewCreds->fsuid = 0;
+# endif
+ pOldCreds = override_creds(pNewCreds);
+#endif
+ RTStrPrintf(szFileBuf, cMaxBuf,
+ "/sys/bus/pci/drivers/%s/unbind",
+ PCI_STUB_MODULE);
+ pFile = vboxPciFileOpen(szFileBuf, O_WRONLY);
+ if (pFile)
+ {
+
+ /* Don't write trailing \0 */
+ vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen);
+ vboxPciFileClose(pFile);
+ }
+ else
+ printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf);
+
+ RTStrPrintf(szFileBuf, cMaxBuf,
+ "/sys/bus/pci/drivers/%s/bind",
+ pIns->szPrevDriver);
+ pFile = vboxPciFileOpen(szFileBuf, O_WRONLY);
+ if (pFile)
+ {
+
+ /* Don't write trailing \0 */
+ vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen);
+ vboxPciFileClose(pFile);
+ pIns->szPrevDriver[0] = '\0';
+ }
+ else
+ printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf);
+
+#if RTLNX_VER_MIN(2,6,29)
+ revert_creds(pOldCreds);
+ put_cred(pNewCreds);
+#endif
+
+ done:
+ kfree(szCmdBuf);
+ kfree(szFileBuf);
+ }
+
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) vboxPciOsDevInit(PVBOXRAWPCIINS pIns, uint32_t fFlags)
+{
+ struct pci_dev *pPciDev = NULL;
+ int rc = VINF_SUCCESS;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (fFlags & PCIRAWDRIVERRFLAG_DETACH_HOST_DRIVER)
+ {
+ rc = vboxPciLinuxDevDetachHostDriver(pIns);
+ if (RT_FAILURE(rc))
+ {
+ printk(KERN_DEBUG "Cannot detach host driver for device %x: %d\n",
+ pIns->HostPciAddress, rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pPciDev = PCI_DEV_GET_SLOT((pIns->HostPciAddress) >> 8,
+ (pIns->HostPciAddress) & 0xff);
+
+ if (RT_LIKELY(pPciDev))
+ {
+ int rcLnx = pci_enable_device(pPciDev);
+
+ if (!rcLnx)
+ {
+ pIns->pPciDev = pPciDev;
+ vbpci_printk(KERN_DEBUG, pPciDev, "%s\n", __func__);
+
+#if RTLNX_VER_MIN(2,6,1)
+ if (pci_enable_msi(pPciDev) == 0)
+ pIns->fMsiUsed = true;
+#endif
+
+ /** @todo
+ * pci_enable_msix(pPciDev, entries, nvec)
+ *
+ * In fact, if device uses interrupts, and cannot be forced to use MSI or MSI-X
+ * we have to refuse using it, as we cannot work with shared PCI interrupts (unless we're lucky
+ * to grab unshared PCI interrupt).
+ */
+ }
+ else
+ rc = RTErrConvertFromErrno(RT_ABS(rcLnx));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+
+DECLHIDDEN(int) vboxPciOsDevDeinit(PVBOXRAWPCIINS pIns, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ vbpci_printk(KERN_DEBUG, pPciDev, "%s\n", __func__);
+
+ if (RT_LIKELY(pPciDev))
+ {
+ int iRegion;
+ for (iRegion = 0; iRegion < 7; ++iRegion)
+ {
+ if (pIns->aRegionR0Mapping[iRegion])
+ {
+ iounmap(pIns->aRegionR0Mapping[iRegion]);
+ pIns->aRegionR0Mapping[iRegion] = 0;
+ pci_release_region(pPciDev, iRegion);
+ }
+ }
+
+ vboxPciLinuxDevUnregisterWithIommu(pIns);
+
+#if RTLNX_VER_MIN(2,6,1)
+ if (pIns->fMsiUsed)
+ pci_disable_msi(pPciDev);
+#endif
+ // pci_disable_msix(pPciDev);
+ pci_disable_device(pPciDev);
+ vboxPciLinuxDevReattachHostDriver(pIns);
+
+ PCI_DEV_PUT(pPciDev);
+ pIns->pPciDev = NULL;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+
+DECLHIDDEN(int) vboxPciOsDevDestroy(PVBOXRAWPCIINS pIns)
+{
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) vboxPciOsDevGetRegionInfo(PVBOXRAWPCIINS pIns,
+ int32_t iRegion,
+ RTHCPHYS *pRegionStart,
+ uint64_t *pu64RegionSize,
+ bool *pfPresent,
+ uint32_t *pfFlags)
+{
+ int rc = VINF_SUCCESS;
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (RT_LIKELY(pPciDev))
+ {
+ int fFlags = pci_resource_flags(pPciDev, iRegion);
+
+ if ( ((fFlags & (IORESOURCE_MEM | IORESOURCE_IO)) == 0)
+ || ((fFlags & IORESOURCE_DISABLED) != 0))
+ {
+ *pfPresent = false;
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t fResFlags = 0;
+ *pfPresent = true;
+
+ if (fFlags & IORESOURCE_MEM)
+ fResFlags |= PCIRAW_ADDRESS_SPACE_MEM;
+
+ if (fFlags & IORESOURCE_IO)
+ fResFlags |= PCIRAW_ADDRESS_SPACE_IO;
+
+#ifdef IORESOURCE_MEM_64
+ if (fFlags & IORESOURCE_MEM_64)
+ fResFlags |= PCIRAW_ADDRESS_SPACE_BAR64;
+#endif
+
+ if (fFlags & IORESOURCE_PREFETCH)
+ fResFlags |= PCIRAW_ADDRESS_SPACE_MEM_PREFETCH;
+
+ *pfFlags = fResFlags;
+ *pRegionStart = pci_resource_start(pPciDev, iRegion);
+ *pu64RegionSize = pci_resource_len (pPciDev, iRegion);
+
+ vbpci_printk(KERN_DEBUG, pPciDev,
+ "region %d: %s %llx+%lld\n",
+ iRegion, (fFlags & IORESOURCE_MEM) ? "mmio" : "pio",
+ *pRegionStart, *pu64RegionSize);
+ }
+ }
+ else
+ {
+ *pfPresent = false;
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+
+DECLHIDDEN(int) vboxPciOsDevMapRegion(PVBOXRAWPCIINS pIns,
+ int32_t iRegion,
+ RTHCPHYS RegionStart,
+ uint64_t u64RegionSize,
+ uint32_t fFlags,
+ RTR0PTR *pRegionBase)
+{
+ int rc = VINF_SUCCESS;
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (!pPciDev || iRegion < 0 || iRegion > 0)
+ {
+ if (pPciDev)
+ vbpci_printk(KERN_DEBUG, pPciDev, "invalid region %d\n", iRegion);
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ vbpci_printk(KERN_DEBUG, pPciDev, "reg=%d start=%llx size=%lld\n",
+ iRegion, RegionStart, u64RegionSize);
+
+ if ( (pci_resource_flags(pPciDev, iRegion) & IORESOURCE_IO)
+ || RegionStart != pci_resource_start(pPciDev, iRegion)
+ || u64RegionSize != pci_resource_len(pPciDev, iRegion))
+ {
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * XXX: Current code never calls unmap. To avoid leaking mappings
+ * only request and map resources once.
+ */
+ if (!pIns->aRegionR0Mapping[iRegion])
+ {
+ int rcLnx;
+ *pRegionBase = pIns->aRegionR0Mapping[iRegion];
+
+ rcLnx = pci_request_region(pPciDev, iRegion, "vboxpci");
+ if (!rcLnx)
+ {
+#if RTLNX_VER_MIN(2,6,25)
+ /*
+ * ioremap() defaults to no caching since the 2.6 kernels.
+ * ioremap_nocache() has been removed finally in 5.6-rc1.
+ */
+ RTR0PTR R0PtrMapping = ioremap(pci_resource_start(pPciDev, iRegion),
+ pci_resource_len(pPciDev, iRegion));
+#else /* KERNEL_VERSION < 2.6.25 */
+ /* For now no caching, try to optimize later. */
+ RTR0PTR R0PtrMapping = ioremap_nocache(pci_resource_start(pPciDev, iRegion),
+ pci_resource_len(pPciDev, iRegion));
+#endif /* KERNEL_VERSION < 2.6.25 */
+ if (R0PtrMapping != NIL_RTR0PTR)
+ pIns->aRegionR0Mapping[iRegion] = R0PtrMapping;
+ else
+ {
+#if RTLNX_VER_MIN(2,6,25)
+ vbpci_printk(KERN_DEBUG, pPciDev, "ioremap() failed\n");
+#else
+ vbpci_printk(KERN_DEBUG, pPciDev, "ioremap_nocache() failed\n");
+#endif
+ pci_release_region(pPciDev, iRegion);
+ rc = VERR_MAP_FAILED;
+ }
+ }
+ else
+ rc = VERR_RESOURCE_BUSY;
+ }
+
+ if (RT_SUCCESS(rc))
+ *pRegionBase = pIns->aRegionR0Mapping[iRegion];
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+
+DECLHIDDEN(int) vboxPciOsDevUnmapRegion(PVBOXRAWPCIINS pIns,
+ int32_t iRegion,
+ RTHCPHYS RegionStart,
+ uint64_t u64RegionSize,
+ RTR0PTR RegionBase)
+{
+ /* XXX: Current code never calls unmap. */
+ return VERR_NOT_IMPLEMENTED;
+}
+
+DECLHIDDEN(int) vboxPciOsDevPciCfgWrite(PVBOXRAWPCIINS pIns, uint32_t Register, PCIRAWMEMLOC *pValue)
+{
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ int rc = VINF_SUCCESS;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (RT_LIKELY(pPciDev))
+ {
+ switch (pValue->cb)
+ {
+ case 1:
+ pci_write_config_byte(pPciDev, Register, pValue->u.u8);
+ break;
+ case 2:
+ pci_write_config_word(pPciDev, Register, pValue->u.u16);
+ break;
+ case 4:
+ pci_write_config_dword(pPciDev, Register, pValue->u.u32);
+ break;
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+
+DECLHIDDEN(int) vboxPciOsDevPciCfgRead(PVBOXRAWPCIINS pIns, uint32_t Register, PCIRAWMEMLOC *pValue)
+{
+ struct pci_dev *pPciDev = pIns->pPciDev;
+ int rc = VINF_SUCCESS;
+
+ if (RT_LIKELY(pPciDev))
+ {
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ switch (pValue->cb)
+ {
+ case 1:
+ pci_read_config_byte(pPciDev, Register, &pValue->u.u8);
+ break;
+ case 2:
+ pci_read_config_word(pPciDev, Register, &pValue->u.u16);
+ break;
+ case 4:
+ pci_read_config_dword(pPciDev, Register, &pValue->u.u32);
+ break;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+/**
+ * Interrupt service routine.
+ *
+ * @returns In 2.6 we indicate whether we've handled the IRQ or not.
+ *
+ * @param iIrq The IRQ number.
+ * @param pvDevId The device ID, a pointer to PVBOXRAWPCIINS.
+ * @param pRegs Register set. Removed in 2.6.19.
+ */
+#if RTLNX_VER_MIN(2,6,19) && !defined(DOXYGEN_RUNNING)
+static irqreturn_t vboxPciOsIrqHandler(int iIrq, void *pvDevId)
+#else
+static irqreturn_t vboxPciOsIrqHandler(int iIrq, void *pvDevId, struct pt_regs *pRegs)
+#endif
+{
+ PVBOXRAWPCIINS pIns = (PVBOXRAWPCIINS)pvDevId;
+ bool fTaken = true;
+
+ if (pIns && pIns->IrqHandler.pfnIrqHandler)
+ fTaken = pIns->IrqHandler.pfnIrqHandler(pIns->IrqHandler.pIrqContext, iIrq);
+#ifndef VBOX_WITH_SHARED_PCI_INTERRUPTS
+ /* If we don't allow interrupts sharing, we consider all interrupts as non-shared, thus targetted to us. */
+ fTaken = true;
+#endif
+
+ return fTaken;
+}
+
+DECLHIDDEN(int) vboxPciOsDevRegisterIrqHandler(PVBOXRAWPCIINS pIns, PFNRAWPCIISR pfnHandler, void* pIrqContext, int32_t *piHostIrq)
+{
+ int rc;
+ int32_t iIrq = pIns->pPciDev->irq;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (iIrq == 0)
+ {
+ vbpci_printk(KERN_NOTICE, pIns->pPciDev, "no irq assigned\n");
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return VERR_INVALID_PARAMETER;
+ }
+
+ rc = request_irq(iIrq,
+ vboxPciOsIrqHandler,
+#ifdef VBOX_WITH_SHARED_PCI_INTERRUPTS
+ /* Allow interrupts sharing. */
+# if RTLNX_VER_MIN(2,6,20)
+ IRQF_SHARED,
+# else
+ SA_SHIRQ,
+# endif
+
+#else
+
+ /* We don't allow interrupts sharing */
+ /* XXX overhaul */
+# if RTLNX_VER_MIN(2,6,20) && RTLNX_VER_MAX(4,1,0)
+ IRQF_DISABLED, /* keep irqs disabled when calling the action handler */
+# else
+ 0,
+# endif
+#endif
+ DRIVER_NAME,
+ pIns);
+ if (rc)
+ {
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev,
+ "could not request irq %d, error %d\n", iIrq, rc);
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return VERR_RESOURCE_BUSY;
+ }
+
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "got irq %d\n", iIrq);
+ *piHostIrq = iIrq;
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) vboxPciOsDevUnregisterIrqHandler(PVBOXRAWPCIINS pIns, int32_t iHostIrq)
+{
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "freeing irq %d\n", iHostIrq);
+ free_irq(iHostIrq, pIns);
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return VINF_SUCCESS;
+}
+
+DECLHIDDEN(int) vboxPciOsDevPowerStateChange(PVBOXRAWPCIINS pIns, PCIRAWPOWERSTATE aState)
+{
+ int rc;
+
+ switch (aState)
+ {
+ case PCIRAW_POWER_ON:
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_ON\n");
+ /* Reset device, just in case. */
+ vboxPciLinuxDevReset(pIns);
+ /* register us with IOMMU */
+ rc = vboxPciLinuxDevRegisterWithIommu(pIns);
+ break;
+ case PCIRAW_POWER_RESET:
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_RESET\n");
+ rc = vboxPciLinuxDevReset(pIns);
+ break;
+ case PCIRAW_POWER_OFF:
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_OFF\n");
+ /* unregister us from IOMMU */
+ rc = vboxPciLinuxDevUnregisterWithIommu(pIns);
+ break;
+ case PCIRAW_POWER_SUSPEND:
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_SUSPEND\n");
+ rc = VINF_SUCCESS;
+ /// @todo what do we do here?
+ break;
+ case PCIRAW_POWER_RESUME:
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_RESUME\n");
+ rc = VINF_SUCCESS;
+ /// @todo what do we do here?
+ break;
+ default:
+ vbpci_printk(KERN_DEBUG, pIns->pPciDev, "unknown power state %u\n", aState);
+ /* to make compiler happy */
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ return rc;
+}
+
+
+#ifdef VBOX_WITH_IOMMU
+/** Callback for FNRAWPCICONTIGPHYSMEMINFO. */
+static DECLCALLBACK(int) vboxPciOsContigMemInfo(PRAWPCIPERVM pVmCtx, RTHCPHYS HostStart, RTGCPHYS GuestStart,
+ uint64_t cMemSize, PCIRAWMEMINFOACTION Action)
+{
+ struct iommu_domain* domain = ((PVBOXRAWPCIDRVVM)(pVmCtx->pDriverData))->pIommuDomain;
+ int rc = VINF_SUCCESS;
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ switch (Action)
+ {
+ case PCIRAW_MEMINFO_MAP:
+ {
+ int flags, r;
+
+ if (iommu_iova_to_phys(domain, GuestStart))
+ break;
+
+ flags = IOMMU_READ | IOMMU_WRITE;
+ /** @todo flags |= IOMMU_CACHE; */
+
+ r = iommu_map(domain, GuestStart, HostStart, get_order(cMemSize), flags);
+ if (r)
+ {
+ printk(KERN_ERR "vboxPciOsContigMemInfo:"
+ "iommu failed to map pfn=%llx\n", HostStart);
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+ rc = VINF_SUCCESS;
+ break;
+ }
+ case PCIRAW_MEMINFO_UNMAP:
+ {
+ int order;
+ order = iommu_unmap(domain, GuestStart, get_order(cMemSize));
+ NOREF(order);
+ break;
+ }
+
+ default:
+ printk(KERN_DEBUG "Unsupported action: %d\n", (int)Action);
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+ return rc;
+}
+#endif
+
+DECLHIDDEN(int) vboxPciOsInitVm(PVBOXRAWPCIDRVVM pThis, PVM pVM, PRAWPCIPERVM pVmData)
+{
+ int rc = VINF_SUCCESS;
+
+#ifdef VBOX_WITH_IOMMU
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (IOMMU_PRESENT())
+ {
+ pThis->pIommuDomain = IOMMU_DOMAIN_ALLOC();
+ if (!pThis->pIommuDomain)
+ {
+ vbpci_printk(KERN_DEBUG, NULL, "cannot allocate IOMMU domain\n");
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ pVmData->pfnContigMemInfo = vboxPciOsContigMemInfo;
+
+ vbpci_printk(KERN_DEBUG, NULL, "created IOMMU domain %p\n",
+ pThis->pIommuDomain);
+ }
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+#endif
+ return rc;
+}
+
+DECLHIDDEN(void) vboxPciOsDeinitVm(PVBOXRAWPCIDRVVM pThis, PVM pVM)
+{
+#ifdef VBOX_WITH_IOMMU
+ IPRT_LINUX_SAVE_EFL_AC();
+
+ if (pThis->pIommuDomain)
+ {
+ vbpci_printk(KERN_DEBUG, NULL, "freeing IOMMU domain %p\n",
+ pThis->pIommuDomain);
+ iommu_domain_free(pThis->pIommuDomain);
+ pThis->pIommuDomain = NULL;
+ }
+
+ IPRT_LINUX_RESTORE_EFL_AC();
+#endif
+}