HGCM.cpp revision 677833bc953b6cb418c701facbdcf4aa18d6c44e
/** @file
*
* HGCM (Host-Guest Communication Manager)
*/
/*
* Copyright (C) 2006 InnoTek Systemberatung GmbH
*
* 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 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.
*
* If you received this file as part of a commercial VirtualBox
* distribution, then only the terms of your commercial VirtualBox
* license agreement apply instead of the previous paragraph.
*/
/*
* NOT FOR REVIEWING YET. A LOT OF TODO/MISSED/INCOMPLETE/SKETCH CODE INSIDE!
*/
#include "Logging.h"
#include <string.h>
#include "hgcm/HGCMThread.h"
#include <iprt/critsect.h>
#include <iprt/semaphore.h>
#include <VBox/VBoxGuest.h>
/**
*
* Service location types:
*
* LOCAL SERVICE
* service DLL is loaded by the VM process,
* and directly called by the VM HGCM instance.
*/
/**
* A service gets one thread, which synchronously delivers messages to
* the service. This is good for serialization.
*
* Some services may want to process messages asynchronously, and will want
* a next message to be delivered, while a previous message is still being
* processed.
*
* The dedicated service thread delivers a next message when service
* returns after fetching a previous one. The service will call a message
* completion callback when message is actually processed. So returning
* from the service call means only that the service is processing message.
*
* 'Message processed' condition is indicated by service, which call the
* callback, even if the callback is called synchronously in the dedicated
* thread.
*
* This message completion callback is only valid for Call requests.
* Connect and Disconnect are processed sznchronously by service.
*
*/
/** @todo services registration, only registered service dll can be loaded */
/** @todo a registered LOCAL service must also get a thread, for now
* a thread per service (later may be there will be an option to
* have a thread per client for a service, however I do not think it's
* really necessary).
* The requests will be queued and executed by the service thread,
* an IRQ notification will be ussued when a request is completed.
*
* May be other services (like VRDP acceleration) should still use
* the EMT thread, because they have their own threads for long
* operations.
* So we have to distinguish those services during
* External dlls will always have its own thread,
* internal (trusted) services will choose between having executed
* on EMT or on a separate thread.
*
*/
/** Internal helper service object. HGCM code would use it to
* hold information about services and communicate with services.
* The HGCMService is an (in future) abstract class that implements
* common functionality. There will be derived classes for specific
* service types (Local, etc).
*/
/** @todo should be HGCMObject */
class HGCMService
{
private:
static HGCMService *sm_pSvcListHead;
static HGCMService *sm_pSvcListTail;
uint32_t volatile m_u32RefCnt;
char *m_pszSvcName;
char *m_pszSvcLibrary;
int loadServiceDLL (void);
void unloadServiceDLL (void);
int InstanceCreate (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort);
void InstanceDestroy (void);
HGCMService ();
~HGCMService () {};
public:
static int LoadService (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort);
void ReleaseService (void);
int GuestCall (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], bool fBlock);
int HostCall (PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[]);
};
class HGCMClient: public HGCMObject
{
public:
~HGCMClient ();
/** Service that the client is connected to. */
/** Client specific data. */
void *pvData;
};
HGCMClient::~HGCMClient ()
{
}
{
if (pService->SizeOfClient () > 0)
{
if (!pvData)
{
return VERR_NO_MEMORY;
}
}
return VINF_SUCCESS;
}
/*
* Messages processed by worker threads.
*/
#define HGCMMSGID_SVC_LOAD (0)
#define HGCMMSGID_SVC_UNLOAD (1)
#define HGCMMSGID_SVC_CONNECT (2)
#define HGCMMSGID_SVC_DISCONNECT (3)
#define HGCMMSGID_GUESTCALL (4)
class HGCMMsgSvcLoad: public HGCMMsgCore
{
};
class HGCMMsgSvcUnload: public HGCMMsgCore
{
};
class HGCMMsgSvcConnect: public HGCMMsgCore
{
public:
/* client identifier */
};
class HGCMMsgSvcDisconnect: public HGCMMsgCore
{
public:
/* client identifier */
};
class HGCMMsgHeader: public HGCMMsgCore
{
public:
/* Command pointer/identifier. */
/* Port to be informed on message completion. */
};
class HGCMMsgCall: public HGCMMsgHeader
{
public:
/* client identifier */
/* function number */
/* number of parameters */
};
/*
* Messages processed by main HGCM thread.
*/
#define HGCMMSGID_CONNECT (10)
#define HGCMMSGID_DISCONNECT (11)
#define HGCMMSGID_LOAD (12)
#define HGCMMSGID_HOSTCALL (13)
class HGCMMsgConnect: public HGCMMsgHeader
{
public:
/* service location */
/* client identifier */
};
class HGCMMsgDisconnect: public HGCMMsgHeader
{
public:
/* client identifier */
};
class HGCMMsgLoad: public HGCMMsgHeader
{
public:
virtual ~HGCMMsgLoad ()
{
}
{
if (!pszServiceName || !pszServiceLibrary)
{
return VERR_NO_MEMORY;
}
return VINF_SUCCESS;
}
char *pszServiceName;
char *pszServiceLibrary;
};
class HGCMMsgHostCall: public HGCMMsgHeader
{
public:
char *pszServiceName;
/* function number */
/* number of parameters */
};
/* static */ DECLCALLBACK(void) HGCMService::svcHlpCallComplete (VBOXHGCMCALLHANDLE callHandle, int32_t rc)
{
{
/* Only call the completion for these messages. The helper
* is called by the service, and the service does not get
* any other messages.
*/
}
else
{
AssertFailed ();
}
}
:
m_thread (0),
m_u32RefCnt (0),
m_pSvcNext (NULL),
m_pSvcPrev (NULL),
m_pszSvcName (NULL),
{
}
{
switch (u32MsgId)
{
case HGCMMSGID_SVC_LOAD: return new HGCMMsgSvcLoad ();
case HGCMMSGID_SVC_UNLOAD: return new HGCMMsgSvcUnload ();
case HGCMMSGID_SVC_CONNECT: return new HGCMMsgSvcConnect ();
case HGCMMSGID_SVC_DISCONNECT: return new HGCMMsgSvcDisconnect ();
case HGCMMSGID_GUESTCALL: return new HGCMMsgCall ();
case HGCMMSGID_CONNECT: return new HGCMMsgConnect ();
case HGCMMSGID_DISCONNECT: return new HGCMMsgDisconnect ();
case HGCMMSGID_LOAD: return new HGCMMsgLoad ();
case HGCMMSGID_HOSTCALL: return new HGCMMsgHostCall ();
default:
}
return NULL;
}
{
/* Call the VMMDev port interface to issue IRQ notification. */
{
}
return;
}
{
int rc = VINF_SUCCESS;
bool bUnloaded = false;
while (!bUnloaded)
{
if (VBOX_FAILURE (rc))
{
RTThreadSleep(100);
continue;
}
{
case HGCMMSGID_SVC_LOAD:
{
LogFlow(("HGCMMSGID_SVC_LOAD\n"));
} break;
case HGCMMSGID_SVC_UNLOAD:
{
LogFlow(("HGCMMSGID_SVC_UNLOAD\n"));
pSvc->unloadServiceDLL ();
bUnloaded = true;
} break;
case HGCMMSGID_SVC_CONNECT:
{
LogFlow(("HGCMMSGID_SVC_CONNECT\n"));
rc = VINF_SUCCESS;
if (pClient)
{
}
} break;
case HGCMMSGID_SVC_DISCONNECT:
{
LogFlow(("HGCMMSGID_SVC_DISCONNECT\n"));
rc = VINF_SUCCESS;
if (pClient)
{
}
} break;
case HGCMMSGID_GUESTCALL:
{
LogFlow(("HGCMMSGID_GUESTCALL\n"));
rc = VINF_SUCCESS;
if (pClient)
{
pSvc->m_fntable.pfnCall ((VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientID, HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function, pMsg->cParms, pMsg->paParms);
}
else
{
}
} break;
case HGCMMSGID_HOSTCALL:
{
LogFlow(("HGCMMSGID_HOSTCALL\n"));
pSvc->m_fntable.pfnHostCall ((VBOXHGCMCALLHANDLE)pMsg, 0, NULL, pMsg->u32Function, pMsg->cParms, pMsg->paParms);
rc = VINF_SUCCESS;
} break;
default:
{
} break;
}
{
/* For HGCMMSGID_GUESTCALL & HGCMMSGID_HOSTCALL the service
* calls the completion helper. Other messages have to be
* completed here.
*/
}
}
return;
}
int HGCMService::InstanceCreate (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort)
{
int rc = VINF_SUCCESS;
char achThreadName[14];
/* @todo do a proper fix 0x12345678 -> sizeof */
rc = hgcmThreadCreate (&m_thread, achThreadName, hgcmServiceThread, this, 0x12345678 /* sizeof (HGCMMsgCall) */);
if (VBOX_SUCCESS(rc))
{
if (!m_pszSvcName || !m_pszSvcLibrary)
{
m_pszSvcName = NULL;
rc = VERR_NO_MEMORY;
}
else
{
m_svcHelpers.pvInstance = this;
/* Execute the load request on the service thread. */
if (VBOX_SUCCESS(rc))
{
}
}
}
else
{
Log(("HGCMService::InstanceCreate: FAILURE: service thread creation\n"));
}
if (VBOX_FAILURE(rc))
{
InstanceDestroy ();
}
return rc;
}
void HGCMService::InstanceDestroy (void)
{
LogFlow(("HGCMService::InstanceDestroy\n"));
int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_UNLOAD, sizeof (HGCMMsgSvcUnload), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
}
m_pszSvcName = NULL;
LogFlow(("HGCMService::InstanceDestroy completed\n"));
}
{
if (!loc || (loc->type != VMMDevHGCMLoc_LocalHost && loc->type != VMMDevHGCMLoc_LocalHost_Existing))
{
return false;
}
{
return false;
}
return true;
}
/** Services are searched by FindService function which is called
* by the main HGCM thread during CONNECT message processing.
* Reference count of the service is increased.
* Services are loaded by the LoadService function.
* Note: both methods are executed by the main HGCM thread.
*/
{
if (!loc || (loc->type != VMMDevHGCMLoc_LocalHost && loc->type != VMMDevHGCMLoc_LocalHost_Existing))
{
return VERR_INVALID_PARAMETER;
}
/* Look at already loaded services. */
while (psvc)
{
{
break;
}
}
if (psvc)
{
return VINF_SUCCESS;
}
return VERR_ACCESS_DENIED;
}
{
while (psvc)
{
{
break;
}
}
return psvc;
}
/* static */ int HGCMService::LoadService (const char *pszServiceLibrary, const char *pszServiceName, PPDMIHGCMPORT pHGCMPort)
{
int rc = VINF_SUCCESS;
/* Look at already loaded services to avoid double loading. */
if (psvc)
{
}
else
{
psvc = new HGCMService ();
if (!psvc)
{
Log(("HGCMService::Load: memory allocation for the service failed!!!\n"));
rc = VERR_NO_MEMORY;
}
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS(rc))
{
/* Insert the just created service to list for future references. */
if (sm_pSvcListHead)
{
}
else
{
}
}
}
}
return rc;
}
void HGCMService::ReleaseService (void)
{
AssertRelease(u32RefCnt != ~0U);
if (u32RefCnt == 0)
{
/** @todo We do not unload services. Cache them all. Unloading will be later. HGCMObject! */
LogFlow(("HGCMService::ReleaseService: no more references.\n"));
// InstanceDestroy ();
// delete this;
}
}
/** Helper function to load a local service DLL.
*
* @return VBox code
*/
int HGCMService::loadServiceDLL (void)
{
if (m_pszSvcLibrary == NULL)
{
return VERR_INVALID_PARAMETER;
}
int rc = VINF_SUCCESS;
if (VBOX_SUCCESS(rc))
{
LogFlow(("HGCMService::loadServiceDLL: successfully loaded the library.\n"));
{
Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, rc = %d, m_pfnLoad = %p\n", VBOX_HGCM_SVCLOAD_NAME, rc, m_pfnLoad));
if (VBOX_SUCCESS(rc))
{
/* m_pfnLoad was NULL */
}
}
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS (rc))
{
)
{
Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n"));
{
}
}
}
}
}
else
{
LogFlow(("HGCMService::loadServiceDLL: failed to load service library. The service is not available.\n"));
}
if (VBOX_FAILURE(rc))
{
unloadServiceDLL ();
}
return rc;
}
/** Helper function to free a local service DLL.
*
* @return VBox code
*/
void HGCMService::unloadServiceDLL (void)
{
if (m_hLdrMod)
{
}
}
{
int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_CONNECT, sizeof (HGCMMsgSvcConnect), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
}
else
{
}
return rc;
}
{
int rc = VINF_SUCCESS;
rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_SVC_DISCONNECT, sizeof (HGCMMsgSvcDisconnect), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
}
else
{
}
return rc;
}
/* Forward the call request to the dedicated service thread.
*/
int HGCMService::GuestCall (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fBlock)
{
HGCMMSGHANDLE hMsg = 0;
LogFlow(("MAIN::HGCMService::Call\n"));
int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_GUESTCALL, sizeof (HGCMMsgCall), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
if (fBlock)
else
{
}
}
else
{
}
return rc;
}
/* Forward the call request to the dedicated service thread.
*/
int HGCMService::HostCall (PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
HGCMMSGHANDLE hMsg = 0;
int rc = hgcmMsgAlloc (m_thread, &hMsg, HGCMMSGID_HOSTCALL, sizeof (HGCMMsgHostCall), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
}
else
{
}
return rc;
}
/* Main HGCM thread that processes CONNECT/DISCONNECT requests. */
{
int rc = VINF_SUCCESS;
for (;;)
{
LogFlow(("hgcmThread: Going to get a message\n"));
if (VBOX_FAILURE (rc))
{
Log(("hgcmThread: message get failed, rc = %Vrc\n"));
RTThreadSleep(100);
continue;
}
{
case HGCMMSGID_CONNECT:
{
LogFlow(("HGCMMSGID_CONNECT\n"));
/* Search if the service exists.
* Create an information structure for the client.
* Inform the service about the client.
* Generate and return the client id.
*/
if (VBOX_SUCCESS (rc))
{
/* Allocate a client information structure */
if (!pClient)
{
Log(("hgcmConnect::Could not allocate HGCMClient\n"));
rc = VERR_NO_MEMORY;
}
else
{
if (VBOX_SUCCESS(rc))
{
}
if (VBOX_FAILURE(rc))
{
}
else
{
}
}
}
if (VBOX_FAILURE(rc))
{
if (pService)
{
pService->ReleaseService ();
}
}
} break;
case HGCMMSGID_DISCONNECT:
{
LogFlow(("HGCMMSGID_DISCONNECT\n"));
/* Forward call to the service dedicated HGCM thread. */
if (!pClient)
{
Log(("MAIN::hgcmThread:HGCMMSGID_DISCONNECT: FAILURE resolving client id\n"));
}
else
{
}
} break;
case HGCMMSGID_LOAD:
{
LogFlow(("HGCMMSGID_LOAD\n"));
} break;
case HGCMMSGID_HOSTCALL:
{
LogFlow(("HGCMMSGID_HOSTCALL at hgcmThread\n"));
if (pService)
{
LogFlow(("HGCMMSGID_HOSTCALL found service, forwarding the call.\n"));
}
} break;
default:
{
} break;
}
}
return;
}
static HGCMTHREADHANDLE g_hgcmThread = 0;
/*
* Find a service and inform it about a client connection.
* Main HGCM thread will process this request for serialization.
*/
int hgcmConnectInternal (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, PHGCMSERVICELOCATION pLoc, uint32_t *pu32ClientID, bool fBlock)
{
int rc = VINF_SUCCESS;
LogFlow(("MAIN::hgcmConnectInternal: pHGCMPort = %p, pCmd = %p, loc = %p, pu32ClientID = %p\n",
{
return VERR_INVALID_PARAMETER;
}
HGCMMSGHANDLE hMsg = 0;
rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_CONNECT, sizeof (HGCMMsgConnect), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
if (fBlock)
else
{
}
}
else
{
}
return rc;
}
/*
* Tell a service that the client is disconnecting.
* Main HGCM thread will process this request for serialization.
*/
int hgcmDisconnectInternal (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, bool fBlock)
{
int rc = VINF_SUCCESS;
LogFlow(("MAIN::hgcmDisconnectInternal: pHGCMPort = %p, pCmd = %p, u32ClientID = %d\n",
{
return VERR_INVALID_PARAMETER;
}
HGCMMSGHANDLE hMsg = 0;
rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_DISCONNECT, sizeof (HGCMMsgDisconnect), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
if (fBlock)
else
{
}
}
else
{
}
return rc;
}
{
int rc = VINF_SUCCESS;
LogFlow(("MAIN::hgcmLoadInternal: name = %s, lib = %s\n",
if (!pszServiceName || !pszServiceLibrary)
{
return VERR_INVALID_PARAMETER;
}
HGCMMSGHANDLE hMsg = 0;
if (VBOX_SUCCESS(rc))
{
if (VBOX_SUCCESS (rc))
{
}
}
else
{
}
return rc;
}
/*
* Call a service.
* The service dedicated thread will process this request.
*/
int hgcmGuestCallInternal (PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientID, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], bool fBlock)
{
int rc = VINF_SUCCESS;
LogFlow(("MAIN::hgcmCallInternal: pHGCMPort = %p, pCmd = %p, u32ClientID = %d, u32Function = %d, cParms = %d, aParms = %p\n",
{
return VERR_INVALID_PARAMETER;
}
if (!pClient)
{
return VERR_INVALID_PARAMETER;
}
rc = pClient->pService->GuestCall (pHGCMPort, pCmd, u32ClientID, u32Function, cParms, aParms, fBlock);
return rc;
}
/*
* Call a service.
* The service dedicated thread will process this request.
*/
int hgcmHostCallInternal (const char *pszServiceName, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[])
{
int rc = VINF_SUCCESS;
LogFlow(("MAIN::hgcmHostCallInternal: service = %s, u32Function = %d, cParms = %d, aParms = %p\n",
if (!pszServiceName)
{
return VERR_INVALID_PARAMETER;
}
HGCMMSGHANDLE hMsg = 0;
rc = hgcmMsgAlloc (g_hgcmThread, &hMsg, HGCMMSGID_HOSTCALL, sizeof (HGCMMsgHostCall), hgcmMessageAlloc);
if (VBOX_SUCCESS(rc))
{
}
else
{
}
return rc;
}
int hgcmInit (void)
{
int rc = VINF_SUCCESS;
Log(("MAIN::hgcmInit\n"));
rc = hgcmThreadInit ();
if (VBOX_FAILURE(rc))
{
Log(("FAILURE: Can't init worker threads.\n"));
}
else
{
/* Start main HGCM thread that will process Connect/Disconnect requests. */
/* @todo do a proper fix 0x12345678 -> sizeof */
rc = hgcmThreadCreate (&g_hgcmThread, "MainHGCMthread", hgcmThread, NULL, 0x12345678 /*sizeof (HGCMMsgConnect)*/);
if (VBOX_FAILURE (rc))
{
Log(("FAILURE: HGCM initialization.\n"));
}
}
return rc;
}