/* $Id $ */ /** @file * VBoxPci - PCI card passthrough support (Host), Common Code. */ /* * Copyright (C) 2011-2014 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. */ /** @page pg_rawpci VBoxPci - host PCI support * * This is a kernel module that works as host proxy between guest and * PCI hardware. * */ #define LOG_GROUP LOG_GROUP_DEV_PCI_RAW #include #include #include #include #include #include #include #include #include #include #include "VBoxPciInternal.h" #define DEVPORT_2_VBOXRAWPCIINS(pPort) \ ( (PVBOXRAWPCIINS)((uint8_t *)pPort - RT_OFFSETOF(VBOXRAWPCIINS, DevPort)) ) /** * Implements the SUPDRV component factor interface query method. * * @returns Pointer to an interface. NULL if not supported. * * @param pSupDrvFactory Pointer to the component factory registration structure. * @param pSession The session - unused. * @param pszInterfaceUuid The factory interface id. */ static DECLCALLBACK(void *) vboxPciQueryFactoryInterface(PCSUPDRVFACTORY pSupDrvFactory, PSUPDRVSESSION pSession, const char *pszInterfaceUuid) { PVBOXRAWPCIGLOBALS pGlobals = (PVBOXRAWPCIGLOBALS)((uint8_t *)pSupDrvFactory - RT_OFFSETOF(VBOXRAWPCIGLOBALS, SupDrvFactory)); /* * Convert the UUID strings and compare them. */ RTUUID UuidReq; int rc = RTUuidFromStr(&UuidReq, pszInterfaceUuid); if (RT_SUCCESS(rc)) { if (!RTUuidCompareStr(&UuidReq, RAWPCIFACTORY_UUID_STR)) { ASMAtomicIncS32(&pGlobals->cFactoryRefs); return &pGlobals->RawPciFactory; } } else Log(("VBoxRawPci: rc=%Rrc, uuid=%s\n", rc, pszInterfaceUuid)); return NULL; } DECLINLINE(int) vboxPciDevLock(PVBOXRAWPCIINS pThis) { #ifdef VBOX_WITH_SHARED_PCI_INTERRUPTS RTSpinlockAcquire(pThis->hSpinlock); return VINF_SUCCESS; #else int rc = RTSemFastMutexRequest(pThis->hFastMtx); AssertRC(rc); return rc; #endif } DECLINLINE(void) vboxPciDevUnlock(PVBOXRAWPCIINS pThis) { #ifdef VBOX_WITH_SHARED_PCI_INTERRUPTS RTSpinlockRelease(pThis->hSpinlock); #else RTSemFastMutexRelease(pThis->hFastMtx); #endif } DECLINLINE(int) vboxPciVmLock(PVBOXRAWPCIDRVVM pThis) { int rc = RTSemFastMutexRequest(pThis->hFastMtx); AssertRC(rc); return rc; } DECLINLINE(void) vboxPciVmUnlock(PVBOXRAWPCIDRVVM pThis) { RTSemFastMutexRelease(pThis->hFastMtx); } DECLINLINE(int) vboxPciGlobalsLock(PVBOXRAWPCIGLOBALS pGlobals) { int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); return rc; } DECLINLINE(void) vboxPciGlobalsUnlock(PVBOXRAWPCIGLOBALS pGlobals) { RTSemFastMutexRelease(pGlobals->hFastMtx); } static PVBOXRAWPCIINS vboxPciFindInstanceLocked(PVBOXRAWPCIGLOBALS pGlobals, uint32_t iHostAddress) { PVBOXRAWPCIINS pCur; for (pCur = pGlobals->pInstanceHead; pCur != NULL; pCur = pCur->pNext) { if (iHostAddress == pCur->HostPciAddress) return pCur; } return NULL; } static void vboxPciUnlinkInstanceLocked(PVBOXRAWPCIGLOBALS pGlobals, PVBOXRAWPCIINS pToUnlink) { if (pGlobals->pInstanceHead == pToUnlink) pGlobals->pInstanceHead = pToUnlink->pNext; else { PVBOXRAWPCIINS pCur; for (pCur = pGlobals->pInstanceHead; pCur != NULL; pCur = pCur->pNext) { if (pCur->pNext == pToUnlink) { pCur->pNext = pToUnlink->pNext; break; } } } pToUnlink->pNext = NULL; } DECLHIDDEN(void) vboxPciDevCleanup(PVBOXRAWPCIINS pThis) { pThis->DevPort.pfnDeinit(&pThis->DevPort, 0); if (pThis->hFastMtx) { RTSemFastMutexDestroy(pThis->hFastMtx); pThis->hFastMtx = NIL_RTSEMFASTMUTEX; } if (pThis->hSpinlock) { RTSpinlockDestroy(pThis->hSpinlock); pThis->hSpinlock = NIL_RTSPINLOCK; } vboxPciGlobalsLock(pThis->pGlobals); vboxPciUnlinkInstanceLocked(pThis->pGlobals, pThis); vboxPciGlobalsUnlock(pThis->pGlobals); } /** * @copydoc RAWPCIDEVPORT:: pfnInit */ static DECLCALLBACK(int) vboxPciDevInit(PRAWPCIDEVPORT pPort, uint32_t fFlags) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevInit(pThis, fFlags); vboxPciDevUnlock(pThis); return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnDeinit */ static DECLCALLBACK(int) vboxPciDevDeinit(PRAWPCIDEVPORT pPort, uint32_t fFlags) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); if (pThis->IrqHandler.pfnIrqHandler) { vboxPciOsDevUnregisterIrqHandler(pThis, pThis->IrqHandler.iHostIrq); pThis->IrqHandler.iHostIrq = 0; pThis->IrqHandler.pfnIrqHandler = NULL; } rc = vboxPciOsDevDeinit(pThis, fFlags); vboxPciDevUnlock(pThis); return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnDestroy */ static DECLCALLBACK(int) vboxPciDevDestroy(PRAWPCIDEVPORT pPort) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; rc = vboxPciOsDevDestroy(pThis); if (rc == VINF_SUCCESS) { if (pThis->hFastMtx) { RTSemFastMutexDestroy(pThis->hFastMtx); pThis->hFastMtx = NIL_RTSEMFASTMUTEX; } if (pThis->hSpinlock) { RTSpinlockDestroy(pThis->hSpinlock); pThis->hSpinlock = NIL_RTSPINLOCK; } vboxPciGlobalsLock(pThis->pGlobals); vboxPciUnlinkInstanceLocked(pThis->pGlobals, pThis); vboxPciGlobalsUnlock(pThis->pGlobals); RTMemFree(pThis); } return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnGetRegionInfo */ static DECLCALLBACK(int) vboxPciDevGetRegionInfo(PRAWPCIDEVPORT pPort, int32_t iRegion, RTHCPHYS *pRegionStart, uint64_t *pu64RegionSize, bool *pfPresent, uint32_t *pfFlags) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevGetRegionInfo(pThis, iRegion, pRegionStart, pu64RegionSize, pfPresent, pfFlags); vboxPciDevUnlock(pThis); return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnMapRegion */ static DECLCALLBACK(int) vboxPciDevMapRegion(PRAWPCIDEVPORT pPort, int32_t iRegion, RTHCPHYS RegionStart, uint64_t u64RegionSize, int32_t fFlags, RTR0PTR *pRegionBase) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevMapRegion(pThis, iRegion, RegionStart, u64RegionSize, fFlags, pRegionBase); vboxPciDevUnlock(pThis); return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnUnapRegion */ static DECLCALLBACK(int) vboxPciDevUnmapRegion(PRAWPCIDEVPORT pPort, int32_t iRegion, RTHCPHYS RegionStart, uint64_t u64RegionSize, RTR0PTR RegionBase) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevUnmapRegion(pThis, iRegion, RegionStart, u64RegionSize, RegionBase); vboxPciDevUnlock(pThis); return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnPciCfgRead */ static DECLCALLBACK(int) vboxPciDevPciCfgRead(PRAWPCIDEVPORT pPort, uint32_t Register, PCIRAWMEMLOC *pValue) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevPciCfgRead(pThis, Register, pValue); vboxPciDevUnlock(pThis); return rc; } /** * @copydoc RAWPCIDEVPORT:: pfnPciCfgWrite */ static DECLCALLBACK(int) vboxPciDevPciCfgWrite(PRAWPCIDEVPORT pPort, uint32_t Register, PCIRAWMEMLOC *pValue) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevPciCfgWrite(pThis, Register, pValue); vboxPciDevUnlock(pThis); return rc; } static DECLCALLBACK(int) vboxPciDevRegisterIrqHandler(PRAWPCIDEVPORT pPort, PFNRAWPCIISR pfnHandler, void* pIrqContext, PCIRAWISRHANDLE *phIsr) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; int32_t iHostIrq = 0; if (pfnHandler == NULL) return VERR_INVALID_PARAMETER; vboxPciDevLock(pThis); if (pThis->IrqHandler.pfnIrqHandler) { rc = VERR_ALREADY_EXISTS; } else { rc = vboxPciOsDevRegisterIrqHandler(pThis, pfnHandler, pIrqContext, &iHostIrq); if (RT_SUCCESS(rc)) { *phIsr = 0xcafe0000; pThis->IrqHandler.iHostIrq = iHostIrq; pThis->IrqHandler.pfnIrqHandler = pfnHandler; pThis->IrqHandler.pIrqContext = pIrqContext; } } vboxPciDevUnlock(pThis); return rc; } static DECLCALLBACK(int) vboxPciDevUnregisterIrqHandler(PRAWPCIDEVPORT pPort, PCIRAWISRHANDLE hIsr) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; if (hIsr != 0xcafe0000) return VERR_INVALID_PARAMETER; vboxPciDevLock(pThis); rc = vboxPciOsDevUnregisterIrqHandler(pThis, pThis->IrqHandler.iHostIrq); if (RT_SUCCESS(rc)) { pThis->IrqHandler.pfnIrqHandler = NULL; pThis->IrqHandler.pIrqContext = NULL; pThis->IrqHandler.iHostIrq = 0; } vboxPciDevUnlock(pThis); return rc; } static DECLCALLBACK(int) vboxPciDevPowerStateChange(PRAWPCIDEVPORT pPort, PCIRAWPOWERSTATE aState, uint64_t *pu64Param) { PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); int rc; vboxPciDevLock(pThis); rc = vboxPciOsDevPowerStateChange(pThis, aState); switch (aState) { case PCIRAW_POWER_ON: /* * Let virtual device know about VM caps. */ *pu64Param = VBOX_DRV_VMDATA(pThis)->pPerVmData->fVmCaps; break; default: pu64Param = 0; break; } vboxPciDevUnlock(pThis); return rc; } /** * Creates a new instance. * * @returns VBox status code. * @param pGlobals The globals. * @param pszName The instance name. * @param ppDevPort Where to store the pointer to our port interface. */ static int vboxPciNewInstance(PVBOXRAWPCIGLOBALS pGlobals, uint32_t u32HostAddress, uint32_t fFlags, PRAWPCIPERVM pVmCtx, PRAWPCIDEVPORT *ppDevPort, uint32_t *pfDevFlags) { int rc; PVBOXRAWPCIINS pNew = (PVBOXRAWPCIINS)RTMemAllocZ(sizeof(*pNew)); if (!pNew) return VERR_NO_MEMORY; pNew->pGlobals = pGlobals; pNew->hSpinlock = NIL_RTSPINLOCK; pNew->cRefs = 1; pNew->pNext = NULL; pNew->HostPciAddress = u32HostAddress; pNew->pVmCtx = pVmCtx; pNew->DevPort.u32Version = RAWPCIDEVPORT_VERSION; pNew->DevPort.pfnInit = vboxPciDevInit; pNew->DevPort.pfnDeinit = vboxPciDevDeinit; pNew->DevPort.pfnDestroy = vboxPciDevDestroy; pNew->DevPort.pfnGetRegionInfo = vboxPciDevGetRegionInfo; pNew->DevPort.pfnMapRegion = vboxPciDevMapRegion; pNew->DevPort.pfnUnmapRegion = vboxPciDevUnmapRegion; pNew->DevPort.pfnPciCfgRead = vboxPciDevPciCfgRead; pNew->DevPort.pfnPciCfgWrite = vboxPciDevPciCfgWrite; pNew->DevPort.pfnPciCfgRead = vboxPciDevPciCfgRead; pNew->DevPort.pfnPciCfgWrite = vboxPciDevPciCfgWrite; pNew->DevPort.pfnRegisterIrqHandler = vboxPciDevRegisterIrqHandler; pNew->DevPort.pfnUnregisterIrqHandler = vboxPciDevUnregisterIrqHandler; pNew->DevPort.pfnPowerStateChange = vboxPciDevPowerStateChange; pNew->DevPort.u32VersionEnd = RAWPCIDEVPORT_VERSION; rc = RTSpinlockCreate(&pNew->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxPCI"); if (RT_SUCCESS(rc)) { rc = RTSemFastMutexCreate(&pNew->hFastMtx); if (RT_SUCCESS(rc)) { rc = pNew->DevPort.pfnInit(&pNew->DevPort, fFlags); if (RT_SUCCESS(rc)) { *ppDevPort = &pNew->DevPort; pNew->pNext = pGlobals->pInstanceHead; pGlobals->pInstanceHead = pNew; } else { RTSemFastMutexDestroy(pNew->hFastMtx); RTSpinlockDestroy(pNew->hSpinlock); RTMemFree(pNew); } } } return rc; } /** * @copydoc RAWPCIFACTORY::pfnCreateAndConnect */ static DECLCALLBACK(int) vboxPciFactoryCreateAndConnect(PRAWPCIFACTORY pFactory, uint32_t u32HostAddress, uint32_t fFlags, PRAWPCIPERVM pVmCtx, PRAWPCIDEVPORT *ppDevPort, uint32_t *pfDevFlags) { PVBOXRAWPCIGLOBALS pGlobals = (PVBOXRAWPCIGLOBALS)((uint8_t *)pFactory - RT_OFFSETOF(VBOXRAWPCIGLOBALS, RawPciFactory)); int rc; LogFlow(("vboxPciFactoryCreateAndConnect: PCI=%x fFlags=%#x\n", u32HostAddress, fFlags)); Assert(pGlobals->cFactoryRefs > 0); rc = vboxPciGlobalsLock(pGlobals); AssertRCReturn(rc, rc); /* First search if there's no existing instance with same host device * address - if so - we cannot continue. */ if (vboxPciFindInstanceLocked(pGlobals, u32HostAddress) != NULL) { rc = VERR_RESOURCE_BUSY; goto unlock; } rc = vboxPciNewInstance(pGlobals, u32HostAddress, fFlags, pVmCtx, ppDevPort, pfDevFlags); unlock: vboxPciGlobalsUnlock(pGlobals); return rc; } /** * @copydoc RAWPCIFACTORY::pfnRelease */ static DECLCALLBACK(void) vboxPciFactoryRelease(PRAWPCIFACTORY pFactory) { PVBOXRAWPCIGLOBALS pGlobals = (PVBOXRAWPCIGLOBALS)((uint8_t *)pFactory - RT_OFFSETOF(VBOXRAWPCIGLOBALS, RawPciFactory)); int32_t cRefs = ASMAtomicDecS32(&pGlobals->cFactoryRefs); Assert(cRefs >= 0); NOREF(cRefs); LogFlow(("vboxPciFactoryRelease: cRefs=%d (new)\n", cRefs)); } /** * @copydoc RAWPCIFACTORY::pfnInitVm */ static DECLCALLBACK(int) vboxPciFactoryInitVm(PRAWPCIFACTORY pFactory, PVM pVM, PRAWPCIPERVM pVmData) { PVBOXRAWPCIDRVVM pThis = (PVBOXRAWPCIDRVVM)RTMemAllocZ(sizeof(VBOXRAWPCIDRVVM)); int rc; if (!pThis) return VERR_NO_MEMORY; rc = RTSemFastMutexCreate(&pThis->hFastMtx); if (RT_SUCCESS(rc)) { rc = vboxPciOsInitVm(pThis, pVM, pVmData); if (RT_SUCCESS(rc)) { #ifdef VBOX_WITH_IOMMU /* If IOMMU notification routine in pVmData->pfnContigMemInfo is set - we have functional IOMMU hardware. */ if (pVmData->pfnContigMemInfo) pVmData->fVmCaps |= PCIRAW_VMFLAGS_HAS_IOMMU; #endif pThis->pPerVmData = pVmData; pVmData->pDriverData = pThis; return VINF_SUCCESS; } RTSemFastMutexDestroy(pThis->hFastMtx); pThis->hFastMtx = NIL_RTSEMFASTMUTEX; RTMemFree(pThis); } return rc; } /** * @copydoc RAWPCIFACTORY::pfnDeinitVm */ static DECLCALLBACK(void) vboxPciFactoryDeinitVm(PRAWPCIFACTORY pFactory, PVM pVM, PRAWPCIPERVM pPciData) { if (pPciData->pDriverData) { PVBOXRAWPCIDRVVM pThis = (PVBOXRAWPCIDRVVM)pPciData->pDriverData; #ifdef VBOX_WITH_IOMMU /* If we have IOMMU, need to unmap all guest's physical pages from IOMMU on VM termination. */ #endif vboxPciOsDeinitVm(pThis, pVM); if (pThis->hFastMtx) { RTSemFastMutexDestroy(pThis->hFastMtx); pThis->hFastMtx = NIL_RTSEMFASTMUTEX; } RTMemFree(pThis); pPciData->pDriverData = NULL; } } static bool vboxPciCanUnload(PVBOXRAWPCIGLOBALS pGlobals) { int rc = vboxPciGlobalsLock(pGlobals); bool fRc = !pGlobals->pInstanceHead && pGlobals->cFactoryRefs <= 0; vboxPciGlobalsUnlock(pGlobals); AssertRC(rc); return fRc; } static int vboxPciInitIdc(PVBOXRAWPCIGLOBALS pGlobals) { int rc; Assert(!pGlobals->fIDCOpen); /* * Establish a connection to SUPDRV and register our component factory. */ rc = SUPR0IdcOpen(&pGlobals->SupDrvIDC, 0 /* iReqVersion = default */, 0 /* iMinVersion = default */, NULL, NULL, NULL); if (RT_SUCCESS(rc)) { rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); if (RT_SUCCESS(rc)) { pGlobals->fIDCOpen = true; Log(("VBoxRawPci: pSession=%p\n", SUPR0IdcGetSession(&pGlobals->SupDrvIDC))); return rc; } /* bail out. */ LogRel(("VBoxRawPci: Failed to register component factory, rc=%Rrc\n", rc)); SUPR0IdcClose(&pGlobals->SupDrvIDC); } return rc; } /** * Try to close the IDC connection to SUPDRV if established. * * @returns VBox status code. * @retval VINF_SUCCESS on success. * @retval VERR_WRONG_ORDER if we're busy. * * @param pGlobals Pointer to the globals. */ DECLHIDDEN(int) vboxPciDeleteIdc(PVBOXRAWPCIGLOBALS pGlobals) { int rc; Assert(pGlobals->hFastMtx != NIL_RTSEMFASTMUTEX); /* * Check before trying to deregister the factory. */ if (!vboxPciCanUnload(pGlobals)) return VERR_WRONG_ORDER; if (!pGlobals->fIDCOpen) rc = VINF_SUCCESS; else { /* * Disconnect from SUPDRV. */ rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); AssertRC(rc); SUPR0IdcClose(&pGlobals->SupDrvIDC); pGlobals->fIDCOpen = false; } return rc; } /** * Initializes the globals. * * @returns VBox status code. * @param pGlobals Pointer to the globals. */ DECLHIDDEN(int) vboxPciInitGlobals(PVBOXRAWPCIGLOBALS pGlobals) { /* * Initialize the common portions of the structure. */ int rc = RTSemFastMutexCreate(&pGlobals->hFastMtx); if (RT_SUCCESS(rc)) { pGlobals->pInstanceHead = NULL; pGlobals->RawPciFactory.pfnRelease = vboxPciFactoryRelease; pGlobals->RawPciFactory.pfnCreateAndConnect = vboxPciFactoryCreateAndConnect; pGlobals->RawPciFactory.pfnInitVm = vboxPciFactoryInitVm; pGlobals->RawPciFactory.pfnDeinitVm = vboxPciFactoryDeinitVm; memcpy(pGlobals->SupDrvFactory.szName, "VBoxRawPci", sizeof("VBoxRawPci")); pGlobals->SupDrvFactory.pfnQueryFactoryInterface = vboxPciQueryFactoryInterface; pGlobals->fIDCOpen = false; } return rc; } /** * Deletes the globals. * * * @param pGlobals Pointer to the globals. */ DECLHIDDEN(void) vboxPciDeleteGlobals(PVBOXRAWPCIGLOBALS pGlobals) { Assert(!pGlobals->fIDCOpen); /* * Release resources. */ if (pGlobals->hFastMtx) { RTSemFastMutexDestroy(pGlobals->hFastMtx); pGlobals->hFastMtx = NIL_RTSEMFASTMUTEX; } } int vboxPciInit(PVBOXRAWPCIGLOBALS pGlobals) { /* * Initialize the common portions of the structure. */ int rc = vboxPciInitGlobals(pGlobals); if (RT_SUCCESS(rc)) { rc = vboxPciInitIdc(pGlobals); if (RT_SUCCESS(rc)) return rc; /* bail out. */ vboxPciDeleteGlobals(pGlobals); } return rc; } void vboxPciShutdown(PVBOXRAWPCIGLOBALS pGlobals) { int rc = vboxPciDeleteIdc(pGlobals); if (RT_SUCCESS(rc)) vboxPciDeleteGlobals(pGlobals); }