/* $Id: VBoxPci-linux.c 69500 2017-10-28 15:14:05Z vboxsync $ */ /** @file * VBoxPci - PCI Driver (Host), Linux Specific Code. */ /* * Copyright (C) 2011-2017 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the * VirtualBox OSE 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. */ /********************************************************************************************************************************* * 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 #include #include #include #include #include #include "VBoxPciInternal.h" #ifdef VBOX_WITH_IOMMU # include # include # include # if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) && \ (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 41) || LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0)) # include # else # include # endif # if LINUX_VERSION_CODE < KERNEL_VERSION(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 VBoxPciLinuxInit(void); static void 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 LINUX_VERSION_CODE >= KERNEL_VERSION(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) # define PCI_DEV_GET_SLOT(bus, devfn) pci_get_bus_and_slot(bus, devfn) #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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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(get_ds()); ret = vfs_write(file, data, size, &offset); 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 : ""); 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)); } PCI_DEV_PUT(pPciDev); pPciDev = NULL; if (fDetach) { char* szCmdBuf; char* szFileBuf; struct file* pFile; int iCmdLen; const int cMaxBuf = 128; const struct cred *pOldCreds; struct cred *pNewCreds; /* * 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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) pNewCreds = prepare_creds(); if (!pNewCreds) goto done; # if LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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; const struct cred *pOldCreds; struct cred *pNewCreds; 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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) pNewCreds = prepare_creds(); if (!pNewCreds) goto done; # if LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(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) { /* For now no caching, try to optimize later. */ RTR0PTR R0PtrMapping = ioremap_nocache(pci_resource_start(pPciDev, iRegion), pci_resource_len(pPciDev, iRegion)); if (R0PtrMapping != NIL_RTR0PTR) pIns->aRegionR0Mapping[iRegion] = R0PtrMapping; else { vbpci_printk(KERN_DEBUG, pPciDev, "ioremap_nocache() failed\n"); 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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) IRQF_SHARED, # else SA_SHIRQ, # endif #else /* We don't allow interrupts sharing */ /* XXX overhaul */ # if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) && LINUX_VERSION_CODE < KERNEL_VERSION(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 }