HGCMInternal.cpp revision f0beabb75153a7beca9ece29fee0bfe5f7b50fe6
/* $Revision$ */
/** @file
* VBoxGuestLib - Host-Guest Communication Manager internal functions, implemented by VBoxGuest
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/* Entire file is ifdef'ed with VBGL_VBOXGUEST */
#ifdef VBGL_VBOXGUEST
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "VBGLInternal.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The max parameter buffer size for a user request. */
#define VBGLR0_MAX_HGCM_USER_PARM _1M
/** The max parameter buffer size for a kernel request. */
#ifdef RT_OS_LINUX
/** Linux needs to use bounce buffers since RTR0MemObjLockUser has unwanted
* side effects. */
# define USE_BOUNCH_BUFFERS
#endif
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Lock info structure used by VbglR0HGCMInternalCall and its helpers.
*/
struct VbglR0ParmInfo
{
struct
{
#ifdef USE_BOUNCH_BUFFERS
void *pvSmallBuf;
#endif
} aLockBufs[10];
};
/* These functions can be only used by VBoxGuest. */
{
int rc;
if (!pConnectInfo || !pAsyncCallback)
return VERR_INVALID_PARAMETER;
pHGCMConnect = NULL;
/* Allocate request */
rc = VbglGRAlloc ((VMMDevRequestHeader **)&pHGCMConnect, sizeof (VMMDevHGCMConnect), VMMDevReq_HGCMConnect);
if (RT_SUCCESS(rc))
{
/* Initialize request memory */
pHGCMConnect->u32ClientID = 0;
/* Issue request */
if (RT_SUCCESS(rc))
{
/* Check if host decides to process the request asynchronously. */
if (rc == VINF_HGCM_ASYNC_EXECUTE)
{
/* Wait for request completion interrupt notification from host */
}
}
}
return rc;
}
{
int rc;
if (!pDisconnectInfo || !pAsyncCallback)
return VERR_INVALID_PARAMETER;
/* Allocate request */
rc = VbglGRAlloc ((VMMDevRequestHeader **)&pHGCMDisconnect, sizeof (VMMDevHGCMDisconnect), VMMDevReq_HGCMDisconnect);
if (RT_SUCCESS(rc))
{
/* Initialize request memory */
/* Issue request */
if (RT_SUCCESS(rc))
{
/* Check if host decides to process the request asynchronously. */
if (rc == VINF_HGCM_ASYNC_EXECUTE)
{
/* Wait for request completion interrupt notification from host */
}
}
}
return rc;
}
#if 0 /* new code using page list and whatnot. */
/**
*
* @returns VBox status code.
*
* @param pCallInfo The call info.
* @param cbCallInfo The size of the call info structure.
* @param fIsUser Is it a user request or kernel request.
* @param pcbExtra Where to return the extra request space needed for
* physical page lists.
*/
static int vbglR0HGCMInternalPreprocessCall(VBoxGuestHGCMCallInfo const *pCallInfo, uint32_t cbCallInfo,
{
/*
* Lock down the any linear buffers so we can get their addresses
* and figure out how much extra storage we need for page lists.
*
* Note! With kernel mode users we can be assertive. For user mode users
* we should just (debug) log it and fail without any fanfare.
*/
*pcbExtra = 0;
{
{
case VMMDevHGCMParmType_32bit:
break;
case VMMDevHGCMParmType_64bit:
break;
if (fIsUser)
return VERR_INVALID_PARAMETER;
if (cb)
{
AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM), VERR_OUT_OF_RANGE);
AssertMsgReturn(pPgLst->offFirstPage < PAGE_SIZE, ("#x\n", pPgLst->offFirstPage), VERR_INVALID_PARAMETER);
AssertMsgReturn(pPgLst->flags > VBOX_HGCM_F_PARM_DIRECTION_NONE && pPgLst->flags <= VBOX_HGCM_F_PARM_DIRECTION_BOTH,
Log4(("GstHGCMCall: parm=%u type=pglst: cb=%#010x cPgs=%u offPg0=%#x flags=%#x\n", iParm, cb, cPages, pPgLst->offFirstPage, pPgLst->flags));
while (u32-- > 0)
{
}
}
else
break;
if (fIsUser)
return VERR_INVALID_PARAMETER;
if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST())
{
AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM), VERR_OUT_OF_RANGE);
if (cb != 0)
Log4(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p\n", iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr));
else
break;
}
/* fall thru */
if (cb != 0)
{
#ifdef USE_BOUNCH_BUFFERS
void *pvSmallBuf = NULL;
#endif
int rc;
if (!fIsUser)
{
AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM), VERR_OUT_OF_RANGE);
if (RT_FAILURE(rc))
{
Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemObjLockKernel(,%p,%#x) -> %Rrc\n",
return rc;
}
Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p locked kernel -> %p\n", iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj));
}
else
{
if (cb > VBGLR0_MAX_HGCM_USER_PARM)
{
Log(("GstHGCMCall: id=%#x fn=%u parm=%u pv=%p cb=%#x > %#x -> out of range\n",
pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr, cb, VBGLR0_MAX_HGCM_USER_PARM));
return VERR_OUT_OF_RANGE;
}
#ifndef USE_BOUNCH_BUFFERS
if (RT_FAILURE(rc))
{
Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemObjLockUser(,%p,%#x,nil) -> %Rrc\n",
return rc;
}
Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p locked user -> %p\n", iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj));
#else /* USE_BOUNCH_BUFFERS */
/*
* This is a bit massive, but we don't want to waste a
* whole page for a 3 byte string buffer (guest props).
*
* The threshold is ASSUMING sizeof(RTMEMHDR) == 16 and
* the system is using some power of two allocator.
*/
/** @todo A more efficient strategy would be to combine buffers. However it
* is probably going to be more massive than the current code, so
* it can wait till later. */
{
if (RT_UNLIKELY(!pvSmallBuf))
return VERR_NO_MEMORY;
if (fCopyIn)
{
if (RT_FAILURE(rc))
{
Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemUserCopyFrom(,%p,%#x) -> %Rrc\n",
return rc;
}
}
if (RT_FAILURE(rc))
{
Log(("GstHGCMCall: RTR0MemObjLockKernel failed for small buffer: rc=%Rrc pvSmallBuf=%p cb=%#x\n", rc, pvSmallBuf, cb));
return rc;
}
Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p small buffer %p -> %p\n", iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, pvSmallBuf, hObj));
}
else
{
if (RT_FAILURE(rc))
return rc;
if (!fCopyIn)
else
{
if (RT_FAILURE(rc))
{
Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemUserCopyFrom(,%p,%#x) -> %Rrc\n",
return rc;
}
}
Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p big buffer -> %p\n", iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj));
}
#endif /* USE_BOUNCH_BUFFERS */
}
#ifdef USE_BOUNCH_BUFFERS
#endif
{
}
}
else
break;
default:
return VERR_INVALID_PARAMETER;
}
}
return VINF_SUCCESS;
}
/**
* Translates locked linear address to the normal type.
* The locked types are only for the guest side and not handled by the host.
*
* @returns normal linear address type.
* @param enmType The type.
*/
static HGCMFunctionParameterType vbglR0HGCMInternalConvertLinAddrType(HGCMFunctionParameterType enmType)
{
switch (enmType)
{
return VMMDevHGCMParmType_LinAddr_In;
return VMMDevHGCMParmType_LinAddr_Out;
return VMMDevHGCMParmType_LinAddr;
default:
return enmType;
}
}
/**
* Translates linear address types to page list direction flags.
*
* @returns page list flags.
* @param enmType The type.
*/
{
switch (enmType)
{
default: AssertFailed();
return VBOX_HGCM_F_PARM_DIRECTION_BOTH;
}
}
/**
* Initializes the call request that we're sending to the host.
*
* @returns VBox status code.
*
* @param pCallInfo The call info.
* @param cbCallInfo The size of the call info structure.
* @param fIsUser Is it a user request or kernel request.
* @param pcbExtra Where to return the extra request space needed for
* physical page lists.
*/
static void vbglR0HGCMInternalInitCall(VMMDevHGCMCall *pHGCMCall, VBoxGuestHGCMCallInfo const *pCallInfo, uint32_t cbCallInfo,
{
/*
* The call request headers.
*/
/*
* The parameters.
*/
{
{
case VMMDevHGCMParmType_32bit:
case VMMDevHGCMParmType_64bit:
break;
{
HGCMPageListInfo const *pSrcPgLst = (HGCMPageListInfo *)((uint8_t *)pCallInfo + pSrcParm->u.PageList.offset);
}
else
break;
if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST())
{
break;
}
/* fall thru */
{
#ifdef USE_BOUNCH_BUFFERS
#endif
{
#ifdef USE_BOUNCH_BUFFERS
if (fIsUser)
else
#endif
{
}
}
else
{
#ifdef USE_BOUNCH_BUFFERS
if (fIsUser)
else
#endif
}
iLockBuf++;
}
else
{
}
break;
default:
AssertFailed();
break;
}
}
}
/**
* Performs the call and completion wait.
*
* @returns VBox status code of this operation, not necessarily the call.
*
* @param pHGCMCall The HGCM call info.
* @param pfnAsyncCallback The async callback that will wait for the call
* to complete.
* @param pvAsyncData Argument for the callback.
* @param u32AsyncData Argument for the callback.
*/
static int vbglR0HGCMInternalDoCall(VMMDevHGCMCall *pHGCMCall, VBGLHGCMCALLBACK *pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData)
{
int rc;
Log(("calling VbglGRPerform\n"));
/*
* If the call failed, but as a result of the request itself, then pretend
* success. Upper layers will interpret the result code in the packet.
*/
if ( RT_FAILURE(rc)
{
rc = VINF_SUCCESS;
}
/*
* Check if host decides to process the request asynchronously,
* if so, we wait for it to complete using the caller supplied callback.
*/
if ( RT_SUCCESS(rc)
&& rc == VINF_HGCM_ASYNC_EXECUTE)
{
Log(("Processing HGCM call asynchronously\n"));
/** @todo timeout vs. interrupted. */
/*
* If the request isn't completed by the time the callback returns
* we will have to try cancel it.
*/
{
/** @todo use a new request for this! See @bugref{4052}. */
}
}
Log(("GstHGCMCall: rc=%Rrc result=%Rrc fu32Flags=%#x\n", rc, pHGCMCall->header.result, pHGCMCall->header.fu32Flags));
return rc;
}
/**
* Copies the result of the call back to the caller info structure and user
* buffers (if using bounce buffers).
*
* @returns rc, unless RTR0MemUserCopyTo fails.
* @param pCallInfo Call info structure to update.
* @param pHGCMCall HGCM call request.
* @param fIsUser Is it a user (true) or kernel request.
* @param rc The current result code. Passed along to
* preserve informational status codes.
*/
static int vbglR0HGCMInternalCopyBackResult(VBoxGuestHGCMCallInfo *pCallInfo, VMMDevHGCMCall const *pHGCMCall,
{
#ifdef USE_BOUNCH_BUFFERS
#endif
/*
* The call result.
*/
/*
* Copy back parameters.
*/
{
{
case VMMDevHGCMParmType_32bit:
case VMMDevHGCMParmType_64bit:
break;
break;
#ifdef USE_BOUNCH_BUFFERS
if ( fIsUser
iLockBuf++;
#endif
break;
if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST())
{
break;
}
/* fall thru */
{
#ifdef USE_BOUNCH_BUFFERS
if (fIsUser)
{
if (cbOut)
{
cbOut);
if (RT_FAILURE(rc2))
return rc2;
iLockBuf++;
}
iLockBuf++;
}
#endif
break;
}
default:
AssertFailed();
break;
}
}
#ifdef USE_BOUNCH_BUFFERS
#endif
return rc;
}
DECLR0VBGL(int) VbglR0HGCMInternalCall(VBoxGuestHGCMCallInfo *pCallInfo, uint32_t cbCallInfo, uint32_t fFlags,
{
struct VbglR0ParmInfo ParmInfo;
int rc;
/*
* Basic validation.
*/
AssertMsgReturn(!pCallInfo || !pfnAsyncCallback || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK),
Log(("GstHGCMCall: u32ClientID=%#x u32Function=%u cParms=%u cbCallInfo=%#x fFlags=%#x\n",
pCallInfo->u32ClientID, pCallInfo->u32ClientID, pCallInfo->u32Function, pCallInfo->cParms, cbCallInfo, fFlags));
/*
* Validate, lock and buffer the parameters for the call.
* This will calculate the amount of extra space for physical page list.
*/
if (RT_SUCCESS(rc))
{
/*
* Allocate the request buffer and recreate the call request.
*/
if (RT_SUCCESS(rc))
{
/*
* Perform the call.
*/
if (RT_SUCCESS(rc))
{
/*
* Copy back the result (parameters and buffers that changed).
*/
}
}
}
/*
* Release locks and free bounce buffers.
*/
{
#ifdef USE_BOUNCH_BUFFERS
#endif
}
return rc;
}
# if ARCH_BITS == 64
DECLR0VBGL(int) VbglR0HGCMInternalCall32(VBoxGuestHGCMCallInfo *pCallInfo, uint32_t cbCallInfo, uint32_t fFlags,
{
int rc;
/*
* Input validation.
*/
AssertMsgReturn(!pCallInfo || !pfnAsyncCallback || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK),
AssertReturn((fFlags & VBGLR0_HGCMCALL_F_MODE_MASK) == VBGLR0_HGCMCALL_F_KERNEL, VERR_INVALID_PARAMETER);
Log(("VbglR0HGCMInternalCall32: cParms=%d, u32Function=%d, fFlags=%#x\n", cParms, pCallInfo->u32Function, fFlags));
/*
* The simple approach, allocate a temporary request and convert the parameters.
*/
pCallInfo64 = (VBoxGuestHGCMCallInfo *)RTMemTmpAllocZ(sizeof(*pCallInfo64) + cParms * sizeof(HGCMFunctionParameter));
if (!pCallInfo64)
return VERR_NO_TMP_MEMORY;
*pCallInfo64 = *pCallInfo;
{
{
case VMMDevHGCMParmType_32bit:
break;
case VMMDevHGCMParmType_64bit:
break;
break;
default:
break;
}
if (RT_FAILURE(rc))
break;
}
if (RT_SUCCESS(rc))
{
rc = VbglR0HGCMInternalCall(pCallInfo64, sizeof(*pCallInfo64) + cParms * sizeof(HGCMFunctionParameter), fFlags,
/*
* Copy back.
*/
{
{
case VMMDevHGCMParmType_32bit:
break;
case VMMDevHGCMParmType_64bit:
break;
break;
default:
break;
}
}
*pCallInfo = *pCallInfo64;
}
return rc;
}
# endif /* ARCH_BITS == 64 */
# else /* old code: */
/** @todo merge with the one below (use a header file). Too lazy now. */
DECLR0VBGL(int) VbglR0HGCMInternalCall (VBoxGuestHGCMCallInfo *pCallInfo, uint32_t cbCallInfo, uint32_t fFlags,
{
unsigned iParm;
int rc;
AssertMsgReturn(!pCallInfo || !pAsyncCallback || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK),
Log (("GstHGCMCall: pCallInfo->cParms = %d, pHGCMCall->u32Function = %d, fFlags=%#x\n",
if (cbCallInfo == 0)
{
/* Caller did not specify the size (a valid value should be at least sizeof(VBoxGuestHGCMCallInfo)).
* Compute the size.
*/
}
else if (cbCallInfo < sizeof (VBoxGuestHGCMCallInfo))
{
return VERR_INVALID_PARAMETER;
}
else
{
}
/* Allocate request */
rc = VbglGRAlloc ((VMMDevRequestHeader **)&pHGCMCall, sizeof (VMMDevHGCMCall) + cbParms, VMMDevReq_HGCMCall);
if (RT_SUCCESS(rc))
{
void *apvCtx[VBOX_HGCM_MAX_PARMS];
/* Initialize request memory */
if (cbParms)
{
/* Lock user buffers. */
{
{
case VMMDevHGCMParmType_32bit:
case VMMDevHGCMParmType_64bit:
break;
else
break;
else
break;
else
break;
break;
/* PORTME: When porting this to Darwin and other systems where the entire kernel isn't mapped
into every process, all linear address will have to be converted to physical SG lists at
this point. Care must also be taken on these guests to not mix kernel and user addresses
it will assume that it's in the current memory context (i.e. use CR3 to translate it).
These kind of problems actually applies to some patched linux kernels too, including older
fedora releases. (The patch is the infamous 4G/4G patch, aka 4g4g, by Ingo Molnar.) */
fFlags);
break;
default:
break;
}
if (RT_FAILURE (rc))
break;
}
}
/* Check that the parameter locking was ok. */
if (RT_SUCCESS(rc))
{
Log (("calling VbglGRPerform\n"));
/* Issue request */
/** If the call failed, but as a result of the request itself, then pretend success
* Upper layers will interpret the result code in the packet.
*/
{
rc = VINF_SUCCESS;
}
if (RT_SUCCESS(rc))
{
/* Check if host decides to process the request asynchronously. */
if (rc == VINF_HGCM_ASYNC_EXECUTE)
{
/* Wait for request completion interrupt notification from host */
Log (("Processing HGCM call asynchronously\n"));
}
{
if (cbParms)
{
}
}
else
{
/* The callback returns without completing the request,
* that means the wait was interrrupted. That can happen
* if the request times out, the system reboots or the
* VBoxService ended abnormally.
*
* Cancel the request, the host will not write to the
* memory related to the cancelled request.
*/
Log (("Cancelling HGCM call\n"));
}
}
}
/* Unlock user buffers. */
{
{
{
}
}
else
}
else
}
return rc;
}
# if ARCH_BITS == 64
/** @todo merge with the one above (use a header file). Too lazy now. */
DECLR0VBGL(int) VbglR0HGCMInternalCall32 (VBoxGuestHGCMCallInfo *pCallInfo, uint32_t cbCallInfo, uint32_t fFlags,
{
unsigned iParm;
int rc;
AssertMsgReturn(!pCallInfo || !pAsyncCallback || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK),
Log (("GstHGCMCall32: pCallInfo->cParms = %d, pHGCMCall->u32Function = %d, fFlags=%#x\n",
if (cbCallInfo == 0)
{
/* Caller did not specify the size (a valid value should be at least sizeof(VBoxGuestHGCMCallInfo)).
* Compute the size.
*/
}
else if (cbCallInfo < sizeof (VBoxGuestHGCMCallInfo))
{
return VERR_INVALID_PARAMETER;
}
else
{
}
/* Allocate request */
rc = VbglGRAlloc ((VMMDevRequestHeader **)&pHGCMCall, sizeof (VMMDevHGCMCall) + cbParms, VMMDevReq_HGCMCall32);
if (RT_SUCCESS(rc))
{
void *apvCtx[VBOX_HGCM_MAX_PARMS];
/* Initialize request memory */
if (cbParms)
{
/* Lock user buffers. */
{
{
case VMMDevHGCMParmType_32bit:
case VMMDevHGCMParmType_64bit:
break;
else
break;
else
break;
else
break;
break;
/* PORTME: When porting this to Darwin and other systems where the entire kernel isn't mapped
into every process, all linear address will have to be converted to physical SG lists at
this point. Care must also be taken on these guests to not mix kernel and user addresses
it will assume that it's in the current memory context (i.e. use CR3 to translate it).
These kind of problems actually applies to some patched linux kernels too, including older
fedora releases. (The patch is the infamous 4G/4G patch, aka 4g4g, by Ingo Molnar.) */
fFlags);
break;
default:
break;
}
if (RT_FAILURE (rc))
break;
}
}
/* Check that the parameter locking was ok. */
if (RT_SUCCESS(rc))
{
Log (("calling VbglGRPerform\n"));
/* Issue request */
/** If the call failed, but as a result of the request itself, then pretend success
* Upper layers will interpret the result code in the packet.
*/
{
rc = VINF_SUCCESS;
}
if (RT_SUCCESS(rc))
{
/* Check if host decides to process the request asynchronously. */
if (rc == VINF_HGCM_ASYNC_EXECUTE)
{
/* Wait for request completion interrupt notification from host */
Log (("Processing HGCM call asynchronously\n"));
}
{
if (cbParms)
}
else
{
/* The callback returns without completing the request,
* that means the wait was interrrupted. That can happen
* if the request times out, the system reboots or the
* VBoxService ended abnormally.
*
* Cancel the request, the host will not write to the
* memory related to the cancelled request.
*/
Log (("Cancelling HGCM call\n"));
}
}
}
/* Unlock user buffers. */
{
{
{
}
}
else
}
else
}
return rc;
}
# endif /* ARCH_BITS == 64 */
# endif /* old code */
#endif /* VBGL_VBOXGUEST */