DisplayImpl.cpp revision dc7e0f093e66df3ea06a27af17713dea87186b9c
/** @file
*
* VBox frontends: Basic Frontend (BFE):
* Implementation of VMDisplay class
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#define LOG_GROUP LOG_GROUP_MAIN
#ifdef VBOXBFE_WITHOUT_COM
# include "COMDefs.h"
#else
#endif
#include <iprt/semaphore.h>
#include <VBox/VBoxGuest.h>
#ifdef RT_OS_L4
#include <stdio.h>
#endif
#include "DisplayImpl.h"
#include "Framebuffer.h"
#include "VMMDevInterface.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* VMDisplay driver instance data.
*/
typedef struct DRVMAINDISPLAY
{
/** Pointer to the display object. */
/** Pointer to the driver instance structure. */
/** Our display connector interface. */
/** Converts PDMIDISPLAYCONNECTOR pointer to a DRVMAINDISPLAY pointer. */
#define PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface) ( (PDRVMAINDISPLAY) ((uintptr_t)pInterface - RT_OFFSETOF(DRVMAINDISPLAY, Connector)) )
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
{
mpVbvaMemory = NULL;
mfVideoAccelEnabled = false;
mfPendingVideoAccelEnable = false;
mfMachineRunning = false;
mcbVbvaPartial = 0;
// reset the event sems
// by default, we have an internal Framebuffer which is
// NULL, i.e. a black hole for no display output
mFramebuffer = 0;
mInternalFramebuffer = true;
mFramebufferOpened = false;
}
{
mFramebuffer = 0;
}
// public methods only for internal purposes
/////////////////////////////////////////////////////////////////////////////
/**
* Handle display resize event.
*
* @returns COM status code
* @param w New display width
* @param h New display height
*/
int VMDisplay::handleDisplayResize (int w, int h)
{
LogFlow(("VMDisplay::handleDisplayResize(): w=%d, h=%d\n", w, h));
// if there is no Framebuffer, this call is not interesting
if (mFramebuffer == NULL)
return VINF_SUCCESS;
/* Atomically set the resize status before calling the framebuffer. The new InProgress status will
* disable access to the VGA device by the EMT thread.
*/
AssertRelease(f);NOREF(f);
// callback into the Framebuffer to notify it
mFramebuffer->Lock();
if (!finished)
{
LogFlow(("VMDisplay::handleDisplayResize: external framebuffer wants us to wait!\n"));
/* Note: The previously obtained framebuffer lock must be preserved.
* The EMT keeps the framebuffer lock until the resize process completes.
*/
return VINF_VGA_RESIZE_IN_PROGRESS;
}
/* Set the status so the 'handleResizeCompleted' would work. */
f = ASMAtomicCmpXchgU32 (&mu32ResizeStatus, ResizeStatus_UpdateDisplayData, ResizeStatus_InProgress);
AssertRelease(f);NOREF(f);
/* The method also unlocks the framebuffer. */
return VINF_SUCCESS;
}
/**
* Framebuffer has been resized.
* Read the new display data and unlock the framebuffer.
*
* @thread EMT
*/
void VMDisplay::handleResizeCompletedEMT (void)
{
LogFlowFunc(("\n"));
if (mFramebuffer)
{
/* Framebuffer has completed the resize. Update the connector data. */
/* Unlock framebuffer. */
mFramebuffer->Unlock();
}
/* Go into non resizing state. */
bool f = ASMAtomicCmpXchgU32 (&mu32ResizeStatus, ResizeStatus_Void, ResizeStatus_UpdateDisplayData);
AssertRelease(f);NOREF(f);
}
/**
* Notification that the framebuffer has completed the
* asynchronous resize processing
*
* @returns COM status code
*/
{
LogFlow(("VMDisplay::ResizeCompleted\n"));
// this is only valid for external framebuffers
if (mInternalFramebuffer)
return E_FAIL;
/* Set the flag indicating that the resize has completed and display data need to be updated. */
bool f = ASMAtomicCmpXchgU32 (&mu32ResizeStatus, ResizeStatus_UpdateDisplayData, ResizeStatus_InProgress);
AssertRelease(f);NOREF(f);
return S_OK;
}
{
/* Correct negative x and y coordinates. */
if (*px < 0)
{
*px = 0;
}
if (*py < 0)
{
*py = 0;
}
/* Also check if coords are greater than the display resolution. */
}
/**
* Handle display update
*
* @returns COM status code
* @param w New display width
* @param h New display height
*/
void VMDisplay::handleDisplayUpdate (int x, int y, int w, int h)
{
// if there is no Framebuffer, this call is not interesting
if (mFramebuffer == NULL)
return;
mFramebuffer->Lock();
if (w == 0 || h == 0)
{
mFramebuffer->Unlock();
return;
}
// special processing for the internal Framebuffer
if (mInternalFramebuffer)
{
mFramebuffer->Unlock();
}
else
{
// callback into the Framebuffer to notify it
mFramebuffer->Unlock();
if (!finished)
{
// the Framebuffer needs more time to process
// the event so we have to halt the VM until it's done
}
}
}
// IDisplay properties
/////////////////////////////////////////////////////////////////////////////
/**
* Returns the current display width in pixel
*
* @returns COM status code
* @param width Address of result variable.
*/
{
}
/**
* Returns the current display height in pixel
*
* @returns COM status code
* @param height Address of result variable.
*/
{
}
/**
* Returns the current display color depth in bits
*
* @returns COM status code
* @param bitsPerPixel Address of result variable.
*/
{
}
void VMDisplay::updatePointerShape(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot, uint32_t width, uint32_t height, void *pShape)
{
}
// IDisplay methods
/////////////////////////////////////////////////////////////////////////////
/**
* Registers an external Framebuffer
*
* @returns COM status code
* @param Framebuffer external Framebuffer object
*/
{
if (!Framebuffer)
return E_POINTER;
// free current Framebuffer (if there is any)
mFramebuffer = 0;
mInternalFramebuffer = false;
return S_OK;
}
/* InvalidateAndUpdate schedules a request that eventually calls */
/* mpDrv->pUpPort->pfnUpdateDisplayAll which in turns accesses the */
/* framebuffer. In order to synchronize with other framebuffer */
void
{
}
/**
* Does a full invalidation of the VM display and instructs the VM
* to update it immediately.
*
* @returns COM status code
*/
{
LogFlow (("VMDisplay::InvalidateAndUpdate(): BEGIN\n"));
LogFlow (("VMDisplay::InvalidateAndUpdate(): sending DPYUPDATE request\n"));
/* pdm.h says that this has to be called from the EMT thread */
if (VBOX_SUCCESS(rcVBox))
if (VBOX_FAILURE(rcVBox))
return rc;
}
// private methods
/////////////////////////////////////////////////////////////////////////////
/**
* Helper to update the display information from the Framebuffer
*
*/
void VMDisplay::updateDisplayData()
{
while(!mFramebuffer)
{
#if RT_OS_L4
asm volatile ("nop":::"memory");
l4_sleep(5);
#else
#endif
}
// the driver might not have been constructed yet
if (mpDrv)
{
}
}
void VMDisplay::resetFramebuffer()
{
if (!mFramebuffer)
return;
// the driver might not have been constructed yet
if (mpDrv)
{
}
}
/**
* Handle display resize event
*
* @param pInterface VMDisplay connector.
* @param cx New width in pixels.
* @param cy New height in pixels.
*/
DECLCALLBACK(int) VMDisplay::displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface, uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy)
{
// forward call to instance handler
}
/**
* Handle display update
*
* @param pInterface VMDisplay connector.
* @param x Left upper boundary x.
* @param y Left upper boundary y.
* @param cx Update rect width.
* @param cy Update rect height.
*/
{
// forward call to instance handler
}
/**
* Periodic display refresh callback.
*
* @param pInterface VMDisplay connector.
*/
{
/* Contrary to displayUpdateCallback and displayResizeCallback
* the framebuffer lock must be taken since the the function
* pointed to by pDrv->pUpPort->pfnUpdateDisplay is anaware
* of any locking issues. */
{
#ifdef DEBUG_sunlover
LogFlowFunc (("ResizeStatus_UpdateDisplayData\n"));
#endif /* DEBUG_sunlover */
/* The framebuffer was resized and display data need to be updated. */
/* Continue with normal processing because the status here is ResizeStatus_Void. */
/* Repaint the display because VM continued to run during the framebuffer resize. */
/* Ignore the refresh to replay the logic. */
return;
}
else if (u32ResizeStatus == ResizeStatus_InProgress)
{
#ifdef DEBUG_sunlover
LogFlowFunc (("ResizeStatus_InProcess\n"));
#endif /* DEBUG_sunlover */
/* The framebuffer is being resized. Do not call the VGA device back. Immediately return. */
return;
}
{
/* Acceleration was enabled while machine was not yet running
* due to restoring from saved state. Update entire display and
* actually enable acceleration.
*/
/* Acceleration can not be yet enabled.*/
if (pDisplay->mfMachineRunning)
{
/* Reset the pending state. */
pDisplay->mfPendingVideoAccelEnable = false;
}
}
else
{
if (pDisplay->mfVideoAccelEnabled)
{
pDisplay->VideoAccelFlush ();
}
else
{
}
}
}
/**
* Reset notification
*
* @param pInterface Display connector.
*/
{
LogFlow(("Display::displayResetCallback\n"));
/* Disable VBVA mode. */
}
/**
* LFBModeChange notification
*
* @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange
*/
DECLCALLBACK(void) VMDisplay::displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled)
{
/**
* @todo: If we got the callback then VM if definitely running.
* But a better method should be implemented.
*/
/* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */
}
DECLCALLBACK(void) VMDisplay::displayProcessAdapterDataCallback(PPDMIDISPLAYCONNECTOR pInterface, void *pvVRAM, uint32_t u32VRAMSize)
{
}
DECLCALLBACK(void) VMDisplay::displayProcessDisplayDataCallback(PPDMIDISPLAYCONNECTOR pInterface, void *pvVRAM, unsigned uScreenId)
{
}
typedef struct _VBVADIRTYREGION
{
/* Copies of object's pointers used by vbvaRgn functions. */
/* Merged rectangles. */
{
return;
}
{
LogFlow(("vbvaRgnDirtyRect: x = %d, y = %d, w = %d, h = %d\n", phdr->x, phdr->y, phdr->w, phdr->h));
/*
* Here update rectangles are accumulated to form an update area.
* @todo
* Now the simplies method is used which builds one rectangle that
* includes all update areas. A bit more advanced method can be
* employed here. The method should be fast however.
*/
{
/* Empty rectangle. */
return;
}
{
/* This is the first rectangle to be added. */
}
else
{
/* Adjust region coordinates. */
}
}
{
if (prgn->pFramebuffer && w != 0 && h != 0)
{
}
}
static void vbvaSetMemoryFlags (VBVAMEMORY *pVbvaMemory, bool fVideoAccelEnabled, bool fVideoAccelVRDP)
{
if (pVbvaMemory)
{
/* This called only on changes in mode. So reset VRDP always. */
if (fVideoAccelEnabled)
{
if (fVideoAccelVRDP)
}
}
}
bool VMDisplay::VideoAccelAllowed (void)
{
return true;
}
/**
* @thread EMT
*/
{
int rc = VINF_SUCCESS;
/* Called each time the guest wants to use acceleration,
* or when the VGA device disables acceleration,
* or when restoring the saved state with accel enabled.
*
* VGA device disables acceleration on each video mode change
* and on reset.
*
* Guest enabled acceleration at will. And it needs to enable
* acceleration after a mode change.
*/
LogFlow(("Display::VideoAccelEnable: mfVideoAccelEnabled = %d, fEnable = %d, pVbvaMemory = %p\n",
/* Strictly check parameters. Callers must not pass anything in the case. */
if (!VideoAccelAllowed ())
return VERR_NOT_SUPPORTED;
/*
* Verify that the VM is in running state. If it is not,
* then this must be postponed until it goes to running.
*/
if (!mfMachineRunning)
{
LogFlow(("Display::VideoAccelEnable: Machine is not yet running.\n"));
if (fEnable)
{
}
return rc;
}
/* Check that current status is not being changed */
if (mfVideoAccelEnabled == fEnable)
return rc;
if (mfVideoAccelEnabled)
{
/* Process any pending orders and empty the VBVA ring buffer. */
VideoAccelFlush ();
}
if (!fEnable && mpVbvaMemory)
/* Safety precaution. There is no more VBVA until everything is setup! */
mpVbvaMemory = NULL;
mfVideoAccelEnabled = false;
/* Update entire display. */
/* Everything OK. VBVA status can be changed. */
/* Notify the VMMDev, which saves VBVA status in the saved state,
* and needs to know current status.
*/
if (pVMMDevPort)
if (fEnable)
{
mfVideoAccelEnabled = true;
/* Initialize the hardware memory. */
mpVbvaMemory->off32Data = 0;
mpVbvaMemory->off32Free = 0;
mpVbvaMemory->indexRecordFirst = 0;
mpVbvaMemory->indexRecordFree = 0;
LogRel(("VBVA: Enabled.\n"));
}
else
{
LogRel(("VBVA: Disabled.\n"));
}
return rc;
}
{
return true;
}
{
if (cbDst >= VBVA_RING_BUFFER_SIZE)
{
AssertFailed ();
return;
}
if (i32Diff <= 0)
{
/* Chunk will not cross buffer boundary. */
}
else
{
/* Chunk crosses buffer boundary. */
}
/* Advance data offset. */
return;
}
{
if (pVMMDevPort)
}
static bool vbvaPartialRead (uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, VBVAMEMORY *pVbvaMemory)
{
LogFlow(("MAIN::DisplayImpl::vbvaPartialRead: p = %p, cb = %d, cbRecord 0x%08X\n",
if (*ppu8)
{
}
else
{
}
if (!pu8New)
{
/* Memory allocation failed, fail the function. */
Log(("MAIN::vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n",
cbRecord));
if (*ppu8)
*pcb = 0;
return false;
}
/* Fetch data from the ring buffer. */
return true;
}
/* For contiguous chunks just return the address in the buffer.
* For crossing boundary - allocate a buffer from heap.
*/
{
#ifdef DEBUG_sunlover
LogFlow(("MAIN::DisplayImpl::vbvaFetchCmd:first = %d, free = %d\n",
#endif /* DEBUG_sunlover */
if (!vbvaVerifyRingBuffer (mpVbvaMemory))
{
return false;
}
if (indexRecordFirst == indexRecordFree)
{
/* No records to process. Return without assigning output variables. */
return true;
}
#ifdef DEBUG_sunlover
LogFlow(("MAIN::DisplayImpl::vbvaFetchCmd: cbRecord = 0x%08X\n",
#endif /* DEBUG_sunlover */
if (mcbVbvaPartial)
{
/* There is a partial read in process. Continue with it. */
LogFlow(("MAIN::DisplayImpl::vbvaFetchCmd: continue partial record mcbVbvaPartial = %d cbRecord 0x%08X, first = %d, free = %d\n",
if (cbRecord > mcbVbvaPartial)
{
/* New data has been added to the record. */
return false;
}
{
/* The record is completed by guest. Return it to the caller. */
*pcbCmd = mcbVbvaPartial;
mcbVbvaPartial = 0;
/* Advance the record index. */
#ifdef DEBUG_sunlover
LogFlow(("MAIN::DisplayImpl::vbvaFetchBytes: partial done ok, data = %d, free = %d\n",
#endif /* DEBUG_sunlover */
}
return true;
}
/* A new record need to be processed. */
{
/* Current record is being written by guest. '=' is important here. */
{
/* Partial read must be started. */
return false;
LogFlow(("MAIN::DisplayImpl::vbvaFetchCmd: started partial record mcbVbvaPartial = 0x%08X cbRecord 0x%08X, first = %d, free = %d\n",
}
return true;
}
/* Current record is complete. */
/* The size of largest contiguos chunk in the ring biffer. */
/* The ring buffer pointer. */
/* The pointer to data in the ring buffer. */
/* Fetch or point the data. */
if (u32BytesTillBoundary >= cbRecord)
{
/* The command does not cross buffer boundary. Return address in the buffer. */
/* Advance data offset. */
}
else
{
/* The command crosses buffer boundary. Rare case, so not optimized. */
if (!dst)
{
return false;
}
#ifdef DEBUG_sunlover
#endif /* DEBUG_sunlover */
}
/* Advance the record index. */
#ifdef DEBUG_sunlover
LogFlow(("MAIN::DisplayImpl::vbvaFetchBytes: done ok, data = %d, free = %d\n",
#endif /* DEBUG_sunlover */
return true;
}
{
{
/* The pointer is inside ring buffer. Must be continuous chunk. */
/* Do nothing. */
}
else
{
/* The pointer is outside. It is then an allocated copy. */
#ifdef DEBUG_sunlover
#endif /* DEBUG_sunlover */
{
mcbVbvaPartial = 0;
}
else
{
}
}
return;
}
/**
* Called regularly on the DisplayRefresh timer.
* Also on behalf of guest, when the ring buffer is full.
*
* @thread EMT
*/
void VMDisplay::VideoAccelFlush (void)
{
#ifdef DEBUG_sunlover
#endif /* DEBUG_sunlover */
if (!mfVideoAccelEnabled)
{
Log(("Display::VideoAccelFlush: called with disabled VBVA!!! Ignoring.\n"));
return;
}
/* Here VBVA is enabled and we have the accelerator memory pointer. */
#ifdef DEBUG_sunlover
LogFlow(("Display::VideoAccelFlush: indexRecordFirst = %d, indexRecordFree = %d, off32Data = %d, off32Free = %d\n",
mpVbvaMemory->indexRecordFirst, mpVbvaMemory->indexRecordFree, mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
#endif /* DEBUG_sunlover */
/* Quick check for "nothing to update" case. */
return;
/* Process the ring buffer */
if (!fFramebufferIsNull)
mFramebuffer->Lock();
/* Initialize dirty rectangles accumulator. */
for (;;)
{
/* Fetch the command data. */
{
Log(("Display::VideoAccelFlush: unable to fetch command. off32Data = %d, off32Free = %d. Disabling VBVA!!!\n",
/* Disable VBVA on those processing errors. */
VideoAccelEnable (false, NULL);
break;
}
if (!cbCmd)
{
/* No more commands yet in the queue. */
break;
}
if (!fFramebufferIsNull)
{
#ifdef DEBUG_sunlover
LogFlow(("MAIN::DisplayImpl::VideoAccelFlush: hdr: cbCmd = %d, x=%d, y=%d, w=%d, h=%d\n", cbCmd, phdr->x, phdr->y, phdr->w, phdr->h));
#endif /* DEBUG_sunlover */
/* Handle the command.
*
* Guest is responsible for updating the guest video memory.
* The Windows guest does all drawing using Eng*.
*
* For local output, only dirty rectangle information is used
* to update changed areas.
*
* Dirty rectangles are accumulated to exclude overlapping updates and
* group small updates to a larger one.
*/
/* Accumulate the update. */
// /* Forward the command to VRDP server. */
// mParent->consoleVRDPServer()->SendUpdate (phdr, cbCmd);
}
}
if (!fFramebufferIsNull)
mFramebuffer->Unlock ();
/* Draw the framebuffer. */
}
/**
* Queries an interface to the driver.
*
* @returns Pointer to interface.
* @returns NULL if the interface was not supported by the driver.
* @param pInterface Pointer to this interface structure.
* @param enmInterface The requested interface identification.
*/
{
switch (enmInterface)
{
case PDMINTERFACE_BASE:
default:
return NULL;
}
}
/**
* Construct a display driver instance.
*
* @returns VBox status.
* @param pDrvIns The driver instance data.
* If the registration structure is needed, pDrvIns->pDrvReg points to it.
* @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
* of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
* iInstance it's expected to be used a bit in this function.
*/
{
/*
* Validate configuration.
*/
if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
{
AssertMsgFailed(("Configuration error: Not possible to attach anything to this driver!\n"));
return VERR_PDM_DRVINS_NO_ATTACH;
}
/*
* Init Interfaces.
*/
/*
*/
pData->pUpPort = (PPDMIDISPLAYPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_DISPLAY_PORT);
{
AssertMsgFailed(("Configuration error: No display port interface above!\n"));
return VERR_PDM_MISSING_INTERFACE_ABOVE;
}
/*
* Get the VMDisplay object pointer and update the mpDrv member.
*/
void *pv;
if (VBOX_FAILURE(rc))
{
return rc;
}
/*
* If there is a Framebuffer, we have to update our display information
*/
/*
* Start periodic screen refreshes
*/
return VINF_SUCCESS;
}
/**
* VMDisplay driver registration record.
*/
{
/* u32Version */
/* szDriverName */
"MainDisplay",
/* pszDescription */
"Main display driver (Main as in the API).",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(DRVMAINDISPLAY),
/* pfnConstruct */
/* pfnDestruct */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
NULL,
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnDetach */
};