diff options
Diffstat (limited to 'src/VBox/Main/src-client/HGCM.cpp')
-rw-r--r-- | src/VBox/Main/src-client/HGCM.cpp | 2730 |
1 files changed, 2730 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/HGCM.cpp b/src/VBox/Main/src-client/HGCM.cpp new file mode 100644 index 00000000..47eb1a7e --- /dev/null +++ b/src/VBox/Main/src-client/HGCM.cpp @@ -0,0 +1,2730 @@ +/* $Id: HGCM.cpp $ */ +/** @file + * HGCM (Host-Guest Communication Manager) + */ + +/* + * Copyright (C) 2006-2020 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. + */ + +#define LOG_GROUP LOG_GROUP_HGCM +#include "LoggingNew.h" + +#include "HGCM.h" +#include "HGCMThread.h" + +#include <VBox/err.h> +#include <VBox/hgcmsvc.h> +#include <VBox/vmm/ssm.h> +#include <VBox/vmm/stam.h> +#include <VBox/sup.h> + +#include <iprt/alloc.h> +#include <iprt/avl.h> +#include <iprt/critsect.h> +#include <iprt/asm.h> +#include <iprt/ldr.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> + +#include <VBox/VMMDev.h> +#include <new> + +/** + * 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 synchronously by the service. + */ + + +/* The maximum allowed size of a service name in bytes. */ +#define VBOX_HGCM_SVC_NAME_MAX_BYTES 1024 + +struct _HGCMSVCEXTHANDLEDATA +{ + char *pszServiceName; + /* The service name follows. */ +}; + +/** 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. + */ + +class HGCMService +{ + private: + VBOXHGCMSVCHELPERS m_svcHelpers; + + static HGCMService *sm_pSvcListHead; + static HGCMService *sm_pSvcListTail; + + static int sm_cServices; + + HGCMThread *m_pThread; + friend DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser); + + uint32_t volatile m_u32RefCnt; + + HGCMService *m_pSvcNext; + HGCMService *m_pSvcPrev; + + char *m_pszSvcName; + char *m_pszSvcLibrary; + + RTLDRMOD m_hLdrMod; + PFNVBOXHGCMSVCLOAD m_pfnLoad; + + VBOXHGCMSVCFNTABLE m_fntable; + + uint32_t m_cClients; + uint32_t m_cClientsAllocated; + + uint32_t *m_paClientIds; + + HGCMSVCEXTHANDLE m_hExtension; + + PUVM m_pUVM; + PPDMIHGCMPORT m_pHgcmPort; + + /** @name Statistics + * @{ */ + STAMPROFILE m_StatHandleMsg; + /** @} */ + + int loadServiceDLL(void); + void unloadServiceDLL(void); + + /* + * Main HGCM thread methods. + */ + int instanceCreate(const char *pszServiceLibrary, const char *pszServiceName, PUVM pUVM, PPDMIHGCMPORT pHgcmPort); + void instanceDestroy(void); + + int saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM); + int loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, uint32_t uVersion); + + HGCMService(); + ~HGCMService() {}; + + static DECLCALLBACK(int) svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc); + static DECLCALLBACK(void) svcHlpDisconnectClient(void *pvInstance, uint32_t u32ClientId); + static DECLCALLBACK(bool) svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle); + static DECLCALLBACK(bool) svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle); + static DECLCALLBACK(int) svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, + STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc, + const char *pszName, va_list va); + static DECLCALLBACK(int) svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va); + static DECLCALLBACK(int) svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser); + static DECLCALLBACK(int) svcHlpInfoDeregister(void *pvInstance, const char *pszName); + static DECLCALLBACK(uint32_t) svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall); + static DECLCALLBACK(uint64_t) svcHlpGetVMMDevSessionId(void *pvInstance); + + public: + + /* + * Main HGCM thread methods. + */ + static int LoadService(const char *pszServiceLibrary, const char *pszServiceName, PUVM pUVM, PPDMIHGCMPORT pHgcmPort); + void UnloadService(bool fUvmIsInvalid); + + static void UnloadAll(bool fUvmIsInvalid); + + static int ResolveService(HGCMService **ppsvc, const char *pszServiceName); + void ReferenceService(void); + void ReleaseService(void); + + static void Reset(void); + + static int SaveState(PSSMHANDLE pSSM); + static int LoadState(PSSMHANDLE pSSM, uint32_t uVersion); + + int CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring); + int DisconnectClient(uint32_t u32ClientId, bool fFromService); + + int HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms); + static void BroadcastNotify(HGCMNOTIFYEVENT enmEvent); + void Notify(HGCMNOTIFYEVENT enmEvent); + + uint32_t SizeOfClient(void) { return m_fntable.cbClient; }; + + int RegisterExtension(HGCMSVCEXTHANDLE handle, PFNHGCMSVCEXT pfnExtension, void *pvExtension); + void UnregisterExtension(HGCMSVCEXTHANDLE handle); + + /* + * The service thread methods. + */ + + int GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, + uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM aParms[], uint64_t tsArrival); + void GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient); +}; + + +class HGCMClient: public HGCMObject +{ + public: + HGCMClient(uint32_t a_fRequestor) + : HGCMObject(HGCMOBJ_CLIENT) + , pService(NULL) + , pvData(NULL) + , fRequestor(a_fRequestor) + {} + ~HGCMClient(); + + int Init(HGCMService *pSvc); + + /** Service that the client is connected to. */ + HGCMService *pService; + + /** Client specific data. */ + void *pvData; + + /** The requestor flags this client was created with. + * @sa VMMDevRequestHeader::fRequestor */ + uint32_t fRequestor; + + private: /* none of this: */ + HGCMClient(); + HGCMClient(HGCMClient const &); + HGCMClient &operator=(HGCMClient const &); +}; + +HGCMClient::~HGCMClient() +{ + if (pService->SizeOfClient() > 0) + { + RTMemFree(pvData); + pvData = NULL; + } +} + + +int HGCMClient::Init(HGCMService *pSvc) +{ + pService = pSvc; + + if (pService->SizeOfClient() > 0) + { + pvData = RTMemAllocZ(pService->SizeOfClient()); + + if (!pvData) + { + return VERR_NO_MEMORY; + } + } + + return VINF_SUCCESS; +} + + +#define HGCM_CLIENT_DATA(pService, pClient)(pClient->pvData) + + + +HGCMService *HGCMService::sm_pSvcListHead = NULL; +HGCMService *HGCMService::sm_pSvcListTail = NULL; +int HGCMService::sm_cServices = 0; + +HGCMService::HGCMService() + : + m_pThread (NULL), + m_u32RefCnt (0), + m_pSvcNext (NULL), + m_pSvcPrev (NULL), + m_pszSvcName (NULL), + m_pszSvcLibrary (NULL), + m_hLdrMod (NIL_RTLDRMOD), + m_pfnLoad (NULL), + m_cClients (0), + m_cClientsAllocated (0), + m_paClientIds (NULL), + m_hExtension (NULL), + m_pUVM (NULL), + m_pHgcmPort (NULL) +{ + RT_ZERO(m_fntable); +} + + +static bool g_fResetting = false; +static bool g_fSaveState = false; + + +/** Helper function to load a local service DLL. + * + * @return VBox code + */ +int HGCMService::loadServiceDLL(void) +{ + LogFlowFunc(("m_pszSvcLibrary = %s\n", m_pszSvcLibrary)); + + if (m_pszSvcLibrary == NULL) + { + return VERR_INVALID_PARAMETER; + } + + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + + int rc; + + if (RTPathHasPath(m_pszSvcLibrary)) + rc = SUPR3HardenedLdrLoadPlugIn(m_pszSvcLibrary, &m_hLdrMod, &ErrInfo.Core); + else + rc = SUPR3HardenedLdrLoadAppPriv(m_pszSvcLibrary, &m_hLdrMod, RTLDRLOAD_FLAGS_LOCAL, &ErrInfo.Core); + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("successfully loaded the library.\n")); + + m_pfnLoad = NULL; + + rc = RTLdrGetSymbol(m_hLdrMod, VBOX_HGCM_SVCLOAD_NAME, (void**)&m_pfnLoad); + + if (RT_FAILURE(rc) || !m_pfnLoad) + { + Log(("HGCMService::loadServiceDLL: Error resolving the service entry point %s, rc = %d, m_pfnLoad = %p\n", + VBOX_HGCM_SVCLOAD_NAME, rc, m_pfnLoad)); + + if (RT_SUCCESS(rc)) + { + /* m_pfnLoad was NULL */ + rc = VERR_SYMBOL_NOT_FOUND; + } + } + + if (RT_SUCCESS(rc)) + { + RT_ZERO(m_fntable); + + m_fntable.cbSize = sizeof(m_fntable); + m_fntable.u32Version = VBOX_HGCM_SVC_VERSION; + m_fntable.pHelpers = &m_svcHelpers; + + rc = m_pfnLoad(&m_fntable); + + LogFlowFunc(("m_pfnLoad rc = %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + if ( m_fntable.pfnUnload == NULL + || m_fntable.pfnConnect == NULL + || m_fntable.pfnDisconnect == NULL + || m_fntable.pfnCall == NULL + ) + { + Log(("HGCMService::loadServiceDLL: at least one of function pointers is NULL\n")); + + rc = VERR_INVALID_PARAMETER; + + if (m_fntable.pfnUnload) + { + m_fntable.pfnUnload(m_fntable.pvService); + } + } + } + } + } + else + { + LogRel(("HGCM: Failed to load the service library: [%s], rc = %Rrc - %s. The service will be not available.\n", + m_pszSvcLibrary, rc, ErrInfo.Core.pszMsg)); + m_hLdrMod = NIL_RTLDRMOD; + } + + if (RT_FAILURE(rc)) + { + unloadServiceDLL(); + } + + return rc; +} + +/** Helper function to free a local service DLL. + * + * @return VBox code + */ +void HGCMService::unloadServiceDLL(void) +{ + if (m_hLdrMod) + { + RTLdrClose(m_hLdrMod); + } + + RT_ZERO(m_fntable); + m_pfnLoad = NULL; + m_hLdrMod = NIL_RTLDRMOD; +} + +/* + * Messages processed by service threads. These threads only call the service entry points. + */ + +#define SVC_MSG_LOAD (0) /**< Load the service library and call VBOX_HGCM_SVCLOAD_NAME entry point. */ +#define SVC_MSG_UNLOAD (1) /**< call pfnUnload and unload the service library. */ +#define SVC_MSG_CONNECT (2) /**< pfnConnect */ +#define SVC_MSG_DISCONNECT (3) /**< pfnDisconnect */ +#define SVC_MSG_GUESTCALL (4) /**< pfnGuestCall */ +#define SVC_MSG_HOSTCALL (5) /**< pfnHostCall */ +#define SVC_MSG_LOADSTATE (6) /**< pfnLoadState. */ +#define SVC_MSG_SAVESTATE (7) /**< pfnSaveState. */ +#define SVC_MSG_QUIT (8) /**< Terminate the thread. */ +#define SVC_MSG_REGEXT (9) /**< pfnRegisterExtension */ +#define SVC_MSG_UNREGEXT (10) /**< pfnRegisterExtension */ +#define SVC_MSG_NOTIFY (11) /**< pfnNotify */ +#define SVC_MSG_GUESTCANCELLED (12) /**< pfnCancelled */ + +class HGCMMsgSvcLoad: public HGCMMsgCore +{ + public: + HGCMMsgSvcLoad() : HGCMMsgCore(), pUVM() {} + + /** The user mode VM handle (for statistics and such). */ + PUVM pUVM; +}; + +class HGCMMsgSvcUnload: public HGCMMsgCore +{ +}; + +class HGCMMsgSvcConnect: public HGCMMsgCore +{ + public: + /** client identifier */ + uint32_t u32ClientId; + /** Requestor flags. */ + uint32_t fRequestor; + /** Set if restoring. */ + bool fRestoring; +}; + +class HGCMMsgSvcDisconnect: public HGCMMsgCore +{ + public: + /* client identifier */ + uint32_t u32ClientId; +}; + +class HGCMMsgHeader: public HGCMMsgCore +{ + public: + HGCMMsgHeader() : pCmd(NULL), pHGCMPort(NULL) {}; + + /* Command pointer/identifier. */ + PVBOXHGCMCMD pCmd; + + /* Port to be informed on message completion. */ + PPDMIHGCMPORT pHGCMPort; +}; + +class HGCMMsgCall: public HGCMMsgHeader +{ + public: + HGCMMsgCall() {} + + HGCMMsgCall(HGCMThread *pThread) + { + InitializeCore(SVC_MSG_GUESTCALL, pThread); + Initialize(); + } + ~HGCMMsgCall() { Log(("~HGCMMsgCall %p\n", this)); } + + /* client identifier */ + uint32_t u32ClientId; + + /* function number */ + uint32_t u32Function; + + /* number of parameters */ + uint32_t cParms; + + VBOXHGCMSVCPARM *paParms; + + /** The STAM_GET_TS() value when the request arrived. */ + uint64_t tsArrival; +}; + +class HGCMMsgCancelled: public HGCMMsgHeader +{ + public: + HGCMMsgCancelled() {} + + HGCMMsgCancelled(HGCMThread *pThread) + { + InitializeCore(SVC_MSG_GUESTCANCELLED, pThread); + Initialize(); + } + ~HGCMMsgCancelled() { Log(("~HGCMMsgCancelled %p\n", this)); } + + /** The client identifier. */ + uint32_t idClient; +}; + +class HGCMMsgLoadSaveStateClient: public HGCMMsgCore +{ + public: + PSSMHANDLE pSSM; + uint32_t uVersion; + uint32_t u32ClientId; +}; + +class HGCMMsgHostCallSvc: public HGCMMsgCore +{ + public: + /* function number */ + uint32_t u32Function; + + /* number of parameters */ + uint32_t cParms; + + VBOXHGCMSVCPARM *paParms; +}; + +class HGCMMsgSvcRegisterExtension: public HGCMMsgCore +{ + public: + /* Handle of the extension to be registered. */ + HGCMSVCEXTHANDLE handle; + /* The extension entry point. */ + PFNHGCMSVCEXT pfnExtension; + /* The extension pointer. */ + void *pvExtension; +}; + +class HGCMMsgSvcUnregisterExtension: public HGCMMsgCore +{ + public: + /* Handle of the registered extension. */ + HGCMSVCEXTHANDLE handle; +}; + +class HGCMMsgNotify: public HGCMMsgCore +{ + public: + /** The event. */ + HGCMNOTIFYEVENT enmEvent; +}; + +static HGCMMsgCore *hgcmMessageAllocSvc(uint32_t u32MsgId) +{ + switch (u32MsgId) + { + case SVC_MSG_LOAD: return new HGCMMsgSvcLoad(); + case SVC_MSG_UNLOAD: return new HGCMMsgSvcUnload(); + case SVC_MSG_CONNECT: return new HGCMMsgSvcConnect(); + case SVC_MSG_DISCONNECT: return new HGCMMsgSvcDisconnect(); + case SVC_MSG_HOSTCALL: return new HGCMMsgHostCallSvc(); + case SVC_MSG_GUESTCALL: return new HGCMMsgCall(); + case SVC_MSG_LOADSTATE: + case SVC_MSG_SAVESTATE: return new HGCMMsgLoadSaveStateClient(); + case SVC_MSG_REGEXT: return new HGCMMsgSvcRegisterExtension(); + case SVC_MSG_UNREGEXT: return new HGCMMsgSvcUnregisterExtension(); + case SVC_MSG_NOTIFY: return new HGCMMsgNotify(); + case SVC_MSG_GUESTCANCELLED: return new HGCMMsgCancelled(); + default: + AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId)); + } + + return NULL; +} + +/* + * The service thread. Loads the service library and calls the service entry points. + */ +DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser) +{ + HGCMService *pSvc = (HGCMService *)pvUser; + AssertRelease(pSvc != NULL); + + bool fQuit = false; + + while (!fQuit) + { + HGCMMsgCore *pMsgCore; + int rc = hgcmMsgGet(pThread, &pMsgCore); + + if (RT_FAILURE(rc)) + { + /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */ + AssertMsgFailed(("%Rrc\n", rc)); + break; + } + + STAM_REL_PROFILE_START(&pSvc->m_StatHandleMsg, a); + + /* Cache required information to avoid unnecessary pMsgCore access. */ + uint32_t u32MsgId = pMsgCore->MsgId(); + + switch (u32MsgId) + { + case SVC_MSG_LOAD: + { + LogFlowFunc(("SVC_MSG_LOAD\n")); + rc = pSvc->loadServiceDLL(); + } break; + + case SVC_MSG_UNLOAD: + { + LogFlowFunc(("SVC_MSG_UNLOAD\n")); + if (pSvc->m_fntable.pfnUnload) + { + pSvc->m_fntable.pfnUnload(pSvc->m_fntable.pvService); + } + + pSvc->unloadServiceDLL(); + fQuit = true; + } break; + + case SVC_MSG_CONNECT: + { + HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pMsgCore; + + LogFlowFunc(("SVC_MSG_CONNECT u32ClientId = %d\n", pMsg->u32ClientId)); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + rc = pSvc->m_fntable.pfnConnect(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), + pMsg->fRequestor, pMsg->fRestoring); + + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_DISCONNECT: + { + HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pMsgCore; + + LogFlowFunc(("SVC_MSG_DISCONNECT u32ClientId = %d\n", pMsg->u32ClientId)); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + rc = pSvc->m_fntable.pfnDisconnect(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient)); + + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_GUESTCALL: + { + HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore; + + LogFlowFunc(("SVC_MSG_GUESTCALL u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n", + pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms, pMsg->paParms)); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + pSvc->m_fntable.pfnCall(pSvc->m_fntable.pvService, (VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function, + pMsg->cParms, pMsg->paParms, pMsg->tsArrival); + + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_GUESTCANCELLED: + { + HGCMMsgCancelled *pMsg = (HGCMMsgCancelled *)pMsgCore; + + LogFlowFunc(("SVC_MSG_GUESTCANCELLED idClient = %d\n", pMsg->idClient)); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->idClient, HGCMOBJ_CLIENT); + + if (pClient) + { + pSvc->m_fntable.pfnCancelled(pSvc->m_fntable.pvService, pMsg->idClient, HGCM_CLIENT_DATA(pSvc, pClient)); + + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_HOSTCALL: + { + HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pMsgCore; + + LogFlowFunc(("SVC_MSG_HOSTCALL u32Function = %d, cParms = %d, paParms = %p\n", + pMsg->u32Function, pMsg->cParms, pMsg->paParms)); + + rc = pSvc->m_fntable.pfnHostCall(pSvc->m_fntable.pvService, pMsg->u32Function, pMsg->cParms, pMsg->paParms); + } break; + + case SVC_MSG_LOADSTATE: + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore; + + LogFlowFunc(("SVC_MSG_LOADSTATE\n")); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + /* fRequestor: Restored by the message sender already. */ + bool fHaveClientState = pSvc->m_fntable.pfnLoadState != NULL; + if (pMsg->uVersion > HGCM_SAVED_STATE_VERSION_V2) + rc = SSMR3GetBool(pMsg->pSSM, &fHaveClientState); + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc) ) + { + if (pSvc->m_fntable.pfnLoadState) + rc = pSvc->m_fntable.pfnLoadState(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM, + fHaveClientState ? pMsg->uVersion : 0); + else + AssertLogRelStmt(!fHaveClientState, rc = VERR_INTERNAL_ERROR_5); + } + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_SAVESTATE: + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pMsgCore; + + LogFlowFunc(("SVC_MSG_SAVESTATE\n")); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + rc = VINF_SUCCESS; + + if (pClient) + { + SSMR3PutU32(pMsg->pSSM, pClient->fRequestor); /* Quicker to save this here than in the message sender. */ + rc = SSMR3PutBool(pMsg->pSSM, pSvc->m_fntable.pfnSaveState != NULL); + if (RT_SUCCESS(rc) && pSvc->m_fntable.pfnSaveState) + { + g_fSaveState = true; + rc = pSvc->m_fntable.pfnSaveState(pSvc->m_fntable.pvService, pMsg->u32ClientId, + HGCM_CLIENT_DATA(pSvc, pClient), pMsg->pSSM); + g_fSaveState = false; + } + + hgcmObjDereference(pClient); + } + else + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + } + } break; + + case SVC_MSG_REGEXT: + { + HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pMsgCore; + + LogFlowFunc(("SVC_MSG_REGEXT handle = %p, pfn = %p\n", pMsg->handle, pMsg->pfnExtension)); + + if (pSvc->m_hExtension) + { + rc = VERR_NOT_SUPPORTED; + } + else + { + if (pSvc->m_fntable.pfnRegisterExtension) + { + rc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, pMsg->pfnExtension, + pMsg->pvExtension); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + if (RT_SUCCESS(rc)) + { + pSvc->m_hExtension = pMsg->handle; + } + } + } break; + + case SVC_MSG_UNREGEXT: + { + HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pMsgCore; + + LogFlowFunc(("SVC_MSG_UNREGEXT handle = %p\n", pMsg->handle)); + + if (pSvc->m_hExtension != pMsg->handle) + { + rc = VERR_NOT_SUPPORTED; + } + else + { + if (pSvc->m_fntable.pfnRegisterExtension) + { + rc = pSvc->m_fntable.pfnRegisterExtension(pSvc->m_fntable.pvService, NULL, NULL); + } + else + { + rc = VERR_NOT_SUPPORTED; + } + + pSvc->m_hExtension = NULL; + } + } break; + + case SVC_MSG_NOTIFY: + { + HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pMsgCore; + + LogFlowFunc(("SVC_MSG_NOTIFY enmEvent = %d\n", pMsg->enmEvent)); + + pSvc->m_fntable.pfnNotify(pSvc->m_fntable.pvService, pMsg->enmEvent); + } break; + + default: + { + AssertMsgFailed(("hgcmServiceThread::Unsupported message number %08X\n", u32MsgId)); + rc = VERR_NOT_SUPPORTED; + } break; + } + + if (u32MsgId != SVC_MSG_GUESTCALL) + { + /* For SVC_MSG_GUESTCALL the service calls the completion helper. + * Other messages have to be completed here. + */ + hgcmMsgComplete (pMsgCore, rc); + } + STAM_REL_PROFILE_STOP(&pSvc->m_StatHandleMsg, a); + } +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnCallComplete} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) +{ + HGCMMsgCore *pMsgCore = (HGCMMsgCore *)callHandle; + + /* Only call the completion for these messages. The helper + * is called by the service, and the service does not get + * any other messages. + */ + AssertMsgReturn(pMsgCore->MsgId() == SVC_MSG_GUESTCALL, ("%d\n", pMsgCore->MsgId()), VERR_WRONG_TYPE); + return hgcmMsgComplete(pMsgCore, rc); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnDisconnectClient} + */ +/* static */ DECLCALLBACK(void) HGCMService::svcHlpDisconnectClient(void *pvInstance, uint32_t u32ClientId) +{ + HGCMService *pService = static_cast <HGCMService *> (pvInstance); + + if (pService) + { + pService->DisconnectClient(u32ClientId, true); + } +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallRestored} + */ +/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallRestored(VBOXHGCMCALLHANDLE callHandle) +{ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle; + AssertPtrReturn(pMsgHdr, false); + + PVBOXHGCMCMD pCmd = pMsgHdr->pCmd; + AssertPtrReturn(pCmd, false); + + PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort; + AssertPtrReturn(pHgcmPort, false); + + return pHgcmPort->pfnIsCmdRestored(pHgcmPort, pCmd); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallCancelled} + */ +/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle) +{ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle; + AssertPtrReturn(pMsgHdr, false); + + PVBOXHGCMCMD pCmd = pMsgHdr->pCmd; + AssertPtrReturn(pCmd, false); + + PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort; + AssertPtrReturn(pHgcmPort, false); + + return pHgcmPort->pfnIsCmdCancelled(pHgcmPort, pCmd); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamRegisterV} + */ +/* static */ DECLCALLBACK(int) +HGCMService::svcHlpStamRegisterV(void *pvInstance, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility, + STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list va) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + + return STAMR3RegisterVU(pService->m_pUVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, va); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnStamDeregisterV} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpStamDeregisterV(void *pvInstance, const char *pszPatFmt, va_list va) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + + if (pService->m_pUVM) + return STAMR3DeregisterV(pService->m_pUVM, pszPatFmt, va); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoRegister} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoRegister(void *pvInstance, const char *pszName, const char *pszDesc, + PFNDBGFHANDLEREXT pfnHandler, void *pvUser) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + + return DBGFR3InfoRegisterExternal(pService->m_pUVM, pszName, pszDesc, pfnHandler, pvUser); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnInfoDeregister} + */ +/* static */ DECLCALLBACK(int) HGCMService::svcHlpInfoDeregister(void *pvInstance, const char *pszName) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, VERR_INVALID_PARAMETER); + if (pService->m_pUVM) + return DBGFR3InfoDeregisterExternal(pService->m_pUVM, pszName); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetRequestor} + */ +/* static */ DECLCALLBACK(uint32_t) HGCMService::svcHlpGetRequestor(VBOXHGCMCALLHANDLE hCall) +{ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)(hCall); + AssertPtrReturn(pMsgHdr, VMMDEV_REQUESTOR_LOWEST); + + PVBOXHGCMCMD pCmd = pMsgHdr->pCmd; + AssertPtrReturn(pCmd, VMMDEV_REQUESTOR_LOWEST); + + PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort; + AssertPtrReturn(pHgcmPort, VMMDEV_REQUESTOR_LOWEST); + + return pHgcmPort->pfnGetRequestor(pHgcmPort, pCmd); +} + +/** + * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnGetVMMDevSessionId} + */ +/* static */ DECLCALLBACK(uint64_t) HGCMService::svcHlpGetVMMDevSessionId(void *pvInstance) +{ + HGCMService *pService = static_cast <HGCMService *>(pvInstance); + AssertPtrReturn(pService, UINT64_MAX); + + PPDMIHGCMPORT pHgcmPort = pService->m_pHgcmPort; + AssertPtrReturn(pHgcmPort, UINT64_MAX); + + return pHgcmPort->pfnGetVMMDevSessionId(pHgcmPort); +} + + +static DECLCALLBACK(int) hgcmMsgCompletionCallback(int32_t result, HGCMMsgCore *pMsgCore) +{ + /* Call the VMMDev port interface to issue IRQ notification. */ + HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)pMsgCore; + + LogFlow(("MAIN::hgcmMsgCompletionCallback: message %p\n", pMsgCore)); + + if (pMsgHdr->pHGCMPort) + { + if (!g_fResetting) + return pMsgHdr->pHGCMPort->pfnCompleted(pMsgHdr->pHGCMPort, + g_fSaveState ? VINF_HGCM_SAVE_STATE : result, pMsgHdr->pCmd); + return VERR_ALREADY_RESET; /* best I could find. */ + } + return VERR_NOT_AVAILABLE; +} + +/* + * The main HGCM methods of the service. + */ + +int HGCMService::instanceCreate(const char *pszServiceLibrary, const char *pszServiceName, PUVM pUVM, PPDMIHGCMPORT pHgcmPort) +{ + LogFlowFunc(("name %s, lib %s\n", pszServiceName, pszServiceLibrary)); + /* The maximum length of the thread name, allowed by the RT is 15. */ + char szThreadName[16]; + if (!strncmp(pszServiceName, RT_STR_TUPLE("VBoxShared"))) + RTStrPrintf(szThreadName, sizeof(szThreadName), "Sh%s", pszServiceName + 10); + else if (!strncmp(pszServiceName, RT_STR_TUPLE("VBox"))) + RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName + 4); + else + RTStrCopy(szThreadName, sizeof(szThreadName), pszServiceName); + + int rc = hgcmThreadCreate(&m_pThread, szThreadName, hgcmServiceThread, this, pszServiceName, pUVM); + + if (RT_SUCCESS(rc)) + { + m_pszSvcName = RTStrDup(pszServiceName); + m_pszSvcLibrary = RTStrDup(pszServiceLibrary); + + if (!m_pszSvcName || !m_pszSvcLibrary) + { + RTStrFree(m_pszSvcLibrary); + m_pszSvcLibrary = NULL; + + RTStrFree(m_pszSvcName); + m_pszSvcName = NULL; + + rc = VERR_NO_MEMORY; + } + else + { + m_pUVM = pUVM; + m_pHgcmPort = pHgcmPort; + + /* Register statistics: */ + STAMR3RegisterFU(pUVM, &m_StatHandleMsg, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, + "Message handling", "/HGCM/%s/Msg", pszServiceName); + + /* Initialize service helpers table. */ + m_svcHelpers.pfnCallComplete = svcHlpCallComplete; + m_svcHelpers.pvInstance = this; + m_svcHelpers.pfnDisconnectClient = svcHlpDisconnectClient; + m_svcHelpers.pfnIsCallRestored = svcHlpIsCallRestored; + m_svcHelpers.pfnIsCallCancelled = svcHlpIsCallCancelled; + m_svcHelpers.pfnStamRegisterV = svcHlpStamRegisterV; + m_svcHelpers.pfnStamDeregisterV = svcHlpStamDeregisterV; + m_svcHelpers.pfnInfoRegister = svcHlpInfoRegister; + m_svcHelpers.pfnInfoDeregister = svcHlpInfoDeregister; + m_svcHelpers.pfnGetRequestor = svcHlpGetRequestor; + m_svcHelpers.pfnGetVMMDevSessionId = svcHlpGetVMMDevSessionId; + + /* Execute the load request on the service thread. */ + HGCMMsgCore *pCoreMsg; + rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOAD, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcLoad *pMsg = (HGCMMsgSvcLoad *)pCoreMsg; + + pMsg->pUVM = pUVM; + + rc = hgcmMsgSend(pMsg); + } + } + } + + if (RT_FAILURE(rc)) + { + instanceDestroy(); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +void HGCMService::instanceDestroy(void) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pMsg; + int rc = hgcmMsgAlloc(m_pThread, &pMsg, SVC_MSG_UNLOAD, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + rc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(rc)) + hgcmThreadWait(m_pThread); + } + + if (m_pszSvcName && m_pUVM) + STAMR3DeregisterF(m_pUVM, "/HGCM/%s/*", m_pszSvcName); + m_pUVM = NULL; + m_pHgcmPort = NULL; + + RTStrFree(m_pszSvcLibrary); + m_pszSvcLibrary = NULL; + + RTStrFree(m_pszSvcName); + m_pszSvcName = NULL; + + if (m_paClientIds) + { + RTMemFree(m_paClientIds); + m_paClientIds = NULL; + } +} + +int HGCMService::saveClientState(uint32_t u32ClientId, PSSMHANDLE pSSM) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_SAVESTATE, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg; + + pMsg->u32ClientId = u32ClientId; + pMsg->pSSM = pSSM; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMService::loadClientState(uint32_t u32ClientId, PSSMHANDLE pSSM, uint32_t uVersion) +{ + LogFlowFunc(("%s\n", m_pszSvcName)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_LOADSTATE, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgLoadSaveStateClient *pMsg = (HGCMMsgLoadSaveStateClient *)pCoreMsg; + + pMsg->pSSM = pSSM; + pMsg->uVersion = uVersion; + pMsg->u32ClientId = u32ClientId; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + + +/** The method creates a service and references it. + * + * @param pszServiceLibrary The library to be loaded. + * @param pszServiceName The name of the service. + * @param pUVM The user mode VM handle (for statistics and such). + * @param pHgcmPort The VMMDev HGCM port interface. + * + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::LoadService(const char *pszServiceLibrary, const char *pszServiceName, + PUVM pUVM, PPDMIHGCMPORT pHgcmPort) +{ + LogFlowFunc(("lib %s, name = %s, pUVM = %p\n", pszServiceLibrary, pszServiceName, pUVM)); + + /* Look at already loaded services to avoid double loading. */ + + HGCMService *pSvc; + int rc = HGCMService::ResolveService(&pSvc, pszServiceName); + + if (RT_SUCCESS(rc)) + { + /* The service is already loaded. */ + pSvc->ReleaseService(); + rc = VERR_HGCM_SERVICE_EXISTS; + } + else + { + /* Create the new service. */ + pSvc = new (std::nothrow) HGCMService(); + + if (!pSvc) + { + rc = VERR_NO_MEMORY; + } + else + { + /* Load the library and call the initialization entry point. */ + rc = pSvc->instanceCreate(pszServiceLibrary, pszServiceName, pUVM, pHgcmPort); + + if (RT_SUCCESS(rc)) + { + /* Insert the just created service to list for future references. */ + pSvc->m_pSvcNext = sm_pSvcListHead; + pSvc->m_pSvcPrev = NULL; + + if (sm_pSvcListHead) + { + sm_pSvcListHead->m_pSvcPrev = pSvc; + } + else + { + sm_pSvcListTail = pSvc; + } + + sm_pSvcListHead = pSvc; + + sm_cServices++; + + /* Reference the service (for first time) until it is unloaded on HGCM termination. */ + AssertRelease(pSvc->m_u32RefCnt == 0); + pSvc->ReferenceService(); + + LogFlowFunc(("service %p\n", pSvc)); + } + } + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** The method unloads a service. + * + * @thread main HGCM + */ +void HGCMService::UnloadService(bool fUvmIsInvalid) +{ + LogFlowFunc(("name = %s\n", m_pszSvcName)); + + if (fUvmIsInvalid) + { + m_pUVM = NULL; + m_pHgcmPort = NULL; + } + + /* Remove the service from the list. */ + if (m_pSvcNext) + { + m_pSvcNext->m_pSvcPrev = m_pSvcPrev; + } + else + { + sm_pSvcListTail = m_pSvcPrev; + } + + if (m_pSvcPrev) + { + m_pSvcPrev->m_pSvcNext = m_pSvcNext; + } + else + { + sm_pSvcListHead = m_pSvcNext; + } + + sm_cServices--; + + /* The service must be unloaded only if all clients were disconnected. */ + LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt)); + AssertRelease(m_u32RefCnt == 1); + + /* Now the service can be released. */ + ReleaseService(); +} + +/** The method unloads all services. + * + * @thread main HGCM + */ +/* static */ void HGCMService::UnloadAll(bool fUvmIsInvalid) +{ + while (sm_pSvcListHead) + { + sm_pSvcListHead->UnloadService(fUvmIsInvalid); + } +} + +/** The method obtains a referenced pointer to the service with + * specified name. The caller must call ReleaseService when + * the pointer is no longer needed. + * + * @param ppSvc Where to store the pointer to the service. + * @param pszServiceName The name of the service. + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::ResolveService(HGCMService **ppSvc, const char *pszServiceName) +{ + LogFlowFunc(("ppSvc = %p name = %s\n", + ppSvc, pszServiceName)); + + if (!ppSvc || !pszServiceName) + { + return VERR_INVALID_PARAMETER; + } + + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + if (strcmp(pSvc->m_pszSvcName, pszServiceName) == 0) + { + break; + } + + pSvc = pSvc->m_pSvcNext; + } + + LogFlowFunc(("lookup in the list is %p\n", pSvc)); + + if (pSvc == NULL) + { + *ppSvc = NULL; + return VERR_HGCM_SERVICE_NOT_FOUND; + } + + pSvc->ReferenceService(); + + *ppSvc = pSvc; + + return VINF_SUCCESS; +} + +/** The method increases reference counter. + * + * @thread main HGCM + */ +void HGCMService::ReferenceService(void) +{ + ASMAtomicIncU32(&m_u32RefCnt); + LogFlowFunc(("[%s] m_u32RefCnt = %d\n", m_pszSvcName, m_u32RefCnt)); +} + +/** The method dereferences a service and deletes it when no more refs. + * + * @thread main HGCM + */ +void HGCMService::ReleaseService(void) +{ + LogFlowFunc(("m_u32RefCnt = %d\n", m_u32RefCnt)); + uint32_t u32RefCnt = ASMAtomicDecU32(&m_u32RefCnt); + AssertRelease(u32RefCnt != ~0U); + + LogFlowFunc(("u32RefCnt = %d, name %s\n", u32RefCnt, m_pszSvcName)); + + if (u32RefCnt == 0) + { + instanceDestroy(); + delete this; + } +} + +/** The method is called when the VM is being reset or terminated + * and disconnects all clients from all services. + * + * @thread main HGCM + */ +/* static */ void HGCMService::Reset(void) +{ + g_fResetting = true; + + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + while (pSvc->m_cClients && pSvc->m_paClientIds) + { + LogFlowFunc(("handle %d\n", pSvc->m_paClientIds[0])); + pSvc->DisconnectClient(pSvc->m_paClientIds[0], false); + } + + pSvc = pSvc->m_pSvcNext; + } + + g_fResetting = false; +} + +/** The method saves the HGCM state. + * + * @param pSSM The saved state context. + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::SaveState(PSSMHANDLE pSSM) +{ + /* Save the current handle count and restore afterwards to avoid client id conflicts. */ + int rc = SSMR3PutU32(pSSM, hgcmObjQueryHandleCount()); + AssertRCReturn(rc, rc); + + LogFlowFunc(("%d services to be saved:\n", sm_cServices)); + + /* Save number of services. */ + rc = SSMR3PutU32(pSSM, sm_cServices); + AssertRCReturn(rc, rc); + + /* Save every service. */ + HGCMService *pSvc = sm_pSvcListHead; + + while (pSvc) + { + LogFlowFunc(("Saving service [%s]\n", pSvc->m_pszSvcName)); + + /* Save the length of the service name. */ + rc = SSMR3PutU32(pSSM, (uint32_t) strlen(pSvc->m_pszSvcName) + 1); + AssertRCReturn(rc, rc); + + /* Save the name of the service. */ + rc = SSMR3PutStrZ(pSSM, pSvc->m_pszSvcName); + AssertRCReturn(rc, rc); + + /* Save the number of clients. */ + rc = SSMR3PutU32(pSSM, pSvc->m_cClients); + AssertRCReturn(rc, rc); + + /* Call the service for every client. Normally a service must not have + * a global state to be saved: only per client info is relevant. + * The global state of a service is configured during VM startup. + */ + uint32_t i; + + for (i = 0; i < pSvc->m_cClients; i++) + { + uint32_t u32ClientId = pSvc->m_paClientIds[i]; + + Log(("client id 0x%08X\n", u32ClientId)); + + /* Save the client id. (fRequestor is saved via SVC_MSG_SAVESTATE for convenience.) */ + rc = SSMR3PutU32(pSSM, u32ClientId); + AssertRCReturn(rc, rc); + + /* Call the service, so the operation is executed by the service thread. */ + rc = pSvc->saveClientState(u32ClientId, pSSM); + AssertRCReturn(rc, rc); + } + + pSvc = pSvc->m_pSvcNext; + } + + return VINF_SUCCESS; +} + +/** The method loads saved HGCM state. + * + * @param pSSM The saved state handle. + * @param uVersion The state version being loaded. + * @return VBox rc. + * @thread main HGCM + */ +/* static */ int HGCMService::LoadState(PSSMHANDLE pSSM, uint32_t uVersion) +{ + /* Restore handle count to avoid client id conflicts. */ + uint32_t u32; + + int rc = SSMR3GetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + + hgcmObjSetHandleCount(u32); + + /* Get the number of services. */ + uint32_t cServices; + + rc = SSMR3GetU32(pSSM, &cServices); + AssertRCReturn(rc, rc); + + LogFlowFunc(("%d services to be restored:\n", cServices)); + + while (cServices--) + { + /* Get the length of the service name. */ + rc = SSMR3GetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + AssertReturn(u32 <= VBOX_HGCM_SVC_NAME_MAX_BYTES, VERR_SSM_UNEXPECTED_DATA); + + /* Get the service name. */ + char szServiceName[VBOX_HGCM_SVC_NAME_MAX_BYTES]; + rc = SSMR3GetStrZ(pSSM, szServiceName, u32); + AssertRCReturn(rc, rc); + + LogRel(("HGCM: Restoring [%s]\n", szServiceName)); + + /* Resolve the service instance. */ + HGCMService *pSvc; + rc = ResolveService(&pSvc, szServiceName); + AssertLogRelMsgReturn(pSvc, ("rc=%Rrc, %s\n", rc, szServiceName), VERR_SSM_UNEXPECTED_DATA); + + /* Get the number of clients. */ + uint32_t cClients; + rc = SSMR3GetU32(pSSM, &cClients); + if (RT_FAILURE(rc)) + { + pSvc->ReleaseService(); + AssertFailed(); + return rc; + } + + while (cClients--) + { + /* Get the client ID and fRequest (convieniently save via SVC_MSG_SAVESTATE + but restored here in time for calling CreateAndConnectClient). */ + uint32_t u32ClientId; + rc = SSMR3GetU32(pSSM, &u32ClientId); + uint32_t fRequestor = VMMDEV_REQUESTOR_LEGACY; + if (RT_SUCCESS(rc) && uVersion > HGCM_SAVED_STATE_VERSION_V2) + rc = SSMR3GetU32(pSSM, &fRequestor); + AssertLogRelMsgRCReturnStmt(rc, ("rc=%Rrc, %s\n", rc, szServiceName), pSvc->ReleaseService(), rc); + + /* Connect the client. */ + rc = pSvc->CreateAndConnectClient(NULL, u32ClientId, fRequestor, true /*fRestoring*/); + AssertLogRelMsgRCReturnStmt(rc, ("rc=%Rrc, %s\n", rc, szServiceName), pSvc->ReleaseService(), rc); + + /* Call the service, so the operation is executed by the service thread. */ + rc = pSvc->loadClientState(u32ClientId, pSSM, uVersion); + AssertLogRelMsgRCReturnStmt(rc, ("rc=%Rrc, %s\n", rc, szServiceName), pSvc->ReleaseService(), rc); + } + + pSvc->ReleaseService(); + } + + return VINF_SUCCESS; +} + +/* Create a new client instance and connect it to the service. + * + * @param pu32ClientIdOut If not NULL, then the method must generate a new handle for the client. + * If NULL, use the given 'u32ClientIdIn' handle. + * @param u32ClientIdIn The handle for the client, when 'pu32ClientIdOut' is NULL. + * @param fRequestor The requestor flags, VMMDEV_REQUESTOR_LEGACY if not available. + * @param fRestoring Set if we're restoring a saved state. + * @return VBox status code. + */ +int HGCMService::CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring) +{ + LogFlowFunc(("pu32ClientIdOut = %p, u32ClientIdIn = %d, fRequestor = %#x, fRestoring = %d\n", + pu32ClientIdOut, u32ClientIdIn, fRequestor, fRestoring)); + + /* Allocate a client information structure. */ + HGCMClient *pClient = new (std::nothrow) HGCMClient(fRequestor); + + if (!pClient) + { + Log1WarningFunc(("Could not allocate HGCMClient!!!\n")); + return VERR_NO_MEMORY; + } + + uint32_t handle; + + if (pu32ClientIdOut != NULL) + { + handle = hgcmObjGenerateHandle(pClient); + } + else + { + handle = hgcmObjAssignHandle(pClient, u32ClientIdIn); + } + + LogFlowFunc(("client id = %d\n", handle)); + + AssertRelease(handle); + + /* Initialize the HGCM part of the client. */ + int rc = pClient->Init(this); + + if (RT_SUCCESS(rc)) + { + /* Call the service. */ + HGCMMsgCore *pCoreMsg; + + rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_CONNECT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pCoreMsg; + + pMsg->u32ClientId = handle; + pMsg->fRequestor = fRequestor; + pMsg->fRestoring = fRestoring; + + rc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(rc)) + { + /* Add the client Id to the array. */ + if (m_cClients == m_cClientsAllocated) + { + const uint32_t cDelta = 64; + + /* Guards against integer overflow on 32bit arch and also limits size of m_paClientIds array to 4GB*/ + if (m_cClientsAllocated < UINT32_MAX / sizeof(m_paClientIds[0]) - cDelta) + { + uint32_t *paClientIdsNew; + + paClientIdsNew = (uint32_t *)RTMemRealloc(m_paClientIds, + (m_cClientsAllocated + cDelta) * sizeof(m_paClientIds[0])); + Assert(paClientIdsNew); + + if (paClientIdsNew) + { + m_paClientIds = paClientIdsNew; + m_cClientsAllocated += cDelta; + } + else + { + rc = VERR_NO_MEMORY; + } + } + else + { + rc = VERR_NO_MEMORY; + } + } + + m_paClientIds[m_cClients] = handle; + m_cClients++; + } + } + } + + if (RT_FAILURE(rc)) + { + hgcmObjDeleteHandle(handle); + } + else + { + if (pu32ClientIdOut != NULL) + { + *pu32ClientIdOut = handle; + } + + ReferenceService(); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/* Disconnect the client from the service and delete the client handle. + * + * @param u32ClientId The handle of the client. + * @return VBox rc. + */ +int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService) +{ + int rc = VINF_SUCCESS; + + LogFlowFunc(("client id = %d, fFromService = %d\n", u32ClientId, fFromService)); + + if (!fFromService) + { + /* Call the service. */ + HGCMMsgCore *pCoreMsg; + + rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_DISCONNECT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)pCoreMsg; + + pMsg->u32ClientId = u32ClientId; + + rc = hgcmMsgSend(pMsg); + } + else + { + LogRel(("(%d, %d) [%s] hgcmMsgAlloc(%p, SVC_MSG_DISCONNECT) failed %Rrc\n", + u32ClientId, fFromService, RT_VALID_PTR(m_pszSvcName)? m_pszSvcName: "", m_pThread, rc)); + } + } + + /* Remove the client id from the array in any case, rc does not matter. */ + uint32_t i; + + for (i = 0; i < m_cClients; i++) + { + if (m_paClientIds[i] == u32ClientId) + { + m_cClients--; + + if (m_cClients > i) + memmove(&m_paClientIds[i], &m_paClientIds[i + 1], sizeof(m_paClientIds[0]) * (m_cClients - i)); + + /* Delete the client handle. */ + hgcmObjDeleteHandle(u32ClientId); + + /* The service must be released. */ + ReleaseService(); + + break; + } + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMService::RegisterExtension(HGCMSVCEXTHANDLE handle, + PFNHGCMSVCEXT pfnExtension, + void *pvExtension) +{ + LogFlowFunc(("%s\n", handle->pszServiceName)); + + /* Forward the message to the service thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_REGEXT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcRegisterExtension *pMsg = (HGCMMsgSvcRegisterExtension *)pCoreMsg; + + pMsg->handle = handle; + pMsg->pfnExtension = pfnExtension; + pMsg->pvExtension = pvExtension; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +void HGCMService::UnregisterExtension(HGCMSVCEXTHANDLE handle) +{ + /* Forward the message to the service thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_UNREGEXT, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgSvcUnregisterExtension *pMsg = (HGCMMsgSvcUnregisterExtension *)pCoreMsg; + + pMsg->handle = handle; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); +} + +/** Perform a guest call to the service. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param u32ClientId The client handle to be disconnected and deleted. + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @param tsArrival The STAM_GET_TS() value when the request arrived. + * @return VBox rc. + * @retval VINF_HGCM_ASYNC_EXECUTE on success. + */ +int HGCMService::GuestCall(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t u32ClientId, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival) +{ + LogFlow(("MAIN::HGCMService::GuestCall\n")); + + int rc; + HGCMMsgCall *pMsg = new (std::nothrow) HGCMMsgCall(m_pThread); + if (pMsg) + { + pMsg->Reference(); /** @todo starts out with zero references. */ + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->u32ClientId = u32ClientId; + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + pMsg->tsArrival = tsArrival; + + rc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + else + { + Log(("MAIN::HGCMService::GuestCall: Message allocation failed\n")); + rc = VERR_NO_MEMORY; + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Guest cancelled a request (call, connection attempt, disconnect attempt). + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param idClient The client handle to be disconnected and deleted. + * @return VBox rc. + */ +void HGCMService::GuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient) +{ + LogFlow(("MAIN::HGCMService::GuestCancelled\n")); + + if (m_fntable.pfnCancelled) + { + HGCMMsgCancelled *pMsg = new (std::nothrow) HGCMMsgCancelled(m_pThread); + if (pMsg) + { + pMsg->Reference(); /** @todo starts out with zero references. */ + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->idClient = idClient; + + hgcmMsgPost(pMsg, NULL); + } + else + Log(("MAIN::HGCMService::GuestCancelled: Message allocation failed\n")); + } +} + +/** Perform a host call the service. + * + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @return VBox rc. + */ +int HGCMService::HostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + LogFlowFunc(("%s u32Function = %d, cParms = %d, paParms = %p\n", + m_pszSvcName, u32Function, cParms, paParms)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_HOSTCALL, hgcmMessageAllocSvc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgHostCallSvc *pMsg = (HGCMMsgHostCallSvc *)pCoreMsg; + + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Posts a broadcast notification event to all interested services. + * + * @param enmEvent The notification event. + */ +/*static*/ void HGCMService::BroadcastNotify(HGCMNOTIFYEVENT enmEvent) +{ + for (HGCMService *pService = sm_pSvcListHead; pService != NULL; pService = pService->m_pSvcNext) + { + pService->Notify(enmEvent); + } +} + +/** Posts a broadcast notification event to the service. + * + * @param enmEvent The notification event. + */ +void HGCMService::Notify(HGCMNOTIFYEVENT enmEvent) +{ + LogFlowFunc(("%s enmEvent=%d pfnNotify=%p\n", m_pszSvcName, enmEvent, m_fntable.pfnNotify)); + if (m_fntable.pfnNotify) + { + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_NOTIFY, hgcmMessageAllocSvc); + if (RT_SUCCESS(rc)) + { + HGCMMsgNotify *pMsg = (HGCMMsgNotify *)pCoreMsg; + pMsg->enmEvent = enmEvent; + + rc = hgcmMsgPost(pMsg, NULL); + AssertRC(rc); + } + } +} + +/* + * Main HGCM thread that manages services. + */ + +/* Messages processed by the main HGCM thread. */ +#define HGCM_MSG_CONNECT (10) /**< Connect a client to a service. */ +#define HGCM_MSG_DISCONNECT (11) /**< Disconnect the specified client id. */ +#define HGCM_MSG_LOAD (12) /**< Load the service. */ +#define HGCM_MSG_HOSTCALL (13) /**< Call the service. */ +#define HGCM_MSG_LOADSTATE (14) /**< Load saved state for the specified service. */ +#define HGCM_MSG_SAVESTATE (15) /**< Save state for the specified service. */ +#define HGCM_MSG_RESET (16) /**< Disconnect all clients from the specified service. */ +#define HGCM_MSG_QUIT (17) /**< Unload all services and terminate the thread. */ +#define HGCM_MSG_REGEXT (18) /**< Register a service extension. */ +#define HGCM_MSG_UNREGEXT (19) /**< Unregister a service extension. */ +#define HGCM_MSG_BRD_NOTIFY (20) /**< Broadcast notification event (VM state change). */ + +class HGCMMsgMainConnect: public HGCMMsgHeader +{ + public: + /* Service name. */ + const char *pszServiceName; + /* Where to store the client handle. */ + uint32_t *pu32ClientId; +}; + +class HGCMMsgMainDisconnect: public HGCMMsgHeader +{ + public: + /* Handle of the client to be disconnected. */ + uint32_t u32ClientId; +}; + +class HGCMMsgMainLoad: public HGCMMsgCore +{ + public: + /* Name of the library to be loaded. */ + const char *pszServiceLibrary; + /* Name to be assigned to the service. */ + const char *pszServiceName; + /** The user mode VM handle (for statistics and such). */ + PUVM pUVM; + /** The HGCM port on the VMMDev device (for session ID and such). */ + PPDMIHGCMPORT pHgcmPort; +}; + +class HGCMMsgMainHostCall: public HGCMMsgCore +{ + public: + /* Which service to call. */ + const char *pszServiceName; + /* Function number. */ + uint32_t u32Function; + /* Number of the function parameters. */ + uint32_t cParms; + /* Pointer to array of the function parameters. */ + VBOXHGCMSVCPARM *paParms; +}; + +class HGCMMsgMainLoadSaveState: public HGCMMsgCore +{ + public: + /** Saved state handle. */ + PSSMHANDLE pSSM; + /** The HGCM saved state version being loaded (ignore for save). */ + uint32_t uVersion; +}; + +class HGCMMsgMainReset: public HGCMMsgCore +{ + public: + /** Set if this is actually a shutdown and not a VM reset. */ + bool fForShutdown; +}; + +class HGCMMsgMainQuit: public HGCMMsgCore +{ + public: + /** Whether UVM has gone invalid already or not. */ + bool fUvmIsInvalid; +}; + +class HGCMMsgMainRegisterExtension: public HGCMMsgCore +{ + public: + /** Returned handle to be used in HGCMMsgMainUnregisterExtension. */ + HGCMSVCEXTHANDLE *pHandle; + /** Name of the service. */ + const char *pszServiceName; + /** The extension entry point. */ + PFNHGCMSVCEXT pfnExtension; + /** The extension pointer. */ + void *pvExtension; +}; + +class HGCMMsgMainUnregisterExtension: public HGCMMsgCore +{ + public: + /* Handle of the registered extension. */ + HGCMSVCEXTHANDLE handle; +}; + +class HGCMMsgMainBroadcastNotify: public HGCMMsgCore +{ + public: + /** The notification event. */ + HGCMNOTIFYEVENT enmEvent; +}; + + +static HGCMMsgCore *hgcmMainMessageAlloc (uint32_t u32MsgId) +{ + switch (u32MsgId) + { + case HGCM_MSG_CONNECT: return new HGCMMsgMainConnect(); + case HGCM_MSG_DISCONNECT: return new HGCMMsgMainDisconnect(); + case HGCM_MSG_LOAD: return new HGCMMsgMainLoad(); + case HGCM_MSG_HOSTCALL: return new HGCMMsgMainHostCall(); + case HGCM_MSG_LOADSTATE: + case HGCM_MSG_SAVESTATE: return new HGCMMsgMainLoadSaveState(); + case HGCM_MSG_RESET: return new HGCMMsgMainReset(); + case HGCM_MSG_QUIT: return new HGCMMsgMainQuit(); + case HGCM_MSG_REGEXT: return new HGCMMsgMainRegisterExtension(); + case HGCM_MSG_UNREGEXT: return new HGCMMsgMainUnregisterExtension(); + case HGCM_MSG_BRD_NOTIFY: return new HGCMMsgMainBroadcastNotify(); + + default: + AssertReleaseMsgFailed(("Msg id = %08X\n", u32MsgId)); + } + + return NULL; +} + + +/* The main HGCM thread handler. */ +static DECLCALLBACK(void) hgcmThread(HGCMThread *pThread, void *pvUser) +{ + LogFlowFunc(("pThread = %p, pvUser = %p\n", pThread, pvUser)); + + NOREF(pvUser); + + bool fQuit = false; + + while (!fQuit) + { + HGCMMsgCore *pMsgCore; + int rc = hgcmMsgGet(pThread, &pMsgCore); + + if (RT_FAILURE(rc)) + { + /* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */ + AssertMsgFailed(("%Rrc\n", rc)); + break; + } + + uint32_t u32MsgId = pMsgCore->MsgId(); + + switch (u32MsgId) + { + case HGCM_MSG_CONNECT: + { + HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_CONNECT pszServiceName %s, pu32ClientId %p\n", + pMsg->pszServiceName, pMsg->pu32ClientId)); + + /* Resolve the service name to the pointer to service instance. + */ + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + + if (RT_SUCCESS(rc)) + { + /* Call the service instance method. */ + rc = pService->CreateAndConnectClient(pMsg->pu32ClientId, + 0, + pMsg->pHGCMPort->pfnGetRequestor(pMsg->pHGCMPort, pMsg->pCmd), + pMsg->pHGCMPort->pfnIsCmdRestored(pMsg->pHGCMPort, pMsg->pCmd)); + + /* Release the service after resolve. */ + pService->ReleaseService(); + } + } break; + + case HGCM_MSG_DISCONNECT: + { + HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_DISCONNECT u32ClientId = %d\n", + pMsg->u32ClientId)); + + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT); + + if (!pClient) + { + rc = VERR_HGCM_INVALID_CLIENT_ID; + break; + } + + /* The service the client belongs to. */ + HGCMService *pService = pClient->pService; + + /* Call the service instance to disconnect the client. */ + rc = pService->DisconnectClient(pMsg->u32ClientId, false); + + hgcmObjDereference(pClient); + } break; + + case HGCM_MSG_LOAD: + { + HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_LOAD pszServiceName = %s, pMsg->pszServiceLibrary = %s, pMsg->pUVM = %p\n", + pMsg->pszServiceName, pMsg->pszServiceLibrary, pMsg->pUVM)); + + rc = HGCMService::LoadService(pMsg->pszServiceLibrary, pMsg->pszServiceName, pMsg->pUVM, pMsg->pHgcmPort); + } break; + + case HGCM_MSG_HOSTCALL: + { + HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_HOSTCALL pszServiceName %s, u32Function %d, cParms %d, paParms %p\n", + pMsg->pszServiceName, pMsg->u32Function, pMsg->cParms, pMsg->paParms)); + + /* Resolve the service name to the pointer to service instance. */ + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, pMsg->pszServiceName); + + if (RT_SUCCESS(rc)) + { + rc = pService->HostCall(pMsg->u32Function, pMsg->cParms, pMsg->paParms); + + pService->ReleaseService(); + } + } break; + + case HGCM_MSG_BRD_NOTIFY: + { + HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_BRD_NOTIFY enmEvent=%d\n", pMsg->enmEvent)); + + HGCMService::BroadcastNotify(pMsg->enmEvent); + } break; + + case HGCM_MSG_RESET: + { + LogFlowFunc(("HGCM_MSG_RESET\n")); + + HGCMService::Reset(); + + HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore; + if (!pMsg->fForShutdown) + HGCMService::BroadcastNotify(HGCMNOTIFYEVENT_RESET); + } break; + + case HGCM_MSG_LOADSTATE: + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_LOADSTATE\n")); + + rc = HGCMService::LoadState(pMsg->pSSM, pMsg->uVersion); + } break; + + case HGCM_MSG_SAVESTATE: + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_SAVESTATE\n")); + + rc = HGCMService::SaveState(pMsg->pSSM); + } break; + + case HGCM_MSG_QUIT: + { + HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore; + LogFlowFunc(("HGCM_MSG_QUIT\n")); + + HGCMService::UnloadAll(pMsg->fUvmIsInvalid); + + fQuit = true; + } break; + + case HGCM_MSG_REGEXT: + { + HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_REGEXT\n")); + + /* Allocate the handle data. */ + HGCMSVCEXTHANDLE handle = (HGCMSVCEXTHANDLE)RTMemAllocZ(sizeof(struct _HGCMSVCEXTHANDLEDATA) + + strlen(pMsg->pszServiceName) + + sizeof(char)); + + if (handle == NULL) + { + rc = VERR_NO_MEMORY; + } + else + { + handle->pszServiceName = (char *)((uint8_t *)handle + sizeof(struct _HGCMSVCEXTHANDLEDATA)); + strcpy(handle->pszServiceName, pMsg->pszServiceName); + + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, handle->pszServiceName); + + if (RT_SUCCESS(rc)) + { + pService->RegisterExtension(handle, pMsg->pfnExtension, pMsg->pvExtension); + + pService->ReleaseService(); + } + + if (RT_FAILURE(rc)) + { + RTMemFree(handle); + } + else + { + *pMsg->pHandle = handle; + } + } + } break; + + case HGCM_MSG_UNREGEXT: + { + HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pMsgCore; + + LogFlowFunc(("HGCM_MSG_UNREGEXT\n")); + + HGCMService *pService; + rc = HGCMService::ResolveService(&pService, pMsg->handle->pszServiceName); + + if (RT_SUCCESS(rc)) + { + pService->UnregisterExtension(pMsg->handle); + + pService->ReleaseService(); + } + + RTMemFree(pMsg->handle); + } break; + + default: + { + AssertMsgFailed(("hgcmThread: Unsupported message number %08X!!!\n", u32MsgId)); + rc = VERR_NOT_SUPPORTED; + } break; + } + + /* Complete the message processing. */ + hgcmMsgComplete(pMsgCore, rc); + + LogFlowFunc(("message processed %Rrc\n", rc)); + } +} + + +/* + * The HGCM API. + */ + +/** The main hgcm thread. */ +static HGCMThread *g_pHgcmThread = 0; + +/* + * Public HGCM functions. + * + * hgcmGuest* - called as a result of the guest HGCM requests. + * hgcmHost* - called by the host. + */ + +/* Load a HGCM service from the specified library. + * Assign the specified name to the service. + * + * @param pszServiceLibrary The library to be loaded. + * @param pszServiceName The name to be assigned to the service. + * @param pUVM The user mode VM handle (for statistics and such). + * @param pHgcmPort The HGCM port on the VMMDev device (for session ID and such). + * @return VBox rc. + */ +int HGCMHostLoad(const char *pszServiceLibrary, + const char *pszServiceName, + PUVM pUVM, + PPDMIHGCMPORT pHgcmPort) +{ + LogFlowFunc(("lib = %s, name = %s\n", pszServiceLibrary, pszServiceName)); + + if (!pszServiceLibrary || !pszServiceName) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_LOAD, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. Since the message is synchronous, use the supplied pointers. */ + HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pCoreMsg; + + pMsg->pszServiceLibrary = pszServiceLibrary; + pMsg->pszServiceName = pszServiceName; + pMsg->pUVM = pUVM; + pMsg->pHgcmPort = pHgcmPort; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/* Register a HGCM service extension. + * + * @param pHandle Returned handle for the registered extension. + * @param pszServiceName The name of the service. + * @param pfnExtension The extension entry point (callback). + * @param pvExtension The extension pointer. + * @return VBox rc. + */ +int HGCMHostRegisterServiceExtension(HGCMSVCEXTHANDLE *pHandle, + const char *pszServiceName, + PFNHGCMSVCEXT pfnExtension, + void *pvExtension) +{ + LogFlowFunc(("pHandle = %p, name = %s, pfn = %p, rv = %p\n", pHandle, pszServiceName, pfnExtension, pvExtension)); + + if (!pHandle || !pszServiceName || !pfnExtension) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_REGEXT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. Since the message is synchronous, use the supplied pointers. */ + HGCMMsgMainRegisterExtension *pMsg = (HGCMMsgMainRegisterExtension *)pCoreMsg; + + pMsg->pHandle = pHandle; + pMsg->pszServiceName = pszServiceName; + pMsg->pfnExtension = pfnExtension; + pMsg->pvExtension = pvExtension; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("*pHandle = %p, rc = %Rrc\n", *pHandle, rc)); + return rc; +} + +void HGCMHostUnregisterServiceExtension(HGCMSVCEXTHANDLE handle) +{ + LogFlowFunc(("handle = %p\n", handle)); + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_UNREGEXT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. */ + HGCMMsgMainUnregisterExtension *pMsg = (HGCMMsgMainUnregisterExtension *)pCoreMsg; + + pMsg->handle = handle; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return; +} + +/* Find a service and inform it about a client connection, create a client handle. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param pszServiceName The name of the service to be connected to. + * @param pu32ClientId Where the store the created client handle. + * @return VBox rc. + */ +int HGCMGuestConnect(PPDMIHGCMPORT pHGCMPort, + PVBOXHGCMCMD pCmd, + const char *pszServiceName, + uint32_t *pu32ClientId) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, name = %s, pu32ClientId = %p\n", + pHGCMPort, pCmd, pszServiceName, pu32ClientId)); + + if (pHGCMPort == NULL || pCmd == NULL || pszServiceName == NULL || pu32ClientId == NULL) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_CONNECT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. Since 'pszServiceName' and 'pu32ClientId' + * will not be deallocated by the caller until the message is completed, + * use the supplied pointers. + */ + HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pCoreMsg; + + pMsg->pHGCMPort = pHGCMPort; + pMsg->pCmd = pCmd; + pMsg->pszServiceName = pszServiceName; + pMsg->pu32ClientId = pu32ClientId; + + rc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/* Tell a service that the client is disconnecting, destroy the client handle. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param u32ClientId The client handle to be disconnected and deleted. + * @return VBox rc. + */ +int HGCMGuestDisconnect(PPDMIHGCMPORT pHGCMPort, + PVBOXHGCMCMD pCmd, + uint32_t u32ClientId) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d\n", + pHGCMPort, pCmd, u32ClientId)); + + if (!pHGCMPort || !pCmd || !u32ClientId) + { + return VERR_INVALID_PARAMETER; + } + + /* Forward the request to the main hgcm thread. */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_DISCONNECT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + /* Initialize the message. */ + HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pCoreMsg; + + pMsg->pCmd = pCmd; + pMsg->pHGCMPort = pHGCMPort; + pMsg->u32ClientId = u32ClientId; + + rc = hgcmMsgPost(pMsg, hgcmMsgCompletionCallback); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Helper to send either HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE messages to the main HGCM thread. + * + * @param pSSM The SSM handle. + * @param idMsg The message to be sent: HGCM_MSG_SAVESTATE or HGCM_MSG_LOADSTATE. + * @param uVersion The state version being loaded. + * @return VBox rc. + */ +static int hgcmHostLoadSaveState(PSSMHANDLE pSSM, uint32_t idMsg, uint32_t uVersion) +{ + LogFlowFunc(("pSSM = %p, idMsg = %d, uVersion = uVersion\n", pSSM, idMsg)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, idMsg, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainLoadSaveState *pMsg = (HGCMMsgMainLoadSaveState *)pCoreMsg; + AssertRelease(pMsg); + + pMsg->pSSM = pSSM; + pMsg->uVersion = uVersion; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Save the state of services. + * + * @param pSSM The SSM handle. + * @return VBox rc. + */ +int HGCMHostSaveState(PSSMHANDLE pSSM) +{ + return hgcmHostLoadSaveState(pSSM, HGCM_MSG_SAVESTATE, HGCM_SAVED_STATE_VERSION); +} + +/** Load the state of services. + * + * @param pSSM The SSM handle. + * @param uVersion The state version being loaded. + * @return VBox rc. + */ +int HGCMHostLoadState(PSSMHANDLE pSSM, uint32_t uVersion) +{ + return hgcmHostLoadSaveState(pSSM, HGCM_MSG_LOADSTATE, uVersion); +} + +/** The guest calls the service. + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param u32ClientId The client handle. + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @param tsArrival The STAM_GET_TS() value when the request arrived. + * @return VBox rc. + */ +int HGCMGuestCall(PPDMIHGCMPORT pHGCMPort, + PVBOXHGCMCMD pCmd, + uint32_t u32ClientId, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM *paParms, + uint64_t tsArrival) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n", + pHGCMPort, pCmd, u32ClientId, u32Function, cParms, paParms)); + + if (!pHGCMPort || !pCmd || u32ClientId == 0) + { + return VERR_INVALID_PARAMETER; + } + + int rc = VERR_HGCM_INVALID_CLIENT_ID; + + /* Resolve the client handle to the client instance pointer. */ + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(u32ClientId, HGCMOBJ_CLIENT); + + if (pClient) + { + AssertRelease(pClient->pService); + + /* Forward the message to the service thread. */ + rc = pClient->pService->GuestCall(pHGCMPort, pCmd, u32ClientId, u32Function, cParms, paParms, tsArrival); + + hgcmObjDereference(pClient); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** The guest cancelled a request (call, connect, disconnect) + * + * @param pHGCMPort The port to be used for completion confirmation. + * @param pCmd The VBox HGCM context. + * @param idClient The client handle. + */ +void HGCMGuestCancelled(PPDMIHGCMPORT pHGCMPort, PVBOXHGCMCMD pCmd, uint32_t idClient) +{ + LogFlowFunc(("pHGCMPort = %p, pCmd = %p, idClient = %d\n", pHGCMPort, pCmd, idClient)); + AssertReturnVoid(pHGCMPort); + AssertReturnVoid(pCmd); + AssertReturnVoid(idClient != 0); + + /* Resolve the client handle to the client instance pointer. */ + HGCMClient *pClient = (HGCMClient *)hgcmObjReference(idClient, HGCMOBJ_CLIENT); + + if (pClient) + { + AssertRelease(pClient->pService); + + /* Forward the message to the service thread. */ + pClient->pService->GuestCancelled(pHGCMPort, pCmd, idClient); + + hgcmObjDereference(pClient); + } + + LogFlowFunc(("returns\n")); +} + +/** The host calls the service. + * + * @param pszServiceName The service name to be called. + * @param u32Function The function number. + * @param cParms Number of parameters. + * @param paParms Pointer to array of parameters. + * @return VBox rc. + */ +int HGCMHostCall(const char *pszServiceName, + uint32_t u32Function, + uint32_t cParms, + VBOXHGCMSVCPARM *paParms) +{ + LogFlowFunc(("name = %s, u32Function = %d, cParms = %d, paParms = %p\n", + pszServiceName, u32Function, cParms, paParms)); + + if (!pszServiceName) + { + return VERR_INVALID_PARAMETER; + } + + /* Host calls go to main HGCM thread that resolves the service name to the + * service instance pointer and then, using the service pointer, forwards + * the message to the service thread. + * So it is slow but host calls are intended mostly for configuration and + * other non-time-critical functions. + */ + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_HOSTCALL, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainHostCall *pMsg = (HGCMMsgMainHostCall *)pCoreMsg; + + pMsg->pszServiceName = (char *)pszServiceName; + pMsg->u32Function = u32Function; + pMsg->cParms = cParms; + pMsg->paParms = paParms; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +/** Posts a notification event to all services. + * + * @param enmEvent The notification event. + * @return VBox rc. + */ +int HGCMBroadcastEvent(HGCMNOTIFYEVENT enmEvent) +{ + LogFlowFunc(("enmEvent=%d\n", enmEvent)); + + HGCMMsgCore *pCoreMsg; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pCoreMsg, HGCM_MSG_BRD_NOTIFY, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainBroadcastNotify *pMsg = (HGCMMsgMainBroadcastNotify *)pCoreMsg; + + pMsg->enmEvent = enmEvent; + + rc = hgcmMsgPost(pMsg, NULL); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + + +int HGCMHostReset(bool fForShutdown) +{ + LogFlowFunc(("\n")); + + /* Disconnect all clients. + */ + + HGCMMsgCore *pMsgCore; + int rc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_RESET, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainReset *pMsg = (HGCMMsgMainReset *)pMsgCore; + + pMsg->fForShutdown = fForShutdown; + + rc = hgcmMsgSend(pMsg); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMHostInit(void) +{ + LogFlowFunc(("\n")); + + int rc = hgcmThreadInit(); + + if (RT_SUCCESS(rc)) + { + /* + * Start main HGCM thread. + */ + + rc = hgcmThreadCreate(&g_pHgcmThread, "MainHGCMthread", hgcmThread, NULL /*pvUser*/, NULL /*pszStatsSubDir*/, NULL /*pUVM*/); + + if (RT_FAILURE(rc)) + LogRel(("Failed to start HGCM thread. HGCM services will be unavailable!!! rc = %Rrc\n", rc)); + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + +int HGCMHostShutdown(bool fUvmIsInvalid /*= false*/) +{ + LogFlowFunc(("\n")); + + /* + * Do HGCMReset and then unload all services. + */ + + int rc = HGCMHostReset(true /*fForShutdown*/); + + if (RT_SUCCESS(rc)) + { + /* Send the quit message to the main hgcmThread. */ + HGCMMsgCore *pMsgCore; + rc = hgcmMsgAlloc(g_pHgcmThread, &pMsgCore, HGCM_MSG_QUIT, hgcmMainMessageAlloc); + + if (RT_SUCCESS(rc)) + { + HGCMMsgMainQuit *pMsg = (HGCMMsgMainQuit *)pMsgCore; + pMsg->fUvmIsInvalid = fUvmIsInvalid; + + rc = hgcmMsgSend(pMsg); + + if (RT_SUCCESS(rc)) + { + /* Wait for the thread termination. */ + hgcmThreadWait(g_pHgcmThread); + g_pHgcmThread = NULL; + + hgcmThreadUninit(); + } + } + } + + LogFlowFunc(("rc = %Rrc\n", rc)); + return rc; +} + |