blob: 4286af23fa3e89ec57fa5a4c9b0fa3333fd98d14 [file] [log] [blame]
/*************************************************************************/ /*!
@File
@Title PVR Bridge Module (kernel side)
@Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@Description Receives calls from the user portion of services and
despatches them to functions in the kernel portion.
@License Dual MIT/GPLv2
The contents of this file are subject to the MIT license as set out below.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.
This License is also included in this distribution in the file called
"MIT-COPYING".
EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS
PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ /**************************************************************************/
#include <linux/mm_types.h>
#include "img_defs.h"
#include "pvr_bridge.h"
#include "pvr_bridge_k.h"
#include "connection_server.h"
#include "syscommon.h"
#include "pvr_debug.h"
#include "pvr_debugfs.h"
#include "private_data.h"
#include "linkage.h"
#include "pmr.h"
#include "rgx_bvnc_defs_km.h"
#include "pvrsrv_bridge_init.h"
#include <drm/drmP.h>
#include "pvr_drm.h"
#include "pvr_drv.h"
#include "env_connection.h"
#include <linux/sched.h>
#include <linux/freezer.h>
/* RGX: */
#if defined(SUPPORT_RGX)
#include "rgx_bridge.h"
#endif
#include "srvcore.h"
#include "common_srvcore_bridge.h"
PVRSRV_ERROR InitDMABUFBridge(void);
PVRSRV_ERROR DeinitDMABUFBridge(void);
#if defined(MODULE_TEST)
/************************************************************************/
// additional includes for services testing
/************************************************************************/
#include "pvr_test_bridge.h"
#include "kern_test.h"
/************************************************************************/
// end of additional includes
/************************************************************************/
#endif
/* WARNING!
* The mmap code has its own mutex, to prevent a possible deadlock,
* when using gPVRSRVLock.
* The Linux kernel takes the mm->mmap_sem before calling the mmap
* entry points (PVRMMap, MMapVOpen, MMapVClose), but the ioctl
* entry point may take mm->mmap_sem during fault handling, or
* before calling get_user_pages. If gPVRSRVLock was used in the
* mmap entry points, a deadlock could result, due to the ioctl
* and mmap code taking the two locks in different orders.
* As a corollary to this, the mmap entry points must not call
* any driver code that relies on gPVRSRVLock is held.
*/
static DEFINE_MUTEX(g_sMMapMutex);
#if defined(DEBUG_BRIDGE_KM)
static PPVR_DEBUGFS_ENTRY_DATA gpsPVRDebugFSBridgeStatsEntry;
static struct seq_operations gsBridgeStatsReadOps;
static ssize_t BridgeStatsWrite(const char __user *pszBuffer,
size_t uiCount,
loff_t *puiPosition,
void *pvData);
#endif
#define _DRIVER_SUSPENDED 1
#define _DRIVER_NOT_SUSPENDED 0
static ATOMIC_T g_iDriverSuspended;
static ATOMIC_T g_iNumActiveDriverThreads;
static ATOMIC_T g_iNumActiveKernelThreads;
static IMG_HANDLE g_hDriverThreadEventObject;
PVRSRV_ERROR OSPlatformBridgeInit(void)
{
PVRSRV_ERROR eError;
eError = InitDMABUFBridge();
PVR_LOG_IF_ERROR(eError, "InitDMABUFBridge");
OSAtomicWrite(&g_iDriverSuspended, _DRIVER_NOT_SUSPENDED);
OSAtomicWrite(&g_iNumActiveDriverThreads, 0);
OSAtomicWrite(&g_iNumActiveKernelThreads, 0);
eError = OSEventObjectCreate("Global driver thread event object",
&g_hDriverThreadEventObject);
PVR_LOGG_IF_ERROR(eError, "OSEventObjectCreate", error_);
#if defined(DEBUG_BRIDGE_KM)
{
IMG_INT iResult;
iResult = PVRDebugFSCreateFile("bridge_stats",
NULL,
&gsBridgeStatsReadOps,
BridgeStatsWrite,
NULL,
&g_BridgeDispatchTable[0],
&gpsPVRDebugFSBridgeStatsEntry);
if (iResult != 0)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto error_;
}
}
#endif
return PVRSRV_OK;
error_:
if (g_hDriverThreadEventObject) {
OSEventObjectDestroy(g_hDriverThreadEventObject);
g_hDriverThreadEventObject = NULL;
}
return eError;
}
PVRSRV_ERROR OSPlatformBridgeDeInit(void)
{
PVRSRV_ERROR eError;
#if defined(DEBUG_BRIDGE_KM)
if (gpsPVRDebugFSBridgeStatsEntry != NULL)
{
PVRDebugFSRemoveFile(&gpsPVRDebugFSBridgeStatsEntry);
}
#endif
eError = DeinitDMABUFBridge();
PVR_LOGR_IF_ERROR(eError, "DeinitDMABUFBridge");
if (g_hDriverThreadEventObject != NULL) {
OSEventObjectDestroy(g_hDriverThreadEventObject);
g_hDriverThreadEventObject = NULL;
}
return eError;
}
#if defined(DEBUG_BRIDGE_KM)
static void *BridgeStatsSeqStart(struct seq_file *psSeqFile, loff_t *puiPosition)
{
PVRSRV_BRIDGE_DISPATCH_TABLE_ENTRY *psDispatchTable = (PVRSRV_BRIDGE_DISPATCH_TABLE_ENTRY *)psSeqFile->private;
#if defined(PVRSRV_USE_BRIDGE_LOCK)
OSAcquireBridgeLock();
#else
BridgeGlobalStatsLock();
#endif
if (psDispatchTable == NULL || (*puiPosition) > BRIDGE_DISPATCH_TABLE_ENTRY_COUNT)
{
return NULL;
}
if ((*puiPosition) == 0)
{
return SEQ_START_TOKEN;
}
return &(psDispatchTable[(*puiPosition) - 1]);
}
static void BridgeStatsSeqStop(struct seq_file *psSeqFile, void *pvData)
{
PVR_UNREFERENCED_PARAMETER(psSeqFile);
PVR_UNREFERENCED_PARAMETER(pvData);
#if defined(PVRSRV_USE_BRIDGE_LOCK)
OSReleaseBridgeLock();
#else
BridgeGlobalStatsUnlock();
#endif
}
static void *BridgeStatsSeqNext(struct seq_file *psSeqFile,
void *pvData,
loff_t *puiPosition)
{
PVRSRV_BRIDGE_DISPATCH_TABLE_ENTRY *psDispatchTable = (PVRSRV_BRIDGE_DISPATCH_TABLE_ENTRY *)psSeqFile->private;
loff_t uiItemAskedFor = *puiPosition; /* puiPosition on entry is the index to return */
PVR_UNREFERENCED_PARAMETER(pvData);
/* Is the item asked for (starts at 0) a valid table index? */
if (uiItemAskedFor < BRIDGE_DISPATCH_TABLE_ENTRY_COUNT)
{
(*puiPosition)++; /* on exit it is the next seq index to ask for */
return &(psDispatchTable[uiItemAskedFor]);
}
/* Now passed the end of the table to indicate stop */
return NULL;
}
static int BridgeStatsSeqShow(struct seq_file *psSeqFile, void *pvData)
{
if (pvData == SEQ_START_TOKEN)
{
seq_printf(psSeqFile,
"Total ioctl call count = %u\n"
"Total number of bytes copied via copy_from_user = %u\n"
"Total number of bytes copied via copy_to_user = %u\n"
"Total number of bytes copied via copy_*_user = %u\n\n"
"%3s: %-60s | %-48s | %10s | %20s | %20s | %20s | %20s\n",
g_BridgeGlobalStats.ui32IOCTLCount,
g_BridgeGlobalStats.ui32TotalCopyFromUserBytes,
g_BridgeGlobalStats.ui32TotalCopyToUserBytes,
g_BridgeGlobalStats.ui32TotalCopyFromUserBytes + g_BridgeGlobalStats.ui32TotalCopyToUserBytes,
"#",
"Bridge Name",
"Wrapper Function",
"Call Count",
"copy_from_user (B)",
"copy_to_user (B)",
"Total Time (us)",
"Max Time (us)");
}
else if (pvData != NULL)
{
PVRSRV_BRIDGE_DISPATCH_TABLE_ENTRY *psEntry = (PVRSRV_BRIDGE_DISPATCH_TABLE_ENTRY *)pvData;
IMG_UINT32 ui32Remainder;
seq_printf(psSeqFile,
"%3d: %-60s %-48s %-10u %-20u %-20u %-20" IMG_UINT64_FMTSPEC " %-20" IMG_UINT64_FMTSPEC "\n",
(IMG_UINT32)(((size_t)psEntry-(size_t)g_BridgeDispatchTable)/sizeof(*g_BridgeDispatchTable)),
psEntry->pszIOCName,
(psEntry->pfFunction != NULL) ? psEntry->pszFunctionName : "(null)",
psEntry->ui32CallCount,
psEntry->ui32CopyFromUserTotalBytes,
psEntry->ui32CopyToUserTotalBytes,
OSDivide64r64(psEntry->ui64TotalTimeNS, 1000, &ui32Remainder),
OSDivide64r64(psEntry->ui64MaxTimeNS, 1000, &ui32Remainder));
}
return 0;
}
static struct seq_operations gsBridgeStatsReadOps =
{
.start = BridgeStatsSeqStart,
.stop = BridgeStatsSeqStop,
.next = BridgeStatsSeqNext,
.show = BridgeStatsSeqShow,
};
static ssize_t BridgeStatsWrite(const char __user *pszBuffer,
size_t uiCount,
loff_t *puiPosition,
void *pvData)
{
IMG_UINT32 i;
/* We only care if a '0' is written to the file, if so we reset results. */
char buf[1];
ssize_t iResult = simple_write_to_buffer(&buf[0], sizeof(buf), puiPosition, pszBuffer, uiCount);
if (iResult < 0)
{
return iResult;
}
if (iResult == 0 || buf[0] != '0')
{
return -EINVAL;
}
/* Reset stats. */
#if defined(PVRSRV_USE_BRIDGE_LOCK)
OSAcquireBridgeLock();
#else
BridgeGlobalStatsLock();
#endif
g_BridgeGlobalStats.ui32IOCTLCount = 0;
g_BridgeGlobalStats.ui32TotalCopyFromUserBytes = 0;
g_BridgeGlobalStats.ui32TotalCopyToUserBytes = 0;
for (i = 0; i < ARRAY_SIZE(g_BridgeDispatchTable); i++)
{
g_BridgeDispatchTable[i].ui32CallCount = 0;
g_BridgeDispatchTable[i].ui32CopyFromUserTotalBytes = 0;
g_BridgeDispatchTable[i].ui32CopyToUserTotalBytes = 0;
g_BridgeDispatchTable[i].ui64TotalTimeNS = 0;
g_BridgeDispatchTable[i].ui64MaxTimeNS = 0;
}
#if defined(PVRSRV_USE_BRIDGE_LOCK)
OSReleaseBridgeLock();
#else
BridgeGlobalStatsUnlock();
#endif
return uiCount;
}
#endif /* defined(DEBUG_BRIDGE_KM) */
PVRSRV_ERROR LinuxBridgeBlockClientsAccess(IMG_BOOL bShutdown)
{
PVRSRV_ERROR eError;
IMG_HANDLE hEvent;
eError = OSEventObjectOpen(g_hDriverThreadEventObject, &hEvent);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to open event object", __func__));
return eError;
}
if (OSAtomicCompareExchange(&g_iDriverSuspended, _DRIVER_NOT_SUSPENDED,
_DRIVER_SUSPENDED) == _DRIVER_SUSPENDED)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Driver is already suspended", __func__));
eError = PVRSRV_ERROR_INVALID_PARAMS;
goto out_put;
}
/* now wait for any threads currently in the server to exit */
while (OSAtomicRead(&g_iNumActiveDriverThreads) != 0 ||
(OSAtomicRead(&g_iNumActiveKernelThreads) != 0 && !bShutdown))
{
if (OSAtomicRead(&g_iNumActiveDriverThreads) != 0)
{
PVR_LOG(("%s: waiting for user threads (%d)", __func__,
OSAtomicRead(&g_iNumActiveDriverThreads)));
}
if (OSAtomicRead(&g_iNumActiveKernelThreads) != 0)
{
PVR_LOG(("%s: waiting for kernel threads (%d)", __func__,
OSAtomicRead(&g_iNumActiveKernelThreads)));
}
/* Regular wait is called here (and not OSEventObjectWaitKernel) because
* this code is executed by the caller of .suspend/.shutdown callbacks
* which is most likely PM (or other actor responsible for suspend
* process). Because of that this thread shouldn't and most likely
* event cannot be frozen. */
OSEventObjectWait(hEvent);
}
out_put:
OSEventObjectClose(hEvent);
return eError;
}
PVRSRV_ERROR LinuxBridgeUnblockClientsAccess(void)
{
PVRSRV_ERROR eError;
/* resume the driver and then signal so any waiting threads wake up */
if (OSAtomicCompareExchange(&g_iDriverSuspended, _DRIVER_SUSPENDED,
_DRIVER_NOT_SUSPENDED) == _DRIVER_NOT_SUSPENDED)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Driver is not suspended", __func__));
return PVRSRV_ERROR_INVALID_PARAMS;
}
eError = OSEventObjectSignal(g_hDriverThreadEventObject);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: OSEventObjectSignal failed: %s",
__func__, PVRSRVGetErrorString(eError)));
}
return eError;
}
static PVRSRV_ERROR LinuxBridgeSignalIfSuspended(void)
{
PVRSRV_ERROR eError = PVRSRV_OK;
if (OSAtomicRead(&g_iDriverSuspended) == _DRIVER_SUSPENDED)
{
PVRSRV_ERROR eError = OSEventObjectSignal(g_hDriverThreadEventObject);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to signal driver thread event"
" object: %s", __func__, PVRSRVGetErrorString(eError)));
}
}
return eError;
}
void LinuxBridgeNumActiveKernelThreadsIncrement(void)
{
OSAtomicIncrement(&g_iNumActiveKernelThreads);
}
void LinuxBridgeNumActiveKernelThreadsDecrement(void)
{
OSAtomicDecrement(&g_iNumActiveKernelThreads);
PVR_ASSERT(OSAtomicRead(&g_iNumActiveKernelThreads) >= 0);
/* Signal on every decrement in case LinuxBridgeBlockClientsAccess() is
* waiting for the threads to freeze.
* (error is logged in called function so ignore, we can't do much with
* it anyway) */
(void) LinuxBridgeSignalIfSuspended();
}
static PVRSRV_ERROR _WaitForDriverUnsuspend(void)
{
PVRSRV_ERROR eError;
IMG_HANDLE hEvent;
eError = OSEventObjectOpen(g_hDriverThreadEventObject, &hEvent);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to open event object", __func__));
return eError;
}
while (OSAtomicRead(&g_iDriverSuspended) == _DRIVER_SUSPENDED)
{
/* we should be able to use normal (not kernel) wait here since
* we were just unfrozen and most likely we're not going to
* be frozen again (?) */
OSEventObjectWait(hEvent);
}
OSEventObjectClose(hEvent);
return PVRSRV_OK;
}
PVRSRV_ERROR PVRSRVDriverThreadEnter(void)
{
PVRSRV_ERROR eError;
/* increment first so there is no race between this value and
* g_iDriverSuspended in LinuxBridgeBlockClientsAccess() */
OSAtomicIncrement(&g_iNumActiveDriverThreads);
if (OSAtomicRead(&g_iDriverSuspended) == _DRIVER_SUSPENDED)
{
/* decrement here because the driver is going to be suspended and
* this thread is going to be frozen so we don't want to wait for
* it in LinuxBridgeBlockClientsAccess() */
OSAtomicDecrement(&g_iNumActiveDriverThreads);
/* during suspend procedure this will put the current thread to
* the freezer but during shutdown this will just return */
try_to_freeze();
/* if the thread was unfrozen but the flag is not yet set to
* _DRIVER_NOT_SUSPENDED wait for it
* in case this is a shutdown the thread was not frozen so we'll
* wait here indefinitely but this is ok (and this is in fact what
* we want) because no thread should be entering the driver in such
* case */
eError = _WaitForDriverUnsuspend();
/* increment here because that means that the thread entered the
* driver */
OSAtomicIncrement(&g_iNumActiveDriverThreads);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to wait for driver"
" unsuspend: %s", __func__,
PVRSRVGetErrorString(eError)));
return eError;
}
}
return PVRSRV_OK;
}
void PVRSRVDriverThreadExit(void)
{
OSAtomicDecrement(&g_iNumActiveDriverThreads);
/* if the driver is being suspended then we need to signal the
* event object as the thread suspending the driver is waiting
* for active threads to exit
* error is logged in called function so ignore returned error
*/
(void) LinuxBridgeSignalIfSuspended();
}
int
PVRSRV_BridgeDispatchKM(struct drm_device __maybe_unused *dev, void *arg, struct drm_file *pDRMFile)
{
struct drm_pvr_srvkm_cmd *psSrvkmCmd = (struct drm_pvr_srvkm_cmd *) arg;
PVRSRV_BRIDGE_PACKAGE sBridgePackageKM = { 0 };
CONNECTION_DATA *psConnection = LinuxConnectionFromFile(pDRMFile->filp);
PVRSRV_ERROR error;
if (psConnection == NULL)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Connection is closed", __func__));
return -EFAULT;
}
PVR_ASSERT(psSrvkmCmd != NULL);
DRM_DEBUG("tgid=%d, tgid_connection=%d, bridge_id=%d, func_id=%d",
task_tgid_nr(current),
((ENV_CONNECTION_DATA *)PVRSRVConnectionPrivateData(psConnection))->owner,
psSrvkmCmd->bridge_id,
psSrvkmCmd->bridge_func_id);
if ((error = PVRSRVDriverThreadEnter()) != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: PVRSRVDriverThreadEnter failed: %s",
__func__,
PVRSRVGetErrorString(error)));
goto e0;
}
sBridgePackageKM.ui32BridgeID = psSrvkmCmd->bridge_id;
sBridgePackageKM.ui32FunctionID = psSrvkmCmd->bridge_func_id;
sBridgePackageKM.ui32Size = sizeof(sBridgePackageKM);
sBridgePackageKM.pvParamIn = (void __user *)(uintptr_t)psSrvkmCmd->in_data_ptr;
sBridgePackageKM.ui32InBufferSize = psSrvkmCmd->in_data_size;
sBridgePackageKM.pvParamOut = (void __user *)(uintptr_t)psSrvkmCmd->out_data_ptr;
sBridgePackageKM.ui32OutBufferSize = psSrvkmCmd->out_data_size;
error = BridgedDispatchKM(psConnection, &sBridgePackageKM);
PVRSRVDriverThreadExit();
e0:
return OSPVRSRVToNativeError(error);
}
int
PVRSRV_MMap(struct file *pFile, struct vm_area_struct *ps_vma)
{
CONNECTION_DATA *psConnection = LinuxConnectionFromFile(pFile);
IMG_HANDLE hSecurePMRHandle = (IMG_HANDLE)((uintptr_t)ps_vma->vm_pgoff);
PMR *psPMR;
PVRSRV_ERROR eError;
if (psConnection == NULL)
{
PVR_DPF((PVR_DBG_ERROR, "Invalid connection data"));
return -ENOENT;
}
/*
* The bridge lock used here to protect PVRSRVLookupHandle is replaced
* by a specific lock considering that the handle functions have now
* their own lock. This change was necessary to solve the lockdep issues
* related with the PVRSRV_MMap.
*/
mutex_lock(&g_sMMapMutex);
eError = PVRSRVLookupHandle(psConnection->psHandleBase,
(void **)&psPMR,
hSecurePMRHandle,
PVRSRV_HANDLE_TYPE_PHYSMEM_PMR,
IMG_TRUE);
if (eError != PVRSRV_OK)
{
goto e0;
}
/* Note: PMRMMapPMR will take a reference on the PMR.
* Unref the handle immediately, because we have now done
* the required operation on the PMR (whether it succeeded or not)
*/
eError = PMRMMapPMR(psPMR, ps_vma);
PVRSRVReleaseHandle(psConnection->psHandleBase, hSecurePMRHandle, PVRSRV_HANDLE_TYPE_PHYSMEM_PMR);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "%s: PMRMMapPMR failed (%s)",
__func__, PVRSRVGetErrorString(eError)));
goto e0;
}
mutex_unlock(&g_sMMapMutex);
return 0;
e0:
mutex_unlock(&g_sMMapMutex);
PVR_DPF((PVR_DBG_ERROR, "Unable to translate error %d", eError));
PVR_ASSERT(eError != PVRSRV_OK);
return -ENOENT; // -EAGAIN // or what?
}