/* $Id: VBVABase.cpp 100879 2015-06-09 14:26:20Z bird $ */ | |
/** @file | |
* VirtualBox Video driver, common code - VBVA initialisation and helper | |
* functions. | |
*/ | |
/* | |
* 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. | |
*/ | |
#include <VBox/VBoxVideoGuest.h> | |
#include <VBox/VBoxVideo.h> | |
#include <VBox/err.h> | |
#include <VBox/log.h> | |
#include <iprt/assert.h> | |
#include <iprt/string.h> | |
/* | |
* There is a hardware ring buffer in the graphics device video RAM, formerly | |
* in the VBox VMMDev PCI memory space. | |
* All graphics commands go there serialized by VBoxVBVABufferBeginUpdate. | |
* and vboxHwBufferEndUpdate. | |
* | |
* off32Free is writing position. off32Data is reading position. | |
* off32Free == off32Data means buffer is empty. | |
* There must be always gap between off32Data and off32Free when data | |
* are in the buffer. | |
* Guest only changes off32Free, host changes off32Data. | |
*/ | |
/* Forward declarations of internal functions. */ | |
static void vboxHwBufferFlush(PHGSMIGUESTCOMMANDCONTEXT pCtx); | |
static void vboxHwBufferPlaceDataAt(PVBVABUFFERCONTEXT pCtx, const void *p, | |
uint32_t cb, uint32_t offset); | |
static bool vboxHwBufferWrite(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, | |
const void *p, uint32_t cb); | |
static bool vboxVBVAInformHost(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, | |
int32_t cScreen, bool bEnable) | |
{ | |
bool bRc = false; | |
#if 0 /* All callers check this */ | |
if (ppdev->bHGSMISupported) | |
#endif | |
{ | |
void *p = VBoxHGSMIBufferAlloc(pHGSMICtx, | |
sizeof (VBVAENABLE_EX), | |
HGSMI_CH_VBVA, | |
VBVA_ENABLE); | |
if (!p) | |
{ | |
LogFunc(("HGSMIHeapAlloc failed\n")); | |
} | |
else | |
{ | |
VBVAENABLE_EX *pEnable = (VBVAENABLE_EX *)p; | |
pEnable->Base.u32Flags = bEnable? VBVA_F_ENABLE: VBVA_F_DISABLE; | |
pEnable->Base.u32Offset = pCtx->offVRAMBuffer; | |
pEnable->Base.i32Result = VERR_NOT_SUPPORTED; | |
if (cScreen >= 0) | |
{ | |
pEnable->Base.u32Flags |= VBVA_F_EXTENDED | VBVA_F_ABSOFFSET; | |
pEnable->u32ScreenId = cScreen; | |
} | |
VBoxHGSMIBufferSubmit(pHGSMICtx, p); | |
if (bEnable) | |
{ | |
bRc = RT_SUCCESS(pEnable->Base.i32Result); | |
} | |
else | |
{ | |
bRc = true; | |
} | |
VBoxHGSMIBufferFree(pHGSMICtx, p); | |
} | |
} | |
return bRc; | |
} | |
/* | |
* Public hardware buffer methods. | |
*/ | |
RTDECL(bool) VBoxVBVAEnable(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, | |
VBVABUFFER *pVBVA, int32_t cScreen) | |
{ | |
bool bRc = false; | |
LogFlowFunc(("pVBVA %p\n", pVBVA)); | |
#if 0 /* All callers check this */ | |
if (ppdev->bHGSMISupported) | |
#endif | |
{ | |
LogFunc(("pVBVA %p vbva off 0x%x\n", pVBVA, pCtx->offVRAMBuffer)); | |
pVBVA->hostFlags.u32HostEvents = 0; | |
pVBVA->hostFlags.u32SupportedOrders = 0; | |
pVBVA->off32Data = 0; | |
pVBVA->off32Free = 0; | |
memset(pVBVA->aRecords, 0, sizeof (pVBVA->aRecords)); | |
pVBVA->indexRecordFirst = 0; | |
pVBVA->indexRecordFree = 0; | |
pVBVA->cbPartialWriteThreshold = 256; | |
pVBVA->cbData = pCtx->cbBuffer - sizeof (VBVABUFFER) + sizeof (pVBVA->au8Data); | |
pCtx->fHwBufferOverflow = false; | |
pCtx->pRecord = NULL; | |
pCtx->pVBVA = pVBVA; | |
bRc = vboxVBVAInformHost(pCtx, pHGSMICtx, cScreen, true); | |
} | |
if (!bRc) | |
{ | |
VBoxVBVADisable(pCtx, pHGSMICtx, cScreen); | |
} | |
return bRc; | |
} | |
RTDECL(void) VBoxVBVADisable(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, | |
int32_t cScreen) | |
{ | |
LogFlowFunc(("\n")); | |
pCtx->fHwBufferOverflow = false; | |
pCtx->pRecord = NULL; | |
pCtx->pVBVA = NULL; | |
vboxVBVAInformHost(pCtx, pHGSMICtx, cScreen, false); | |
return; | |
} | |
RTDECL(bool) VBoxVBVABufferBeginUpdate(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx) | |
{ | |
bool bRc = false; | |
// LogFunc(("flags = 0x%08X\n", pCtx->pVBVA? pCtx->pVBVA->u32HostEvents: -1)); | |
if ( pCtx->pVBVA | |
&& (pCtx->pVBVA->hostFlags.u32HostEvents & VBVA_F_MODE_ENABLED)) | |
{ | |
uint32_t indexRecordNext; | |
Assert(!pCtx->fHwBufferOverflow); | |
Assert(pCtx->pRecord == NULL); | |
indexRecordNext = (pCtx->pVBVA->indexRecordFree + 1) % VBVA_MAX_RECORDS; | |
if (indexRecordNext == pCtx->pVBVA->indexRecordFirst) | |
{ | |
/* All slots in the records queue are used. */ | |
vboxHwBufferFlush (pHGSMICtx); | |
} | |
if (indexRecordNext == pCtx->pVBVA->indexRecordFirst) | |
{ | |
/* Even after flush there is no place. Fail the request. */ | |
LogFunc(("no space in the queue of records!!! first %d, last %d\n", | |
pCtx->pVBVA->indexRecordFirst, pCtx->pVBVA->indexRecordFree)); | |
} | |
else | |
{ | |
/* Initialize the record. */ | |
VBVARECORD *pRecord = &pCtx->pVBVA->aRecords[pCtx->pVBVA->indexRecordFree]; | |
pRecord->cbRecord = VBVA_F_RECORD_PARTIAL; | |
pCtx->pVBVA->indexRecordFree = indexRecordNext; | |
// LogFunc(("indexRecordNext = %d\n", indexRecordNext)); | |
/* Remember which record we are using. */ | |
pCtx->pRecord = pRecord; | |
bRc = true; | |
} | |
} | |
return bRc; | |
} | |
RTDECL(void) VBoxVBVABufferEndUpdate(PVBVABUFFERCONTEXT pCtx) | |
{ | |
VBVARECORD *pRecord; | |
// LogFunc(("\n")); | |
Assert(pCtx->pVBVA); | |
pRecord = pCtx->pRecord; | |
Assert(pRecord && (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL)); | |
/* Mark the record completed. */ | |
pRecord->cbRecord &= ~VBVA_F_RECORD_PARTIAL; | |
pCtx->fHwBufferOverflow = false; | |
pCtx->pRecord = NULL; | |
return; | |
} | |
/* | |
* Private operations. | |
*/ | |
static uint32_t vboxHwBufferAvail (const VBVABUFFER *pVBVA) | |
{ | |
int32_t i32Diff = pVBVA->off32Data - pVBVA->off32Free; | |
return i32Diff > 0? i32Diff: pVBVA->cbData + i32Diff; | |
} | |
static void vboxHwBufferFlush(PHGSMIGUESTCOMMANDCONTEXT pCtx) | |
{ | |
/* Issue the flush command. */ | |
void *p = VBoxHGSMIBufferAlloc(pCtx, | |
sizeof (VBVAFLUSH), | |
HGSMI_CH_VBVA, | |
VBVA_FLUSH); | |
if (!p) | |
{ | |
LogFunc(("HGSMIHeapAlloc failed\n")); | |
} | |
else | |
{ | |
VBVAFLUSH *pFlush = (VBVAFLUSH *)p; | |
pFlush->u32Reserved = 0; | |
VBoxHGSMIBufferSubmit(pCtx, p); | |
VBoxHGSMIBufferFree(pCtx, p); | |
} | |
return; | |
} | |
static void vboxHwBufferPlaceDataAt(PVBVABUFFERCONTEXT pCtx, const void *p, | |
uint32_t cb, uint32_t offset) | |
{ | |
VBVABUFFER *pVBVA = pCtx->pVBVA; | |
uint32_t u32BytesTillBoundary = pVBVA->cbData - offset; | |
uint8_t *dst = &pVBVA->au8Data[offset]; | |
int32_t i32Diff = cb - u32BytesTillBoundary; | |
if (i32Diff <= 0) | |
{ | |
/* Chunk will not cross buffer boundary. */ | |
memcpy (dst, p, cb); | |
} | |
else | |
{ | |
/* Chunk crosses buffer boundary. */ | |
memcpy (dst, p, u32BytesTillBoundary); | |
memcpy (&pVBVA->au8Data[0], (uint8_t *)p + u32BytesTillBoundary, i32Diff); | |
} | |
return; | |
} | |
static bool vboxHwBufferWrite(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, | |
const void *p, uint32_t cb) | |
{ | |
VBVARECORD *pRecord; | |
uint32_t cbHwBufferAvail; | |
uint32_t cbWritten = 0; | |
VBVABUFFER *pVBVA = pCtx->pVBVA; | |
Assert(pVBVA); | |
if (!pVBVA || pCtx->fHwBufferOverflow) | |
{ | |
return false; | |
} | |
Assert(pVBVA->indexRecordFirst != pVBVA->indexRecordFree); | |
pRecord = pCtx->pRecord; | |
Assert(pRecord && (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL)); | |
// LogFunc(("%d\n", cb)); | |
cbHwBufferAvail = vboxHwBufferAvail (pVBVA); | |
while (cb > 0) | |
{ | |
uint32_t cbChunk = cb; | |
// LogFunc(("pVBVA->off32Free %d, pRecord->cbRecord 0x%08X, cbHwBufferAvail %d, cb %d, cbWritten %d\n", | |
// pVBVA->off32Free, pRecord->cbRecord, cbHwBufferAvail, cb, cbWritten)); | |
if (cbChunk >= cbHwBufferAvail) | |
{ | |
LogFunc(("1) avail %d, chunk %d\n", cbHwBufferAvail, cbChunk)); | |
vboxHwBufferFlush (pHGSMICtx); | |
cbHwBufferAvail = vboxHwBufferAvail (pVBVA); | |
if (cbChunk >= cbHwBufferAvail) | |
{ | |
LogFunc(("no place for %d bytes. Only %d bytes available after flush. Going to partial writes.\n", | |
cb, cbHwBufferAvail)); | |
if (cbHwBufferAvail <= pVBVA->cbPartialWriteThreshold) | |
{ | |
LogFunc(("Buffer overflow!!!\n")); | |
pCtx->fHwBufferOverflow = true; | |
Assert(false); | |
return false; | |
} | |
cbChunk = cbHwBufferAvail - pVBVA->cbPartialWriteThreshold; | |
} | |
} | |
Assert(cbChunk <= cb); | |
Assert(cbChunk <= vboxHwBufferAvail (pVBVA)); | |
vboxHwBufferPlaceDataAt (pCtx, (uint8_t *)p + cbWritten, cbChunk, pVBVA->off32Free); | |
pVBVA->off32Free = (pVBVA->off32Free + cbChunk) % pVBVA->cbData; | |
pRecord->cbRecord += cbChunk; | |
cbHwBufferAvail -= cbChunk; | |
cb -= cbChunk; | |
cbWritten += cbChunk; | |
} | |
return true; | |
} | |
/* | |
* Public writer to the hardware buffer. | |
*/ | |
RTDECL(bool) VBoxVBVAWrite(PVBVABUFFERCONTEXT pCtx, | |
PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, | |
const void *pv, uint32_t cb) | |
{ | |
return vboxHwBufferWrite (pCtx, pHGSMICtx, pv, cb); | |
} | |
RTDECL(bool) VBoxVBVAOrderSupported(PVBVABUFFERCONTEXT pCtx, unsigned code) | |
{ | |
VBVABUFFER *pVBVA = pCtx->pVBVA; | |
if (!pVBVA) | |
{ | |
return false; | |
} | |
if (pVBVA->hostFlags.u32SupportedOrders & (1 << code)) | |
{ | |
return true; | |
} | |
return false; | |
} | |
RTDECL(void) VBoxVBVASetupBufferContext(PVBVABUFFERCONTEXT pCtx, | |
uint32_t offVRAMBuffer, | |
uint32_t cbBuffer) | |
{ | |
pCtx->offVRAMBuffer = offVRAMBuffer; | |
pCtx->cbBuffer = cbBuffer; | |
} |