| /* $Id: HGSMICommon.cpp 103604 2015-10-22 13:10:56Z bird $ */ | |
| /** @file | |
| * VBox Host Guest Shared Memory Interface (HGSMI) - Functions common to both host and guest. | |
| */ | |
| /* | |
| * Copyright (C) 2006-2015 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_DISABLED /* Maybe we can enabled it all the time now? */ | |
| #define LOG_GROUP LOG_GROUP_HGSMI | |
| #include <iprt/heap.h> | |
| #include <iprt/string.h> | |
| #include <VBox/HGSMI/HGSMI.h> | |
| #include <VBox/log.h> | |
| /* Channel flags. */ | |
| #define HGSMI_CH_F_REGISTERED 0x01 | |
| /* Assertions for situations which could happen and normally must be processed properly | |
| * but must be investigated during development: guest misbehaving, etc. | |
| */ | |
| #ifdef HGSMI_STRICT | |
| #define HGSMI_STRICT_ASSERT_FAILED() AssertFailed() | |
| #define HGSMI_STRICT_ASSERT(expr) Assert(expr) | |
| #else | |
| #define HGSMI_STRICT_ASSERT_FAILED() do {} while (0) | |
| #define HGSMI_STRICT_ASSERT(expr) do {} while (0) | |
| #endif /* !HGSMI_STRICT */ | |
| /* One-at-a-Time Hash from | |
| * http://www.burtleburtle.net/bob/hash/doobs.html | |
| * | |
| * ub4 one_at_a_time(char *key, ub4 len) | |
| * { | |
| * ub4 hash, i; | |
| * for (hash=0, i=0; i<len; ++i) | |
| * { | |
| * hash += key[i]; | |
| * hash += (hash << 10); | |
| * hash ^= (hash >> 6); | |
| * } | |
| * hash += (hash << 3); | |
| * hash ^= (hash >> 11); | |
| * hash += (hash << 15); | |
| * return hash; | |
| * } | |
| */ | |
| static uint32_t hgsmiHashBegin(void) | |
| { | |
| return 0; | |
| } | |
| static uint32_t hgsmiHashProcess(uint32_t hash, | |
| const void *pvData, | |
| size_t cbData) | |
| { | |
| const uint8_t *pu8Data = (const uint8_t *)pvData; | |
| while (cbData--) | |
| { | |
| hash += *pu8Data++; | |
| hash += (hash << 10); | |
| hash ^= (hash >> 6); | |
| } | |
| return hash; | |
| } | |
| static uint32_t hgsmiHashEnd(uint32_t hash) | |
| { | |
| hash += (hash << 3); | |
| hash ^= (hash >> 11); | |
| hash += (hash << 15); | |
| return hash; | |
| } | |
| uint32_t HGSMIChecksum(HGSMIOFFSET offBuffer, | |
| const HGSMIBUFFERHEADER *pHeader, | |
| const HGSMIBUFFERTAIL *pTail) | |
| { | |
| uint32_t u32Checksum = hgsmiHashBegin(); | |
| u32Checksum = hgsmiHashProcess(u32Checksum, &offBuffer, sizeof(offBuffer)); | |
| u32Checksum = hgsmiHashProcess(u32Checksum, pHeader, sizeof(HGSMIBUFFERHEADER)); | |
| u32Checksum = hgsmiHashProcess(u32Checksum, pTail, RT_OFFSETOF(HGSMIBUFFERTAIL, u32Checksum)); | |
| return hgsmiHashEnd(u32Checksum); | |
| } | |
| int HGSMIAreaInitialize(HGSMIAREA *pArea, | |
| void *pvBase, | |
| HGSMISIZE cbArea, | |
| HGSMIOFFSET offBase) | |
| { | |
| uint8_t *pu8Base = (uint8_t *)pvBase; | |
| if ( !pArea /* Check that the area: */ | |
| || cbArea < HGSMIBufferMinimumSize() /* large enough; */ | |
| || pu8Base + cbArea < pu8Base /* no address space wrap; */ | |
| || offBase > UINT32_C(0xFFFFFFFF) - cbArea /* area within the 32 bit space: offBase + cbMem <= 0xFFFFFFFF. */ | |
| ) | |
| { | |
| return VERR_INVALID_PARAMETER; | |
| } | |
| pArea->pu8Base = pu8Base; | |
| pArea->offBase = offBase; | |
| pArea->offLast = cbArea - HGSMIBufferMinimumSize() + offBase; | |
| pArea->cbArea = cbArea; | |
| return VINF_SUCCESS; | |
| } | |
| void HGSMIAreaClear(HGSMIAREA *pArea) | |
| { | |
| if (pArea) | |
| { | |
| RT_ZERO(*pArea); | |
| } | |
| } | |
| /* Initialize the memory buffer including its checksum. | |
| * No changes alloed to the header and the tail after that. | |
| */ | |
| HGSMIOFFSET HGSMIBufferInitializeSingle(const HGSMIAREA *pArea, | |
| HGSMIBUFFERHEADER *pHeader, | |
| HGSMISIZE cbBuffer, | |
| uint8_t u8Channel, | |
| uint16_t u16ChannelInfo) | |
| { | |
| if ( !pArea | |
| || !pHeader | |
| || cbBuffer < HGSMIBufferMinimumSize()) | |
| { | |
| return HGSMIOFFSET_VOID; | |
| } | |
| /* Buffer must be within the area: | |
| * * header data size do not exceed the maximum data size; | |
| * * buffer address is greater than the area base address; | |
| * * buffer address is lower than the maximum allowed for the given data size. | |
| */ | |
| HGSMISIZE cbMaximumDataSize = pArea->offLast - pArea->offBase; | |
| uint32_t u32DataSize = cbBuffer - HGSMIBufferMinimumSize(); | |
| if ( u32DataSize > cbMaximumDataSize | |
| || (uint8_t *)pHeader < pArea->pu8Base | |
| || (uint8_t *)pHeader > pArea->pu8Base + cbMaximumDataSize - u32DataSize) | |
| { | |
| return HGSMIOFFSET_VOID; | |
| } | |
| HGSMIOFFSET offBuffer = HGSMIPointerToOffset(pArea, pHeader); | |
| pHeader->u8Flags = HGSMI_BUFFER_HEADER_F_SEQ_SINGLE; | |
| pHeader->u32DataSize = u32DataSize; | |
| pHeader->u8Channel = u8Channel; | |
| pHeader->u16ChannelInfo = u16ChannelInfo; | |
| RT_ZERO(pHeader->u.au8Union); | |
| HGSMIBUFFERTAIL *pTail = HGSMIBufferTailFromPtr(pHeader, u32DataSize); | |
| pTail->u32Reserved = 0; | |
| pTail->u32Checksum = HGSMIChecksum(offBuffer, pHeader, pTail); | |
| return offBuffer; | |
| } | |
| int HGSMIHeapSetup(HGSMIHEAP *pHeap, | |
| void *pvBase, | |
| HGSMISIZE cbArea, | |
| HGSMIOFFSET offBase, | |
| const HGSMIENV *pEnv) | |
| { | |
| AssertPtrReturn(pHeap, VERR_INVALID_PARAMETER); | |
| AssertPtrReturn(pvBase, VERR_INVALID_PARAMETER); | |
| int rc = HGSMIAreaInitialize(&pHeap->area, pvBase, cbArea, offBase); | |
| if (RT_SUCCESS(rc)) | |
| { | |
| rc = HGSMIMAInit(&pHeap->ma, &pHeap->area, NULL, 0, 0, pEnv); | |
| if (RT_FAILURE(rc)) | |
| { | |
| HGSMIAreaClear(&pHeap->area); | |
| } | |
| } | |
| return rc; | |
| } | |
| void HGSMIHeapDestroy(HGSMIHEAP *pHeap) | |
| { | |
| if (pHeap) | |
| { | |
| HGSMIMAUninit(&pHeap->ma); | |
| RT_ZERO(*pHeap); | |
| } | |
| } | |
| void *HGSMIHeapAlloc(HGSMIHEAP *pHeap, | |
| HGSMISIZE cbData, | |
| uint8_t u8Channel, | |
| uint16_t u16ChannelInfo) | |
| { | |
| HGSMISIZE cbAlloc = HGSMIBufferRequiredSize(cbData); | |
| HGSMIBUFFERHEADER *pHeader = (HGSMIBUFFERHEADER *)HGSMIHeapBufferAlloc(pHeap, cbAlloc); | |
| if (pHeader) | |
| { | |
| HGSMIOFFSET offBuffer = HGSMIBufferInitializeSingle(HGSMIHeapArea(pHeap), pHeader, | |
| cbAlloc, u8Channel, u16ChannelInfo); | |
| if (offBuffer == HGSMIOFFSET_VOID) | |
| { | |
| HGSMIHeapBufferFree(pHeap, pHeader); | |
| pHeader = NULL; | |
| } | |
| } | |
| return pHeader? HGSMIBufferDataFromPtr(pHeader): NULL; | |
| } | |
| void HGSMIHeapFree(HGSMIHEAP *pHeap, | |
| void *pvData) | |
| { | |
| if (pvData) | |
| { | |
| HGSMIBUFFERHEADER *pHeader = HGSMIBufferHeaderFromData(pvData); | |
| HGSMIHeapBufferFree(pHeap, pHeader); | |
| } | |
| } | |
| void *HGSMIHeapBufferAlloc(HGSMIHEAP *pHeap, | |
| HGSMISIZE cbBuffer) | |
| { | |
| void *pvBuf = HGSMIMAAlloc(&pHeap->ma, cbBuffer); | |
| return pvBuf; | |
| } | |
| void HGSMIHeapBufferFree(HGSMIHEAP *pHeap, | |
| void *pvBuf) | |
| { | |
| HGSMIMAFree(&pHeap->ma, pvBuf); | |
| } | |
| typedef struct HGSMIBUFFERCONTEXT | |
| { | |
| const HGSMIBUFFERHEADER *pHeader; /* The original buffer header. */ | |
| void *pvData; /* Payload data in the buffer./ */ | |
| uint32_t cbData; /* Size of data */ | |
| } HGSMIBUFFERCONTEXT; | |
| /** Verify that the given offBuffer points to a valid buffer, which is within the area. | |
| * | |
| * @returns VBox status and the buffer information in pBufferContext. | |
| * @param pArea Area which supposed to contain the buffer. | |
| * @param offBuffer The buffer location in the area. | |
| * @param pBufferContext Where to write information about the buffer. | |
| */ | |
| static int hgsmiVerifyBuffer(const HGSMIAREA *pArea, | |
| HGSMIOFFSET offBuffer, | |
| HGSMIBUFFERCONTEXT *pBufferContext) | |
| { | |
| LogFlowFunc(("buffer 0x%x, area %p %x [0x%x;0x%x]\n", | |
| offBuffer, pArea->pu8Base, pArea->cbArea, pArea->offBase, pArea->offLast)); | |
| int rc = VINF_SUCCESS; | |
| if ( offBuffer < pArea->offBase | |
| || offBuffer > pArea->offLast) | |
| { | |
| LogFunc(("offset 0x%x is outside the area [0x%x;0x%x]!!!\n", | |
| offBuffer, pArea->offBase, pArea->offLast)); | |
| rc = VERR_INVALID_PARAMETER; | |
| HGSMI_STRICT_ASSERT_FAILED(); | |
| } | |
| else | |
| { | |
| void *pvBuffer = HGSMIOffsetToPointer(pArea, offBuffer); | |
| HGSMIBUFFERHEADER header = *HGSMIBufferHeaderFromPtr(pvBuffer); | |
| /* Quick check of the data size, it should be less than the maximum | |
| * data size for the buffer at this offset. | |
| */ | |
| LogFlowFunc(("datasize check: header.u32DataSize = 0x%x pArea->offLast - offBuffer = 0x%x\n", | |
| header.u32DataSize, pArea->offLast - offBuffer)); | |
| if (header.u32DataSize <= pArea->offLast - offBuffer) | |
| { | |
| HGSMIBUFFERTAIL tail = *HGSMIBufferTailFromPtr(pvBuffer, header.u32DataSize); | |
| /* At least both header and tail structures are in the area. Check the checksum. */ | |
| uint32_t u32Checksum = HGSMIChecksum(offBuffer, &header, &tail); | |
| LogFlowFunc(("checksum check: u32Checksum = 0x%x pTail->u32Checksum = 0x%x\n", | |
| u32Checksum, tail.u32Checksum)); | |
| if (u32Checksum == tail.u32Checksum) | |
| { | |
| /* Success. */ | |
| pBufferContext->pHeader = HGSMIBufferHeaderFromPtr(pvBuffer); | |
| pBufferContext->pvData = HGSMIBufferDataFromPtr(pvBuffer); | |
| pBufferContext->cbData = header.u32DataSize; | |
| } | |
| else | |
| { | |
| LogFunc(("invalid checksum 0x%x, expected 0x%x!!!\n", | |
| u32Checksum, tail.u32Checksum)); | |
| rc = VERR_INVALID_STATE; | |
| HGSMI_STRICT_ASSERT_FAILED(); | |
| } | |
| } | |
| else | |
| { | |
| LogFunc(("invalid data size 0x%x, maximum is 0x%x!!!\n", | |
| header.u32DataSize, pArea->offLast - offBuffer)); | |
| rc = VERR_TOO_MUCH_DATA; | |
| HGSMI_STRICT_ASSERT_FAILED(); | |
| } | |
| } | |
| return rc; | |
| } | |
| /** Helper to convert HGSMI channel index to the channel structure pointer. | |
| * | |
| * @returns Pointer to the channel data. | |
| * @param pChannelInfo The channel pool. | |
| * @param u8Channel The channel index. | |
| */ | |
| HGSMICHANNEL *HGSMIChannelFindById(HGSMICHANNELINFO *pChannelInfo, | |
| uint8_t u8Channel) | |
| { | |
| AssertCompile(RT_ELEMENTS(pChannelInfo->Channels) >= 0x100); | |
| HGSMICHANNEL *pChannel = &pChannelInfo->Channels[u8Channel]; | |
| if (pChannel->u8Flags & HGSMI_CH_F_REGISTERED) | |
| { | |
| return pChannel; | |
| } | |
| return NULL; | |
| } | |
| /** Process a guest buffer. | |
| * | |
| * @returns VBox status code. | |
| * @param pArea Area which supposed to contain the buffer. | |
| * @param pChannelInfo The channel pool. | |
| * @param offBuffer The buffer location in the area. | |
| */ | |
| int HGSMIBufferProcess(const HGSMIAREA *pArea, | |
| HGSMICHANNELINFO *pChannelInfo, | |
| HGSMIOFFSET offBuffer) | |
| { | |
| LogFlowFunc(("pArea %p, offBuffer 0x%x\n", pArea, offBuffer)); | |
| AssertPtrReturn(pArea, VERR_INVALID_PARAMETER); | |
| AssertPtrReturn(pChannelInfo, VERR_INVALID_PARAMETER); | |
| /* Guest has prepared a command description at 'offBuffer'. */ | |
| HGSMIBUFFERCONTEXT bufferContext; | |
| int rc = hgsmiVerifyBuffer(pArea, offBuffer, &bufferContext); | |
| if (RT_SUCCESS(rc)) | |
| { | |
| /* Pass the command to the appropriate handler registered with this instance. | |
| * Start with the handler list head, which is the preallocated HGSMI setup channel. | |
| */ | |
| const HGSMICHANNEL *pChannel = HGSMIChannelFindById(pChannelInfo, bufferContext.pHeader->u8Channel); | |
| if (pChannel) | |
| { | |
| const HGSMICHANNELHANDLER *pHandler = &pChannel->handler; | |
| if (pHandler->pfnHandler) | |
| { | |
| pHandler->pfnHandler(pHandler->pvHandler, bufferContext.pHeader->u16ChannelInfo, | |
| bufferContext.pvData, bufferContext.cbData); | |
| } | |
| HGSMI_STRICT_ASSERT(RT_SUCCESS(hgsmiVerifyBuffer(pArea, offBuffer, &bufferContext))); | |
| } | |
| else | |
| { | |
| rc = VERR_INVALID_FUNCTION; | |
| HGSMI_STRICT_ASSERT_FAILED(); | |
| } | |
| } | |
| return rc; | |
| } | |
| /** Register a new HGSMI channel by index. | |
| * | |
| * @returns VBox status code. | |
| * @param pChannelInfo The channel pool managed by the caller. | |
| * @param u8Channel Index of the channel. | |
| * @param pszName Name of the channel (optional, allocated by the caller). | |
| * @param pfnChannelHandler The channel callback. | |
| * @param pvChannelHandler The callback pointer. | |
| */ | |
| int HGSMIChannelRegister(HGSMICHANNELINFO *pChannelInfo, | |
| uint8_t u8Channel, | |
| const char *pszName, | |
| PFNHGSMICHANNELHANDLER pfnChannelHandler, | |
| void *pvChannelHandler) | |
| { | |
| /* Check whether the channel is already registered. */ | |
| HGSMICHANNEL *pChannel = HGSMIChannelFindById(pChannelInfo, u8Channel); | |
| if (pChannel) | |
| { | |
| HGSMI_STRICT_ASSERT_FAILED(); | |
| return VERR_ALREADY_EXISTS; | |
| } | |
| /* Channel is not yet registered. */ | |
| pChannel = &pChannelInfo->Channels[u8Channel]; | |
| pChannel->u8Flags = HGSMI_CH_F_REGISTERED; | |
| pChannel->u8Channel = u8Channel; | |
| pChannel->handler.pfnHandler = pfnChannelHandler; | |
| pChannel->handler.pvHandler = pvChannelHandler; | |
| pChannel->pszName = pszName; | |
| return VINF_SUCCESS; | |
| } |