VirtualBox

source: vbox/trunk/src/VBox/Main/DisplayImpl.cpp@ 394

Last change on this file since 394 was 279, checked in by vboxsync, 18 years ago

Reset the resize event in proper place.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.5 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006 InnoTek Systemberatung GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22#include "DisplayImpl.h"
23#include "FramebufferImpl.h"
24#include "ConsoleImpl.h"
25#include "ConsoleVRDPServer.h"
26#include "VMMDev.h"
27
28#include "Logging.h"
29
30#include <iprt/semaphore.h>
31#include <iprt/thread.h>
32#include <iprt/asm.h>
33
34#include <VBox/pdm.h>
35#include <VBox/cfgm.h>
36#include <VBox/err.h>
37#include <VBox/vm.h>
38
39/**
40 * Display driver instance data.
41 */
42typedef struct DRVMAINDISPLAY
43{
44 /** Pointer to the display object. */
45 Display *pDisplay;
46 /** Pointer to the driver instance structure. */
47 PPDMDRVINS pDrvIns;
48 /** Pointer to the keyboard port interface of the driver/device above us. */
49 PPDMIDISPLAYPORT pUpPort;
50 /** Our display connector interface. */
51 PDMIDISPLAYCONNECTOR Connector;
52} DRVMAINDISPLAY, *PDRVMAINDISPLAY;
53
54/** Converts PDMIDISPLAYCONNECTOR pointer to a DRVMAINDISPLAY pointer. */
55#define PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface) ( (PDRVMAINDISPLAY) ((uintptr_t)pInterface - RT_OFFSETOF(DRVMAINDISPLAY, Connector)) )
56
57#ifdef DEBUG_sunlover
58static STAMPROFILE StatDisplayRefresh;
59static int stam = 0;
60#endif /* DEBUG_sunlover */
61
62// constructor / destructor
63/////////////////////////////////////////////////////////////////////////////
64
65HRESULT Display::FinalConstruct()
66{
67 mpVbvaMemory = NULL;
68 mfVideoAccelEnabled = false;
69 mfVideoAccelVRDP = false;
70 mfu32SupportedOrders = 0;
71 mcVideoAccelVRDPRefs = 0;
72
73 mpPendingVbvaMemory = NULL;
74 mfPendingVideoAccelEnable = false;
75
76 mfMachineRunning = false;
77
78 mpu8VbvaPartial = NULL;
79 mcbVbvaPartial = 0;
80
81 mParent = NULL;
82 mpDrv = NULL;
83 mpVMMDev = NULL;
84 mfVMMDevInited = false;
85 RTSemEventMultiCreate(&mResizeSem);
86 RTSemEventMultiCreate(&mUpdateSem);
87
88 mLastAddress = NULL;
89 mLastLineSize = 0;
90 mLastColorDepth = 0,
91 mLastWidth = 0;
92 mLastHeight = 0;
93
94 return S_OK;
95}
96
97void Display::FinalRelease()
98{
99 if (isReady())
100 uninit();
101}
102
103// public initializer/uninitializer for internal purposes only
104/////////////////////////////////////////////////////////////////////////////
105
106/**
107 * Initializes the display object.
108 *
109 * @returns COM result indicator
110 * @param parent handle of our parent object
111 * @param qemuConsoleData address of common console data structure
112 */
113HRESULT Display::init (Console *parent)
114{
115 LogFlowFunc (("isReady=%d", isReady()));
116
117 ComAssertRet (parent, E_INVALIDARG);
118
119 AutoLock alock (this);
120 ComAssertRet (!isReady(), E_UNEXPECTED);
121
122 mParent = parent;
123
124 /* reset the event sems */
125 RTSemEventMultiReset(mResizeSem);
126 RTSemEventMultiReset(mUpdateSem);
127
128 // by default, we have an internal framebuffer which is
129 // NULL, i.e. a black hole for no display output
130 mFramebuffer = 0;
131 mInternalFramebuffer = true;
132 mFramebufferOpened = false;
133 mSupportedAccelOps = 0;
134
135 mParent->RegisterCallback(this);
136
137 setReady (true);
138 return S_OK;
139}
140
141/**
142 * Uninitializes the instance and sets the ready flag to FALSE.
143 * Called either from FinalRelease() or by the parent when it gets destroyed.
144 */
145void Display::uninit()
146{
147 LogFlowFunc (("isReady=%d\n", isReady()));
148
149 AutoLock alock (this);
150 AssertReturn (isReady(), (void) 0);
151
152 mFramebuffer.setNull();
153 RTSemEventMultiDestroy(mResizeSem);
154 RTSemEventMultiDestroy(mUpdateSem);
155
156 if (mParent)
157 {
158 mParent->UnregisterCallback(this);
159 }
160
161 if (mpDrv)
162 mpDrv->pDisplay = NULL;
163 mpDrv = NULL;
164 mpVMMDev = NULL;
165 mfVMMDevInited = true;
166
167 setReady (false);
168}
169
170// IConsoleCallback method
171STDMETHODIMP Display::OnStateChange(MachineState_T machineState)
172{
173 if (machineState == MachineState_Running)
174 {
175 LogFlowFunc (("Machine running\n"));
176
177 mfMachineRunning = true;
178 }
179 else
180 {
181 mfMachineRunning = false;
182 }
183 return S_OK;
184}
185
186// public methods only for internal purposes
187/////////////////////////////////////////////////////////////////////////////
188
189void Display::callFramebufferResize (FramebufferPixelFormat_T pixelFormat, void *pvVRAM, uint32_t cbLine, int w, int h)
190{
191 Assert (!mFramebuffer.isNull());
192
193 /* Call the framebuffer to try and set required pixelFormat. */
194 BOOL finished = TRUE;
195
196 /* Reset the event here. It could be signalled before it gets to after 'if (!finished)' */
197 RTSemEventMultiReset(mResizeSem);
198
199 mFramebuffer->RequestResize (pixelFormat, (ULONG)pvVRAM, cbLine, w, h, &finished);
200
201 if (!finished)
202 {
203 LogFlowFunc (("External framebuffer wants us to wait!\n"));
204
205 /* Note: The previously obtained framebuffer lock is preserved.
206 * The EMT keeps the lock until the resize process completes.
207 */
208
209 /* The framebuffer needs more time to process
210 * the event so we have to halt the VM until it's done.
211 */
212 RTSemEventMultiWait(mResizeSem, RT_INDEFINITE_WAIT); /** @todo r=bird: this is a serious deadlock point, where EMT is stuck while the main thread is doing VMR3Req for some reason. */
213 }
214}
215
216/**
217 * Handles display resize event.
218 *
219 * @param w New display width
220 * @param h New display height
221 *
222 * @thread EMT
223 */
224void Display::handleDisplayResize (uint32_t bpp, void *pvVRAM, uint32_t cbLine, int w, int h)
225{
226 LogRel (("Display::handleDisplayResize(): pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X\n",
227 pvVRAM, w, h, bpp, cbLine));
228
229 /* If there is no framebuffer, this call is not interesting. */
230 if (mFramebuffer.isNull())
231 {
232 return;
233 }
234
235 mLastAddress = pvVRAM;
236 mLastLineSize = cbLine;
237 mLastColorDepth = bpp,
238 mLastWidth = w;
239 mLastHeight = h;
240
241 FramebufferPixelFormat_T pixelFormat;
242
243 switch (bpp)
244 {
245 case 32: pixelFormat = FramebufferPixelFormat_PixelFormatRGB32; break;
246 case 24: pixelFormat = FramebufferPixelFormat_PixelFormatRGB24; break;
247 case 16: pixelFormat = FramebufferPixelFormat_PixelFormatRGB16; break;
248 default: pixelFormat = FramebufferPixelFormat_PixelFormatDefault; cbLine = 0;
249 }
250
251 mFramebuffer->Lock();
252
253 callFramebufferResize (pixelFormat, pvVRAM, cbLine, w, h);
254
255 /* Indicated whether the FB should be set to a default format in case something went wrong. */
256 bool fResetFormat = false;
257
258 /* Check the framebuffer pixel format. */
259 FramebufferPixelFormat_T newPixelFormat;
260
261 mFramebuffer->COMGETTER(PixelFormat) (&newPixelFormat);
262
263 if (newPixelFormat == FramebufferPixelFormat_PixelFormatDefault)
264 {
265 mpDrv->pUpPort->pfnSetupVRAM (mpDrv->pUpPort, NULL, 0);
266 }
267 else
268 {
269 Assert(pixelFormat == newPixelFormat);
270
271 /* Framebuffer successfully set the requested format. */
272 ULONG address;
273 mFramebuffer->COMGETTER(Address) (&address);
274
275 ULONG lineSize;
276 mFramebuffer->COMGETTER(LineSize) (&lineSize);
277 Assert(lineSize == cbLine);
278
279 /* Trust Framebuffer implementation that it allocated a buffer,
280 * which size is a multiple of PAGE_SIZE.
281 */
282 uint32_t cbBuffer = cbLine * h;
283
284 int rc = mpDrv->pUpPort->pfnSetupVRAM (mpDrv->pUpPort, (void *)address, cbBuffer);
285
286 if (VBOX_FAILURE(rc))
287 {
288 LogFlowFunc (("pfnSetupVRAM returns %Vrc\n", rc));
289 fResetFormat = true;
290 }
291
292#ifdef DEBUG_sunlover
293 if (!stam)
294 {
295 /* protect mpVM */
296 Console::SafeVMPtr pVM (mParent);
297 AssertComRC (pVM.rc());
298
299 STAM_REG(pVM, &StatDisplayRefresh, STAMTYPE_PROFILE, "/PROF/Display/Refresh", STAMUNIT_TICKS_PER_CALL, "Time spent in EMT for display updates.");
300 stam = 1;
301 }
302#endif /* DEBUG_sunlover */
303 }
304
305 if (fResetFormat)
306 {
307 callFramebufferResize (FramebufferPixelFormat_PixelFormatDefault, NULL, 0, w, h);
308 }
309
310 updateDisplayData();
311
312 LogFlowFunc (("Calling VRDP\n"));
313
314 mParent->consoleVRDPServer()->SendResize();
315
316 mFramebuffer->Unlock();
317
318 return;
319}
320
321static void checkCoordBounds (int *px, int *py, int *pw, int *ph, int cx, int cy)
322{
323 /* Correct negative x and y coordinates. */
324 if (*px < 0)
325 {
326 *px += *pw; /* Compute xRight which is also the new width. */
327
328 *pw = (*px < 0)? 0: *px;
329
330 *px = 0;
331 }
332
333 if (*py < 0)
334 {
335 *py += *ph; /* Compute xBottom, which is also the new height. */
336
337 *ph = (*py < 0)? 0: *py;
338
339 *py = 0;
340 }
341
342 /* Also check if coords are greater than the display resolution. */
343 if (*px + *pw > cx)
344 {
345 *pw = cx > *px? cx - *px: 0;
346 }
347
348 if (*py + *ph > cy)
349 {
350 *ph = cy > *py? cy - *py: 0;
351 }
352}
353
354/**
355 * Handles display update event.
356 *
357 * @param x Update area x coordinate
358 * @param y Update area y coordinate
359 * @param w Update area width
360 * @param h Update area height
361 *
362 * @thread EMT
363 */
364void Display::handleDisplayUpdate (int x, int y, int w, int h)
365{
366 // if there is no framebuffer, this call is not interesting
367 if (mFramebuffer.isNull())
368 return;
369
370 mFramebuffer->Lock();
371
372#ifdef DEBUG_sunlover
373 LogFlowFunc (("%d,%d %dx%d (%d,%d)\n",
374 x, y, w, h, mpDrv->Connector.cx, mpDrv->Connector.cy));
375#endif /* DEBUG_sunlover */
376
377 checkCoordBounds (&x, &y, &w, &h, mpDrv->Connector.cx, mpDrv->Connector.cy);
378
379#ifdef DEBUG_sunlover
380 LogFlowFunc (("%d,%d %dx%d (checked)\n", x, y, w, h));
381#endif /* DEBUG_sunlover */
382
383 /* special processing for the internal framebuffer */
384 if (mInternalFramebuffer)
385 {
386 mFramebuffer->Unlock();
387 } else
388 {
389 /* callback into the framebuffer to notify it */
390 BOOL finished = FALSE;
391
392 mFramebuffer->NotifyUpdate(x, y, w, h, &finished);
393
394 if (!finished)
395 {
396 /*
397 * the framebuffer needs more time to process
398 * the event so we have to halt the VM until it's done
399 */
400 RTSemEventMultiReset(mUpdateSem);
401 mFramebuffer->Unlock();
402 RTSemEventMultiWait(mUpdateSem, RT_INDEFINITE_WAIT);
403 } else
404 {
405 mFramebuffer->Unlock();
406 }
407
408 if (!mfVideoAccelEnabled)
409 {
410 /* When VBVA is enabled, the VRDP server is informed in the VideoAccelFlush.
411 * Inform the server here only if VBVA is disabled.
412 */
413 mParent->consoleVRDPServer()->SendUpdateBitmap(x, y, w, h);
414 }
415 }
416 return;
417}
418
419typedef struct _VBVADIRTYREGION
420{
421 /* Copies of object's pointers used by vbvaRgn functions. */
422 IFramebuffer *pFramebuffer;
423 Display *pDisplay;
424 PPDMIDISPLAYPORT pPort;
425
426 /* Merged rectangles. */
427 int32_t xLeft;
428 int32_t xRight;
429 int32_t yTop;
430 int32_t yBottom;
431
432} VBVADIRTYREGION;
433
434static void vbvaRgnInit (VBVADIRTYREGION *prgn, IFramebuffer *pfb, Display *pd, PPDMIDISPLAYPORT pp)
435{
436 memset (prgn, 0, sizeof (VBVADIRTYREGION));
437
438 prgn->pFramebuffer = pfb;
439 prgn->pDisplay = pd;
440 prgn->pPort = pp;
441
442 return;
443}
444
445static void vbvaRgnDirtyRect (VBVADIRTYREGION *prgn, VBVACMDHDR *phdr)
446{
447 LogFlowFunc (("x = %d, y = %d, w = %d, h = %d\n",
448 phdr->x, phdr->y, phdr->w, phdr->h));
449
450 /*
451 * Here update rectangles are accumulated to form an update area.
452 * @todo
453 * Now the simpliest method is used which builds one rectangle that
454 * includes all update areas. A bit more advanced method can be
455 * employed here. The method should be fast however.
456 */
457 if (phdr->w == 0 || phdr->h == 0)
458 {
459 /* Empty rectangle. */
460 return;
461 }
462
463 int32_t xRight = phdr->x + phdr->w;
464 int32_t yBottom = phdr->y + phdr->h;
465
466 if (prgn->xRight == 0)
467 {
468 /* This is the first rectangle to be added. */
469 prgn->xLeft = phdr->x;
470 prgn->yTop = phdr->y;
471 prgn->xRight = xRight;
472 prgn->yBottom = yBottom;
473 }
474 else
475 {
476 /* Adjust region coordinates. */
477 if (prgn->xLeft > phdr->x)
478 {
479 prgn->xLeft = phdr->x;
480 }
481
482 if (prgn->yTop > phdr->y)
483 {
484 prgn->yTop = phdr->y;
485 }
486
487 if (prgn->xRight < xRight)
488 {
489 prgn->xRight = xRight;
490 }
491
492 if (prgn->yBottom < yBottom)
493 {
494 prgn->yBottom = yBottom;
495 }
496 }
497
498 return;
499}
500
501static void vbvaRgnUpdateFramebuffer (VBVADIRTYREGION *prgn)
502{
503 uint32_t w = prgn->xRight - prgn->xLeft;
504 uint32_t h = prgn->yBottom - prgn->yTop;
505
506 if (prgn->pFramebuffer && w != 0 && h != 0)
507 {
508 prgn->pPort->pfnUpdateDisplayRect (prgn->pPort, prgn->xLeft, prgn->yTop, w, h);
509 prgn->pDisplay->handleDisplayUpdate (prgn->xLeft, prgn->yTop, w, h);
510 }
511}
512
513static void vbvaSetMemoryFlags (VBVAMEMORY *pVbvaMemory,
514 bool fVideoAccelEnabled,
515 bool fVideoAccelVRDP,
516 uint32_t fu32SupportedOrders)
517{
518 if (pVbvaMemory)
519 {
520 /* This called only on changes in mode. So reset VRDP always. */
521 uint32_t fu32Flags = VBVA_F_MODE_VRDP_RESET;
522
523 if (fVideoAccelEnabled)
524 {
525 fu32Flags |= VBVA_F_MODE_ENABLED;
526
527 if (fVideoAccelVRDP)
528 {
529 fu32Flags |= VBVA_F_MODE_VRDP | VBVA_F_MODE_VRDP_ORDER_MASK;
530
531 pVbvaMemory->fu32SupportedOrders = fu32SupportedOrders;
532 }
533 }
534
535 pVbvaMemory->fu32ModeFlags = fu32Flags;
536 }
537}
538
539bool Display::VideoAccelAllowed (void)
540{
541 return true;
542}
543
544/**
545 * @thread EMT
546 */
547int Display::VideoAccelEnable (bool fEnable, VBVAMEMORY *pVbvaMemory)
548{
549 int rc = VINF_SUCCESS;
550
551 /* Called each time the guest wants to use acceleration,
552 * or when the VGA device disables acceleration,
553 * or when restoring the saved state with accel enabled.
554 *
555 * VGA device disables acceleration on each video mode change
556 * and on reset.
557 *
558 * Guest enabled acceleration at will. And it has to enable
559 * acceleration after a mode change.
560 */
561 LogFlowFunc (("mfVideoAccelEnabled = %d, fEnable = %d, pVbvaMemory = %p\n",
562 mfVideoAccelEnabled, fEnable, pVbvaMemory));
563
564 /* Strictly check parameters. Callers must not pass anything in the case. */
565 Assert((fEnable && pVbvaMemory) || (!fEnable && pVbvaMemory == NULL));
566
567 if (!VideoAccelAllowed ())
568 {
569 return VERR_NOT_SUPPORTED;
570 }
571
572 /*
573 * Verify that the VM is in running state. If it is not,
574 * then this must be postponed until it goes to running.
575 */
576 if (!mfMachineRunning)
577 {
578 Assert (!mfVideoAccelEnabled);
579
580 LogFlowFunc (("Machine is not yet running.\n"));
581
582 if (fEnable)
583 {
584 mfPendingVideoAccelEnable = fEnable;
585 mpPendingVbvaMemory = pVbvaMemory;
586 }
587
588 return rc;
589 }
590
591 /* Check that current status is not being changed */
592 if (mfVideoAccelEnabled == fEnable)
593 {
594 return rc;
595 }
596
597 if (mfVideoAccelEnabled)
598 {
599 /* Process any pending orders and empty the VBVA ring buffer. */
600 VideoAccelFlush ();
601 }
602
603 if (!fEnable && mpVbvaMemory)
604 {
605 mpVbvaMemory->fu32ModeFlags &= ~VBVA_F_MODE_ENABLED;
606 }
607
608 /* Safety precaution. There is no more VBVA until everything is setup! */
609 mpVbvaMemory = NULL;
610 mfVideoAccelEnabled = false;
611
612 /* Update entire display. */
613 mpDrv->pUpPort->pfnUpdateDisplayAll(mpDrv->pUpPort);
614
615 /* Everything OK. VBVA status can be changed. */
616
617 /* Notify the VMMDev, which saves VBVA status in the saved state,
618 * and needs to know current status.
619 */
620 PPDMIVMMDEVPORT pVMMDevPort = mParent->getVMMDev()->getVMMDevPort ();
621
622 if (pVMMDevPort)
623 {
624 pVMMDevPort->pfnVBVAChange (pVMMDevPort, fEnable);
625 }
626
627 if (fEnable)
628 {
629 mpVbvaMemory = pVbvaMemory;
630 mfVideoAccelEnabled = true;
631
632 /* Initialize the hardware memory. */
633 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
634 mpVbvaMemory->off32Data = 0;
635 mpVbvaMemory->off32Free = 0;
636
637 memset (mpVbvaMemory->aRecords, 0, sizeof (mpVbvaMemory->aRecords));
638 mpVbvaMemory->indexRecordFirst = 0;
639 mpVbvaMemory->indexRecordFree = 0;
640
641 LogRel(("VBVA: Enabled.\n"));
642 }
643 else
644 {
645 LogRel(("VBVA: Disabled.\n"));
646 }
647
648 LogFlowFunc (("VideoAccelEnable: rc = %Vrc.\n", rc));
649
650 return rc;
651}
652
653#ifdef VBOX_VRDP
654#ifdef VRDP_MC
655/* Called always by one VRDP server thread. Can be thread-unsafe.
656 */
657void Display::VideoAccelVRDP (bool fEnable)
658{
659 /* Supporting all orders. */
660 uint32_t fu32SupportedOrders = ~0;
661
662 int c = fEnable?
663 ASMAtomicIncS32 (&mcVideoAccelVRDPRefs):
664 ASMAtomicDecS32 (&mcVideoAccelVRDPRefs);
665
666 Assert (c >= 0);
667
668 if (c == 0)
669 {
670 /* The last client has disconnected, and the accel can be
671 * disabled.
672 */
673 Assert (fEnable == false);
674
675 mfVideoAccelVRDP = false;
676 mfu32SupportedOrders = 0;
677
678 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
679
680 LogRel(("VBVA: VRDP acceleration has been disabled.\n"));
681 }
682 else if ( c == 1
683 && !mfVideoAccelVRDP)
684 {
685 /* The first client has connected. Enable the accel.
686 */
687 Assert (fEnable == true);
688
689 mfVideoAccelVRDP = true;
690 /* Supporting all orders. */
691 mfu32SupportedOrders = ~0;
692
693 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
694
695 LogRel(("VBVA: VRDP acceleration has been requested.\n"));
696 }
697 else
698 {
699 /* A client is connected or disconnected but there is no change in the
700 * accel state. It remains enabled.
701 */
702 Assert (mfVideoAccelVRDP == true);
703 }
704}
705#else
706void Display::VideoAccelVRDP (bool fEnable, uint32_t fu32SupportedOrders)
707{
708 Assert (mfVideoAccelVRDP != fEnable);
709
710 mfVideoAccelVRDP = fEnable;
711
712 if (fEnable)
713 {
714 mfu32SupportedOrders = fu32SupportedOrders;
715 }
716 else
717 {
718 mfu32SupportedOrders = 0;
719 }
720
721 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
722
723 LogRel(("VBVA: VRDP acceleration has been %s.\n", fEnable? "requested": "disabled"));
724}
725#endif /* VRDP_MC */
726#endif /* VBOX_VRDP */
727
728static bool vbvaVerifyRingBuffer (VBVAMEMORY *pVbvaMemory)
729{
730 return true;
731}
732
733static void vbvaFetchBytes (VBVAMEMORY *pVbvaMemory, uint8_t *pu8Dst, uint32_t cbDst)
734{
735 if (cbDst >= VBVA_RING_BUFFER_SIZE)
736 {
737 AssertFailed ();
738 return;
739 }
740
741 uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data;
742 uint8_t *src = &pVbvaMemory->au8RingBuffer[pVbvaMemory->off32Data];
743 int32_t i32Diff = cbDst - u32BytesTillBoundary;
744
745 if (i32Diff <= 0)
746 {
747 /* Chunk will not cross buffer boundary. */
748 memcpy (pu8Dst, src, cbDst);
749 }
750 else
751 {
752 /* Chunk crosses buffer boundary. */
753 memcpy (pu8Dst, src, u32BytesTillBoundary);
754 memcpy (pu8Dst + u32BytesTillBoundary, &pVbvaMemory->au8RingBuffer[0], i32Diff);
755 }
756
757 /* Advance data offset. */
758 pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbDst) % VBVA_RING_BUFFER_SIZE;
759
760 return;
761}
762
763
764static bool vbvaPartialRead (uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, VBVAMEMORY *pVbvaMemory)
765{
766 uint8_t *pu8New;
767
768 LogFlow(("MAIN::DisplayImpl::vbvaPartialRead: p = %p, cb = %d, cbRecord 0x%08X\n",
769 *ppu8, *pcb, cbRecord));
770
771 if (*ppu8)
772 {
773 Assert (*pcb);
774 pu8New = (uint8_t *)RTMemRealloc (*ppu8, cbRecord);
775 }
776 else
777 {
778 Assert (!*pcb);
779 pu8New = (uint8_t *)RTMemAlloc (cbRecord);
780 }
781
782 if (!pu8New)
783 {
784 /* Memory allocation failed, fail the function. */
785 Log(("MAIN::vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n",
786 cbRecord));
787
788 if (*ppu8)
789 {
790 RTMemFree (*ppu8);
791 }
792
793 *ppu8 = NULL;
794 *pcb = 0;
795
796 return false;
797 }
798
799 /* Fetch data from the ring buffer. */
800 vbvaFetchBytes (pVbvaMemory, pu8New + *pcb, cbRecord - *pcb);
801
802 *ppu8 = pu8New;
803 *pcb = cbRecord;
804
805 return true;
806}
807
808/* For contiguous chunks just return the address in the buffer.
809 * For crossing boundary - allocate a buffer from heap.
810 */
811bool Display::vbvaFetchCmd (VBVACMDHDR **ppHdr, uint32_t *pcbCmd)
812{
813 uint32_t indexRecordFirst = mpVbvaMemory->indexRecordFirst;
814 uint32_t indexRecordFree = mpVbvaMemory->indexRecordFree;
815
816#ifdef DEBUG_sunlover
817 LogFlowFunc (("first = %d, free = %d\n",
818 indexRecordFirst, indexRecordFree));
819#endif /* DEBUG_sunlover */
820
821 if (!vbvaVerifyRingBuffer (mpVbvaMemory))
822 {
823 return false;
824 }
825
826 if (indexRecordFirst == indexRecordFree)
827 {
828 /* No records to process. Return without assigning output variables. */
829 return true;
830 }
831
832 VBVARECORD *pRecord = &mpVbvaMemory->aRecords[indexRecordFirst];
833
834#ifdef DEBUG_sunlover
835 LogFlowFunc (("cbRecord = 0x%08X\n", pRecord->cbRecord));
836#endif /* DEBUG_sunlover */
837
838 uint32_t cbRecord = pRecord->cbRecord & ~VBVA_F_RECORD_PARTIAL;
839
840 if (mcbVbvaPartial)
841 {
842 /* There is a partial read in process. Continue with it. */
843
844 Assert (mpu8VbvaPartial);
845
846 LogFlowFunc (("continue partial record mcbVbvaPartial = %d cbRecord 0x%08X, first = %d, free = %d\n",
847 mcbVbvaPartial, pRecord->cbRecord, indexRecordFirst, indexRecordFree));
848
849 if (cbRecord > mcbVbvaPartial)
850 {
851 /* New data has been added to the record. */
852 if (!vbvaPartialRead (&mpu8VbvaPartial, &mcbVbvaPartial, cbRecord, mpVbvaMemory))
853 {
854 return false;
855 }
856 }
857
858 if (!(pRecord->cbRecord & VBVA_F_RECORD_PARTIAL))
859 {
860 /* The record is completed by guest. Return it to the caller. */
861 *ppHdr = (VBVACMDHDR *)mpu8VbvaPartial;
862 *pcbCmd = mcbVbvaPartial;
863
864 mpu8VbvaPartial = NULL;
865 mcbVbvaPartial = 0;
866
867 /* Advance the record index. */
868 mpVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
869
870#ifdef DEBUG_sunlover
871 LogFlowFunc (("partial done ok, data = %d, free = %d\n",
872 mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
873#endif /* DEBUG_sunlover */
874 }
875
876 return true;
877 }
878
879 /* A new record need to be processed. */
880 if (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL)
881 {
882 /* Current record is being written by guest. '=' is important here. */
883 if (cbRecord >= VBVA_RING_BUFFER_SIZE - VBVA_RING_BUFFER_THRESHOLD)
884 {
885 /* Partial read must be started. */
886 if (!vbvaPartialRead (&mpu8VbvaPartial, &mcbVbvaPartial, cbRecord, mpVbvaMemory))
887 {
888 return false;
889 }
890
891 LogFlowFunc (("started partial record mcbVbvaPartial = 0x%08X cbRecord 0x%08X, first = %d, free = %d\n",
892 mcbVbvaPartial, pRecord->cbRecord, indexRecordFirst, indexRecordFree));
893 }
894
895 return true;
896 }
897
898 /* Current record is complete. If it is not empty, process it. */
899 if (cbRecord)
900 {
901 /* The size of largest contiguos chunk in the ring biffer. */
902 uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - mpVbvaMemory->off32Data;
903
904 /* The ring buffer pointer. */
905 uint8_t *au8RingBuffer = &mpVbvaMemory->au8RingBuffer[0];
906
907 /* The pointer to data in the ring buffer. */
908 uint8_t *src = &au8RingBuffer[mpVbvaMemory->off32Data];
909
910 /* Fetch or point the data. */
911 if (u32BytesTillBoundary >= cbRecord)
912 {
913 /* The command does not cross buffer boundary. Return address in the buffer. */
914 *ppHdr = (VBVACMDHDR *)src;
915
916 /* Advance data offset. */
917 mpVbvaMemory->off32Data = (mpVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
918 }
919 else
920 {
921 /* The command crosses buffer boundary. Rare case, so not optimized. */
922 uint8_t *dst = (uint8_t *)RTMemAlloc (cbRecord);
923
924 if (!dst)
925 {
926 LogFlowFunc (("could not allocate %d bytes from heap!!!\n", cbRecord));
927 mpVbvaMemory->off32Data = (mpVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
928 return false;
929 }
930
931 vbvaFetchBytes (mpVbvaMemory, dst, cbRecord);
932
933 *ppHdr = (VBVACMDHDR *)dst;
934
935#ifdef DEBUG_sunlover
936 LogFlowFunc (("Allocated from heap %p\n", dst));
937#endif /* DEBUG_sunlover */
938 }
939 }
940
941 *pcbCmd = cbRecord;
942
943 /* Advance the record index. */
944 mpVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
945
946#ifdef DEBUG_sunlover
947 LogFlowFunc (("done ok, data = %d, free = %d\n",
948 mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
949#endif /* DEBUG_sunlover */
950
951 return true;
952}
953
954void Display::vbvaReleaseCmd (VBVACMDHDR *pHdr, int32_t cbCmd)
955{
956 uint8_t *au8RingBuffer = mpVbvaMemory->au8RingBuffer;
957
958 if ( (uint8_t *)pHdr >= au8RingBuffer
959 && (uint8_t *)pHdr < &au8RingBuffer[VBVA_RING_BUFFER_SIZE])
960 {
961 /* The pointer is inside ring buffer. Must be continuous chunk. */
962 Assert (VBVA_RING_BUFFER_SIZE - ((uint8_t *)pHdr - au8RingBuffer) >= cbCmd);
963
964 /* Do nothing. */
965
966 Assert (!mpu8VbvaPartial && mcbVbvaPartial == 0);
967 }
968 else
969 {
970 /* The pointer is outside. It is then an allocated copy. */
971
972#ifdef DEBUG_sunlover
973 LogFlowFunc (("Free heap %p\n", pHdr));
974#endif /* DEBUG_sunlover */
975
976 if ((uint8_t *)pHdr == mpu8VbvaPartial)
977 {
978 mpu8VbvaPartial = NULL;
979 mcbVbvaPartial = 0;
980 }
981 else
982 {
983 Assert (!mpu8VbvaPartial && mcbVbvaPartial == 0);
984 }
985
986 RTMemFree (pHdr);
987 }
988
989 return;
990}
991
992
993/**
994 * Called regularly on the DisplayRefresh timer.
995 * Also on behalf of guest, when the ring buffer is full.
996 *
997 * @thread EMT
998 */
999void Display::VideoAccelFlush (void)
1000{
1001#ifdef DEBUG_sunlover
1002 LogFlowFunc (("mfVideoAccelEnabled = %d\n", mfVideoAccelEnabled));
1003#endif /* DEBUG_sunlover */
1004
1005 if (!mfVideoAccelEnabled)
1006 {
1007 Log(("Display::VideoAccelFlush: called with disabled VBVA!!! Ignoring.\n"));
1008 return;
1009 }
1010
1011 /* Here VBVA is enabled and we have the accelerator memory pointer. */
1012 Assert(mpVbvaMemory);
1013
1014#ifdef DEBUG_sunlover
1015 LogFlowFunc (("indexRecordFirst = %d, indexRecordFree = %d, off32Data = %d, off32Free = %d\n",
1016 mpVbvaMemory->indexRecordFirst, mpVbvaMemory->indexRecordFree, mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
1017#endif /* DEBUG_sunlover */
1018
1019 /* Quick check for "nothing to update" case. */
1020 if (mpVbvaMemory->indexRecordFirst == mpVbvaMemory->indexRecordFree)
1021 {
1022 return;
1023 }
1024
1025 /* Process the ring buffer */
1026
1027 bool fFramebufferIsNull = mFramebuffer.isNull();
1028
1029 if (!fFramebufferIsNull)
1030 {
1031 mFramebuffer->Lock();
1032 }
1033
1034 /* Initialize dirty rectangles accumulator. */
1035 VBVADIRTYREGION rgn;
1036 vbvaRgnInit (&rgn, mFramebuffer, this, mpDrv->pUpPort);
1037
1038 for (;;)
1039 {
1040 VBVACMDHDR *phdr = NULL;
1041 uint32_t cbCmd = ~0;
1042
1043 /* Fetch the command data. */
1044 if (!vbvaFetchCmd (&phdr, &cbCmd))
1045 {
1046 Log(("Display::VideoAccelFlush: unable to fetch command. off32Data = %d, off32Free = %d. Disabling VBVA!!!\n",
1047 mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
1048
1049 /* Disable VBVA on those processing errors. */
1050 VideoAccelEnable (false, NULL);
1051
1052 break;
1053 }
1054
1055 if (cbCmd == uint32_t(~0))
1056 {
1057 /* No more commands yet in the queue. */
1058 break;
1059 }
1060
1061 if (cbCmd != 0 && !fFramebufferIsNull)
1062 {
1063#ifdef DEBUG_sunlover
1064 LogFlowFunc (("hdr: cbCmd = %d, x=%d, y=%d, w=%d, h=%d\n",
1065 cbCmd, phdr->x, phdr->y, phdr->w, phdr->h));
1066#endif /* DEBUG_sunlover */
1067
1068 /* Handle the command.
1069 *
1070 * Guest is responsible for updating the guest video memory.
1071 * The Windows guest does all drawing using Eng*.
1072 *
1073 * For local output, only dirty rectangle information is used
1074 * to update changed areas.
1075 *
1076 * Dirty rectangles are accumulated to exclude overlapping updates and
1077 * group small updates to a larger one.
1078 */
1079
1080 /* Accumulate the update. */
1081 vbvaRgnDirtyRect (&rgn, phdr);
1082
1083 /* Forward the command to VRDP server. */
1084 mParent->consoleVRDPServer()->SendUpdate (phdr, cbCmd);
1085 }
1086
1087 vbvaReleaseCmd (phdr, cbCmd);
1088 }
1089
1090 if (!fFramebufferIsNull)
1091 {
1092 mFramebuffer->Unlock ();
1093 }
1094
1095 /* Draw the framebuffer. */
1096 vbvaRgnUpdateFramebuffer (&rgn);
1097}
1098
1099
1100// IDisplay properties
1101/////////////////////////////////////////////////////////////////////////////
1102
1103/**
1104 * Returns the current display width in pixel
1105 *
1106 * @returns COM status code
1107 * @param width Address of result variable.
1108 */
1109STDMETHODIMP Display::COMGETTER(Width) (ULONG *width)
1110{
1111 if (!width)
1112 return E_POINTER;
1113
1114 AutoLock alock (this);
1115 CHECK_READY();
1116
1117 CHECK_CONSOLE_DRV (mpDrv);
1118
1119 *width = mpDrv->Connector.cx;
1120 return S_OK;
1121}
1122
1123/**
1124 * Returns the current display height in pixel
1125 *
1126 * @returns COM status code
1127 * @param height Address of result variable.
1128 */
1129STDMETHODIMP Display::COMGETTER(Height) (ULONG *height)
1130{
1131 if (!height)
1132 return E_POINTER;
1133
1134 AutoLock alock (this);
1135 CHECK_READY();
1136
1137 CHECK_CONSOLE_DRV (mpDrv);
1138
1139 *height = mpDrv->Connector.cy;
1140 return S_OK;
1141}
1142
1143/**
1144 * Returns the current display color depth in bits
1145 *
1146 * @returns COM status code
1147 * @param colorDepth Address of result variable.
1148 */
1149STDMETHODIMP Display::COMGETTER(ColorDepth) (ULONG *colorDepth)
1150{
1151 if (!colorDepth)
1152 return E_INVALIDARG;
1153
1154 AutoLock alock (this);
1155 CHECK_READY();
1156
1157 CHECK_CONSOLE_DRV (mpDrv);
1158
1159 uint32_t cBits = 0;
1160 int rc = mpDrv->pUpPort->pfnQueryColorDepth(mpDrv->pUpPort, &cBits);
1161 AssertRC(rc);
1162 *colorDepth = cBits;
1163 return S_OK;
1164}
1165
1166
1167// IDisplay methods
1168/////////////////////////////////////////////////////////////////////////////
1169
1170STDMETHODIMP Display::SetupInternalFramebuffer (ULONG depth)
1171{
1172 LogFlowFunc (("\n"));
1173
1174 AutoLock lock (this);
1175 CHECK_READY();
1176
1177 /*
1178 * Create an internal framebuffer only if depth is not zero. Otherwise, we
1179 * reset back to the "black hole" state as it was at Display construction.
1180 */
1181 ComPtr <IFramebuffer> frameBuf;
1182 if (depth)
1183 {
1184 ComObjPtr <InternalFramebuffer> internal;
1185 internal.createObject();
1186 internal->init (640, 480, depth);
1187 frameBuf = internal; // query interface
1188 }
1189
1190 Console::SafeVMPtrQuiet pVM (mParent);
1191 if (pVM.isOk())
1192 {
1193 /* send request to the EMT thread */
1194 PVMREQ pReq = NULL;
1195 int vrc = VMR3ReqCall (pVM, &pReq, RT_INDEFINITE_WAIT,
1196 (PFNRT) changeFramebuffer, 3,
1197 this, static_cast <IFramebuffer *> (frameBuf),
1198 true /* aInternal */);
1199 if (VBOX_SUCCESS (vrc))
1200 vrc = pReq->iStatus;
1201 VMR3ReqFree (pReq);
1202
1203 ComAssertRCRet (vrc, E_FAIL);
1204 }
1205 else
1206 {
1207 /* No VM is created (VM is powered off), do a direct call */
1208 int vrc = changeFramebuffer (this, frameBuf, true /* aInternal */);
1209 ComAssertRCRet (vrc, E_FAIL);
1210 }
1211
1212 return S_OK;
1213}
1214
1215STDMETHODIMP Display::LockFramebuffer (ULONG *address)
1216{
1217 if (!address)
1218 return E_POINTER;
1219
1220 AutoLock lock(this);
1221 CHECK_READY();
1222
1223 /* only allowed for internal framebuffers */
1224 if (mInternalFramebuffer && !mFramebufferOpened && !mFramebuffer.isNull())
1225 {
1226 CHECK_CONSOLE_DRV (mpDrv);
1227
1228 mFramebuffer->Lock();
1229 mFramebufferOpened = true;
1230 *address = (ULONG)mpDrv->Connector.pu8Data;
1231 return S_OK;
1232 }
1233
1234 return setError (E_FAIL,
1235 tr ("Framebuffer locking is allowed only for the internal framebuffer"));
1236}
1237
1238STDMETHODIMP Display::UnlockFramebuffer()
1239{
1240 AutoLock lock(this);
1241 CHECK_READY();
1242
1243 if (mFramebufferOpened)
1244 {
1245 CHECK_CONSOLE_DRV (mpDrv);
1246
1247 mFramebuffer->Unlock();
1248 mFramebufferOpened = false;
1249 return S_OK;
1250 }
1251
1252 return setError (E_FAIL,
1253 tr ("Framebuffer locking is allowed only for the internal framebuffer"));
1254}
1255
1256STDMETHODIMP Display::RegisterExternalFramebuffer (IFramebuffer *frameBuf)
1257{
1258 LogFlowFunc (("\n"));
1259
1260 if (!frameBuf)
1261 return E_POINTER;
1262
1263 AutoLock lock (this);
1264 CHECK_READY();
1265
1266 Console::SafeVMPtrQuiet pVM (mParent);
1267 if (pVM.isOk())
1268 {
1269 /* send request to the EMT thread */
1270 PVMREQ pReq = NULL;
1271 int vrc = VMR3ReqCall (pVM, &pReq, RT_INDEFINITE_WAIT,
1272 (PFNRT) changeFramebuffer, 3,
1273 this, frameBuf, false /* aInternal */);
1274 if (VBOX_SUCCESS (vrc))
1275 vrc = pReq->iStatus;
1276 VMR3ReqFree (pReq);
1277
1278 ComAssertRCRet (vrc, E_FAIL);
1279 }
1280 else
1281 {
1282 /* No VM is created (VM is powered off), do a direct call */
1283 int vrc = changeFramebuffer (this, frameBuf, false /* aInternal */);
1284 ComAssertRCRet (vrc, E_FAIL);
1285 }
1286
1287 return S_OK;
1288}
1289
1290STDMETHODIMP Display::SetVideoModeHint(ULONG aWidth, ULONG aHeight, ULONG aColorDepth)
1291{
1292 AutoLock lock(this);
1293 CHECK_READY();
1294
1295 CHECK_CONSOLE_DRV (mpDrv);
1296
1297 /*
1298 * Do some rough checks for valid input
1299 */
1300 ULONG width = aWidth;
1301 if (!width)
1302 width = mpDrv->Connector.cx;
1303 ULONG height = aHeight;
1304 if (!height)
1305 height = mpDrv->Connector.cy;
1306 ULONG bpp = aColorDepth;
1307 if (!bpp)
1308 {
1309 uint32_t cBits = 0;
1310 int rc = mpDrv->pUpPort->pfnQueryColorDepth(mpDrv->pUpPort, &cBits);
1311 AssertRC(rc);
1312 bpp = cBits;
1313 }
1314 ULONG vramSize;
1315 mParent->machine()->COMGETTER(VRAMSize)(&vramSize);
1316 /* enough VRAM? */
1317 if ((width * height * (bpp / 8)) > (vramSize * 1024 * 1024))
1318 return setError(E_FAIL, tr("Not enough VRAM for the selected video mode"));
1319
1320 LogRel(("Display::SetVideoModeHint: got a video mode hint (%dx%dx%d)\n",
1321 aWidth, aHeight, bpp));
1322 if (mParent->getVMMDev())
1323 mParent->getVMMDev()->getVMMDevPort()->pfnRequestDisplayChange(mParent->getVMMDev()->getVMMDevPort(), aWidth, aHeight, aColorDepth);
1324 return S_OK;
1325}
1326
1327/**
1328 * Takes a screen shot of the requested size and copies it to the buffer
1329 * allocated by the caller. The screen shot is always a 32-bpp image, so the
1330 * size of the buffer must be at least (((width * 32 + 31) / 32) * 4) * height
1331 * (i.e. dword-aligned). If the requested screen shot dimentions differ from
1332 * the actual VM display size then the screen shot image is stretched and/or
1333 * shrunk accordingly.
1334 *
1335 * @returns COM status code
1336 * @param address the address of the buffer allocated by the caller
1337 * @param width the width of the screenshot to take
1338 * @param height the height of the screenshot to take
1339 */
1340STDMETHODIMP Display::TakeScreenShot (ULONG address, ULONG width, ULONG height)
1341{
1342 /// @todo (r=dmik) this function may take too long to complete if the VM
1343 // is doing something like saving state right now. Which, in case if it
1344 // is called on the GUI thread, will make it unresponsive. We should
1345 // check the machine state here (by enclosing the check and VMRequCall
1346 // within the Console lock to make it atomic).
1347
1348 LogFlowFuncEnter();
1349 LogFlowFunc (("address=%p, width=%d, height=%d\n",
1350 address, width, height));
1351
1352 if (!address)
1353 return E_POINTER;
1354 if (!width || !height)
1355 return E_INVALIDARG;
1356
1357 AutoLock lock(this);
1358 CHECK_READY();
1359
1360 CHECK_CONSOLE_DRV (mpDrv);
1361
1362 Console::SafeVMPtr pVM (mParent);
1363 CheckComRCReturnRC (pVM.rc());
1364
1365 HRESULT rc = S_OK;
1366
1367 LogFlowFunc (("Sending SCREENSHOT request\n"));
1368
1369 /*
1370 * First try use the graphics device features for making a snapshot.
1371 * This does not support streatching, is an optional feature (returns not supported).
1372 *
1373 * Note: It may cause a display resize. Watch out for deadlocks.
1374 */
1375 int rcVBox = VERR_NOT_SUPPORTED;
1376 if ( mpDrv->Connector.cx == width
1377 && mpDrv->Connector.cy == height)
1378 {
1379 PVMREQ pReq;
1380 size_t cbData = RT_ALIGN_Z(width, 4) * 4 * height;
1381 rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1382 (PFNRT)mpDrv->pUpPort->pfnSnapshot, 6, mpDrv->pUpPort,
1383 (void *)address, cbData, NULL, NULL, NULL);
1384 if (VBOX_SUCCESS(rcVBox))
1385 {
1386 rcVBox = pReq->iStatus;
1387 VMR3ReqFree(pReq);
1388 }
1389 }
1390
1391 /*
1392 * If the function returns not supported, or if streaching is requested,
1393 * we'll have to do all the work ourselves using the framebuffer data.
1394 */
1395 if (rcVBox == VERR_NOT_SUPPORTED || rcVBox == VERR_NOT_IMPLEMENTED)
1396 {
1397 /** @todo implement snapshot streching and generic snapshot fallback. */
1398 rc = setError (E_NOTIMPL, tr ("This feature is not implemented"));
1399 }
1400 else if (VBOX_FAILURE(rcVBox))
1401 rc = setError (E_FAIL,
1402 tr ("Could not take a screenshot (%Vrc)"), rcVBox);
1403
1404 LogFlowFunc (("rc=%08X\n", rc));
1405 LogFlowFuncLeave();
1406 return rc;
1407}
1408
1409/**
1410 * Draws an image of the specified size from the given buffer to the given
1411 * point on the VM display. The image is assumed to have a 32-bit depth, so the
1412 * size of one scanline must be ((width * 32 + 31) / 32) * 4), i.e.
1413 * dword-aligned. The total image size in the buffer is height * scanline size.
1414 *
1415 * @returns COM status code
1416 * @param address the address of the buffer containing the image
1417 * @param x the x coordinate on the VM display to place the image to
1418 * @param y the y coordinate on the VM display to place the image to
1419 * @param width the width of the image in the buffer
1420 * @param height the height of the image in the buffer
1421 */
1422STDMETHODIMP Display::DrawToScreen (ULONG address, ULONG x, ULONG y,
1423 ULONG width, ULONG height)
1424{
1425 /// @todo (r=dmik) this function may take too long to complete if the VM
1426 // is doing something like saving state right now. Which, in case if it
1427 // is called on the GUI thread, will make it unresponsive. We should
1428 // check the machine state here (by enclosing the check and VMRequCall
1429 // within the Console lock to make it atomic).
1430
1431 LogFlowFuncEnter();
1432 LogFlowFunc (("address=%p, x=%d, y=%d, width=%d, height=%d\n",
1433 address, x, y, width, height));
1434
1435 AutoLock lock(this);
1436 CHECK_READY();
1437
1438 CHECK_CONSOLE_DRV (mpDrv);
1439
1440 Console::SafeVMPtr pVM (mParent);
1441 CheckComRCReturnRC (pVM.rc());
1442
1443 /*
1444 * Again we're lazy and make the graphics device do all the
1445 * dirty convertion work.
1446 */
1447 PVMREQ pReq;
1448 int rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1449 (PFNRT)mpDrv->pUpPort->pfnDisplayBlt, 6, mpDrv->pUpPort,
1450 (void *)address, x, y, width, height);
1451 if (VBOX_SUCCESS(rcVBox))
1452 {
1453 rcVBox = pReq->iStatus;
1454 VMR3ReqFree(pReq);
1455 }
1456
1457 /*
1458 * If the function returns not supported, we'll have to do all the
1459 * work ourselves using the framebuffer.
1460 */
1461 HRESULT rc = S_OK;
1462 if (rcVBox == VERR_NOT_SUPPORTED || rcVBox == VERR_NOT_IMPLEMENTED)
1463 {
1464 /** @todo implement generic fallback for screen blitting. */
1465 rc = E_NOTIMPL;
1466 }
1467 else if (VBOX_FAILURE(rcVBox))
1468 rc = setError (E_FAIL,
1469 tr ("Could not draw to the screen (%Vrc)"), rcVBox);
1470//@todo
1471// else
1472// {
1473// /* All ok. Redraw the screen. */
1474// handleDisplayUpdate (x, y, width, height);
1475// }
1476
1477 LogFlowFunc (("rc=%08X\n", rc));
1478 LogFlowFuncLeave();
1479 return rc;
1480}
1481
1482/**
1483 * Does a full invalidation of the VM display and instructs the VM
1484 * to update it immediately.
1485 *
1486 * @returns COM status code
1487 */
1488STDMETHODIMP Display::InvalidateAndUpdate()
1489{
1490 LogFlowFuncEnter();
1491
1492 AutoLock lock(this);
1493 CHECK_READY();
1494
1495 CHECK_CONSOLE_DRV (mpDrv);
1496
1497 Console::SafeVMPtr pVM (mParent);
1498 CheckComRCReturnRC (pVM.rc());
1499
1500 HRESULT rc = S_OK;
1501
1502 LogFlowFunc (("Sending DPYUPDATE request\n"));
1503
1504 /* pdm.h says that this has to be called from the EMT thread */
1505 PVMREQ pReq;
1506 int rcVBox = VMR3ReqCallVoid(pVM, &pReq, RT_INDEFINITE_WAIT,
1507 (PFNRT)mpDrv->pUpPort->pfnUpdateDisplayAll, 1, mpDrv->pUpPort);
1508 if (VBOX_SUCCESS(rcVBox))
1509 VMR3ReqFree(pReq);
1510
1511 if (VBOX_FAILURE(rcVBox))
1512 rc = setError (E_FAIL,
1513 tr ("Could not invalidate and update the screen (%Vrc)"), rcVBox);
1514
1515 LogFlowFunc (("rc=%08X\n", rc));
1516 LogFlowFuncLeave();
1517 return rc;
1518}
1519
1520/**
1521 * Notification that the framebuffer has completed the
1522 * asynchronous resize processing
1523 *
1524 * @returns COM status code
1525 */
1526STDMETHODIMP Display::ResizeCompleted()
1527{
1528 LogFlowFunc (("\n"));
1529
1530 /// @todo (dmik) can we AutoLock alock (this); here?
1531 // do it when we switch this class to VirtualBoxBase_NEXT.
1532 // This will require general code review and may add some details.
1533 // In particular, we may want to check whether EMT is really waiting for
1534 // this notification, etc. It might be also good to obey the caller to make
1535 // sure this method is not called from more than one thread at a time
1536 // (and therefore don't use Display lock at all here to save some
1537 // milliseconds).
1538 CHECK_READY();
1539
1540 /* this is only valid for external framebuffers */
1541 if (mInternalFramebuffer)
1542 return setError (E_FAIL,
1543 tr ("Resize completed notification is valid only "
1544 "for external framebuffers"));
1545
1546 /* signal our semaphore */
1547 RTSemEventMultiSignal(mResizeSem);
1548
1549 return S_OK;
1550}
1551
1552/**
1553 * Notification that the framebuffer has completed the
1554 * asynchronous update processing
1555 *
1556 * @returns COM status code
1557 */
1558STDMETHODIMP Display::UpdateCompleted()
1559{
1560 LogFlowFunc (("\n"));
1561
1562 /// @todo (dmik) can we AutoLock alock (this); here?
1563 // do it when we switch this class to VirtualBoxBase_NEXT.
1564 // Tthis will require general code review and may add some details.
1565 // In particular, we may want to check whether EMT is really waiting for
1566 // this notification, etc. It might be also good to obey the caller to make
1567 // sure this method is not called from more than one thread at a time
1568 // (and therefore don't use Display lock at all here to save some
1569 // milliseconds).
1570 CHECK_READY();
1571
1572 /* this is only valid for external framebuffers */
1573 if (mInternalFramebuffer)
1574 return setError (E_FAIL,
1575 tr ("Resize completed notification is valid only "
1576 "for external framebuffers"));
1577
1578 mFramebuffer->Lock();
1579 /* signal our semaphore */
1580 RTSemEventMultiSignal(mUpdateSem);
1581 mFramebuffer->Unlock();
1582
1583 return S_OK;
1584}
1585
1586// private methods
1587/////////////////////////////////////////////////////////////////////////////
1588
1589/**
1590 * Helper to update the display information from the framebuffer.
1591 *
1592 * @param aCheckParams true to compare the parameters of the current framebuffer
1593 * and the new one and issue handleDisplayResize()
1594 * if they differ.
1595 * @thread EMT
1596 */
1597void Display::updateDisplayData (bool aCheckParams /* = false */)
1598{
1599 /* the driver might not have been constructed yet */
1600 if (!mpDrv)
1601 return;
1602
1603#if DEBUG
1604 /*
1605 * Sanity check. Note that this method may be called on EMT after Console
1606 * has started the power down procedure (but before our #drvDestruct() is
1607 * called, in which case pVM will aleady be NULL but mpDrv will not). Since
1608 * we don't really need pVM to proceed, we avoid this check in the release
1609 * build to save some ms (necessary to construct SafeVMPtrQuiet) in this
1610 * time-critical method.
1611 */
1612 Console::SafeVMPtrQuiet pVM (mParent);
1613 if (pVM.isOk())
1614 VM_ASSERT_EMT (pVM.raw());
1615#endif
1616
1617 if (mFramebuffer)
1618 {
1619 HRESULT rc;
1620 ULONG address = 0;
1621 rc = mFramebuffer->COMGETTER(Address) (&address);
1622 AssertComRC (rc);
1623 ULONG lineSize = 0;
1624 rc = mFramebuffer->COMGETTER(LineSize) (&lineSize);
1625 AssertComRC (rc);
1626 ULONG colorDepth = 0;
1627 rc = mFramebuffer->COMGETTER(ColorDepth) (&colorDepth);
1628 AssertComRC (rc);
1629 ULONG width = 0;
1630 rc = mFramebuffer->COMGETTER(Width) (&width);
1631 AssertComRC (rc);
1632 ULONG height = 0;
1633 rc = mFramebuffer->COMGETTER(Height) (&height);
1634 AssertComRC (rc);
1635
1636 /*
1637 * Check current parameters with new ones and issue handleDisplayResize()
1638 * to let the new frame buffer adjust itself properly. Note that it will
1639 * result into a recursive updateDisplayData() call but with
1640 * aCheckOld = false.
1641 */
1642 if (aCheckParams &&
1643 (mLastAddress != (uint8_t *) address ||
1644 mLastLineSize != lineSize ||
1645 mLastColorDepth != colorDepth ||
1646 mLastWidth != (int) width ||
1647 mLastHeight != (int) height))
1648 {
1649 handleDisplayResize (mLastColorDepth,
1650 mLastAddress,
1651 mLastLineSize,
1652 mLastWidth,
1653 mLastHeight);
1654 return;
1655 }
1656
1657 mpDrv->Connector.pu8Data = (uint8_t *) address;
1658 mpDrv->Connector.cbScanline = lineSize;
1659 mpDrv->Connector.cBits = colorDepth;
1660 mpDrv->Connector.cx = width;
1661 mpDrv->Connector.cy = height;
1662 }
1663 else
1664 {
1665 /* black hole */
1666 mpDrv->Connector.pu8Data = NULL;
1667 mpDrv->Connector.cbScanline = 0;
1668 mpDrv->Connector.cBits = 0;
1669 mpDrv->Connector.cx = 0;
1670 mpDrv->Connector.cy = 0;
1671 }
1672}
1673
1674/**
1675 * Changes the current frame buffer. Called on EMT to avoid both
1676 * race conditions and excessive locking.
1677 *
1678 * @note locks this object for writing
1679 * @thread EMT
1680 */
1681/* static */
1682DECLCALLBACK(int) Display::changeFramebuffer (Display *that, IFramebuffer *aFB,
1683 bool aInternal)
1684{
1685 LogFlowFunc (("\n"));
1686
1687 AssertReturn (that, VERR_INVALID_PARAMETER);
1688 AssertReturn (aFB || aInternal, VERR_INVALID_PARAMETER);
1689
1690 /// @todo (r=dmik) AutoCaller
1691
1692 AutoLock alock (that);
1693
1694 that->mFramebuffer = aFB;
1695 that->mInternalFramebuffer = aInternal;
1696 that->mSupportedAccelOps = 0;
1697
1698 /* determine which acceleration functions are supported by this framebuffer */
1699 if (aFB && !aInternal)
1700 {
1701 HRESULT rc;
1702 BOOL accelSupported = FALSE;
1703 rc = aFB->OperationSupported (
1704 FramebufferAccelerationOperation_SolidFillAcceleration, &accelSupported);
1705 AssertComRC (rc);
1706 if (accelSupported)
1707 that->mSupportedAccelOps |=
1708 FramebufferAccelerationOperation_SolidFillAcceleration;
1709 accelSupported = FALSE;
1710 rc = aFB->OperationSupported (
1711 FramebufferAccelerationOperation_ScreenCopyAcceleration, &accelSupported);
1712 AssertComRC (rc);
1713 if (accelSupported)
1714 that->mSupportedAccelOps |=
1715 FramebufferAccelerationOperation_ScreenCopyAcceleration;
1716 }
1717
1718 that->mParent->consoleVRDPServer()->
1719 SetFramebuffer (aFB, aFB ? VRDP_EXTERNAL_FRAMEBUFFER :
1720 VRDP_INTERNAL_FRAMEBUFFER);
1721
1722 that->updateDisplayData (true /* aCheckParams */);
1723
1724 return VINF_SUCCESS;
1725}
1726
1727/**
1728 * Handle display resize event.
1729 *
1730 * @see PDMIDISPLAYCONNECTOR::pfnResize
1731 */
1732DECLCALLBACK(void) Display::displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface,
1733 uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy)
1734{
1735 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1736
1737 LogFlowFunc (("bpp %d, pvVRAM %p, cbLine %d, cx %d, cy %d\n",
1738 bpp, pvVRAM, cbLine, cx, cy));
1739
1740 pDrv->pDisplay->handleDisplayResize(bpp, pvVRAM, cbLine, cx, cy);
1741}
1742
1743/**
1744 * Handle display update.
1745 *
1746 * @see PDMIDISPLAYCONNECTOR::pfnUpdateRect
1747 */
1748DECLCALLBACK(void) Display::displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface,
1749 uint32_t x, uint32_t y, uint32_t cx, uint32_t cy)
1750{
1751 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1752
1753#ifdef DEBUG_sunlover
1754 LogFlowFunc (("pDrv->pDisplay->mfVideoAccelEnabled = %d, %d,%d %dx%d\n",
1755 pDrv->pDisplay->mfVideoAccelEnabled, x, y, cx, cy));
1756#endif /* DEBUG_sunlover */
1757
1758 /* This call does update regardless of VBVA status.
1759 * But in VBVA mode this is called only as result of
1760 * pfnUpdateDisplayAll in the VGA device.
1761 */
1762
1763 pDrv->pDisplay->handleDisplayUpdate(x, y, cx, cy);
1764}
1765
1766/**
1767 * Periodic display refresh callback.
1768 *
1769 * @see PDMIDISPLAYCONNECTOR::pfnRefresh
1770 */
1771DECLCALLBACK(void) Display::displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface)
1772{
1773 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1774
1775#ifdef DEBUG_sunlover
1776 STAM_PROFILE_START(&StatDisplayRefresh, a);
1777#endif /* DEBUG_sunlover */
1778
1779#ifdef DEBUG_sunlover
1780 LogFlowFunc (("pDrv->pDisplay->mfVideoAccelEnabled = %d\n",
1781 pDrv->pDisplay->mfVideoAccelEnabled));
1782#endif /* DEBUG_sunlover */
1783
1784 Display *pDisplay = pDrv->pDisplay;
1785
1786 if (pDisplay->mFramebuffer.isNull())
1787 {
1788 /*
1789 * Do nothing in the "black hole" mode to avoid copying guest
1790 * video memory to the frame buffer
1791 */
1792 }
1793 else
1794 {
1795 if (pDisplay->mfPendingVideoAccelEnable)
1796 {
1797 /* Acceleration was enabled while machine was not yet running
1798 * due to restoring from saved state. Update entire display and
1799 * actually enable acceleration.
1800 */
1801 Assert(pDisplay->mpPendingVbvaMemory);
1802
1803 /* Acceleration can not be yet enabled.*/
1804 Assert(pDisplay->mpVbvaMemory == NULL);
1805 Assert(!pDisplay->mfVideoAccelEnabled);
1806
1807 if (pDisplay->mfMachineRunning)
1808 {
1809 pDisplay->VideoAccelEnable (pDisplay->mfPendingVideoAccelEnable,
1810 pDisplay->mpPendingVbvaMemory);
1811
1812 /* Reset the pending state. */
1813 pDisplay->mfPendingVideoAccelEnable = false;
1814 pDisplay->mpPendingVbvaMemory = NULL;
1815 }
1816 }
1817 else
1818 {
1819 Assert(pDisplay->mpPendingVbvaMemory == NULL);
1820
1821 if (pDisplay->mfVideoAccelEnabled)
1822 {
1823 Assert(pDisplay->mpVbvaMemory);
1824 pDisplay->VideoAccelFlush ();
1825 }
1826 else
1827 {
1828 Assert(pDrv->Connector.pu8Data);
1829 pDrv->pUpPort->pfnUpdateDisplay(pDrv->pUpPort);
1830 }
1831 }
1832#ifdef VRDP_MC
1833 /* Inform to VRDP server that the current display update sequence is
1834 * completed. At this moment the framebuffer memory contains a definite
1835 * image, that is synchronized with the orders already sent to VRDP client.
1836 * The server can now process redraw requests from clients or initial
1837 * fullscreen updates for new clients.
1838 */
1839 Assert (pDisplay->mParent && pDisplay->mParent->consoleVRDPServer());
1840 pDisplay->mParent->consoleVRDPServer()->SendUpdate (NULL, 0);
1841#endif /* VRDP_MC */
1842 }
1843
1844#ifdef DEBUG_sunlover
1845 STAM_PROFILE_STOP(&StatDisplayRefresh, a);
1846#endif /* DEBUG_sunlover */
1847}
1848
1849/**
1850 * Reset notification
1851 *
1852 * @see PDMIDISPLAYCONNECTOR::pfnReset
1853 */
1854DECLCALLBACK(void) Display::displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface)
1855{
1856 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1857
1858 LogFlowFunc (("\n"));
1859
1860 /* Disable VBVA mode. */
1861 pDrv->pDisplay->VideoAccelEnable (false, NULL);
1862}
1863
1864/**
1865 * LFBModeChange notification
1866 *
1867 * @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange
1868 */
1869DECLCALLBACK(void) Display::displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled)
1870{
1871 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1872
1873 LogFlowFunc (("fEnabled=%d\n", fEnabled));
1874
1875 NOREF(fEnabled);
1876
1877 /* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */
1878 pDrv->pDisplay->VideoAccelEnable (false, NULL);
1879}
1880
1881/**
1882 * Queries an interface to the driver.
1883 *
1884 * @returns Pointer to interface.
1885 * @returns NULL if the interface was not supported by the driver.
1886 * @param pInterface Pointer to this interface structure.
1887 * @param enmInterface The requested interface identification.
1888 */
1889DECLCALLBACK(void *) Display::drvQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
1890{
1891 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1892 PDRVMAINDISPLAY pDrv = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1893 switch (enmInterface)
1894 {
1895 case PDMINTERFACE_BASE:
1896 return &pDrvIns->IBase;
1897 case PDMINTERFACE_DISPLAY_CONNECTOR:
1898 return &pDrv->Connector;
1899 default:
1900 return NULL;
1901 }
1902}
1903
1904
1905/**
1906 * Destruct a display driver instance.
1907 *
1908 * @returns VBox status.
1909 * @param pDrvIns The driver instance data.
1910 */
1911DECLCALLBACK(void) Display::drvDestruct(PPDMDRVINS pDrvIns)
1912{
1913 PDRVMAINDISPLAY pData = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1914 LogFlowFunc (("iInstance=%d\n", pDrvIns->iInstance));
1915 if (pData->pDisplay)
1916 {
1917 AutoLock displayLock (pData->pDisplay);
1918 pData->pDisplay->mpDrv = NULL;
1919 pData->pDisplay->mpVMMDev = NULL;
1920 pData->pDisplay->mLastAddress = NULL;
1921 pData->pDisplay->mLastLineSize = 0;
1922 pData->pDisplay->mLastColorDepth = 0,
1923 pData->pDisplay->mLastWidth = 0;
1924 pData->pDisplay->mLastHeight = 0;
1925 }
1926}
1927
1928
1929/**
1930 * Construct a display driver instance.
1931 *
1932 * @returns VBox status.
1933 * @param pDrvIns The driver instance data.
1934 * If the registration structure is needed, pDrvIns->pDrvReg points to it.
1935 * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
1936 * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
1937 * iInstance it's expected to be used a bit in this function.
1938 */
1939DECLCALLBACK(int) Display::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
1940{
1941 PDRVMAINDISPLAY pData = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1942 LogFlowFunc (("iInstance=%d\n", pDrvIns->iInstance));
1943
1944 /*
1945 * Validate configuration.
1946 */
1947 if (!CFGMR3AreValuesValid(pCfgHandle, "Object\0"))
1948 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
1949 PPDMIBASE pBaseIgnore;
1950 int rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pBaseIgnore);
1951 if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
1952 {
1953 AssertMsgFailed(("Configuration error: Not possible to attach anything to this driver!\n"));
1954 return VERR_PDM_DRVINS_NO_ATTACH;
1955 }
1956
1957 /*
1958 * Init Interfaces.
1959 */
1960 pDrvIns->IBase.pfnQueryInterface = Display::drvQueryInterface;
1961
1962 pData->Connector.pfnResize = Display::displayResizeCallback;
1963 pData->Connector.pfnUpdateRect = Display::displayUpdateCallback;
1964 pData->Connector.pfnRefresh = Display::displayRefreshCallback;
1965 pData->Connector.pfnReset = Display::displayResetCallback;
1966 pData->Connector.pfnLFBModeChange = Display::displayLFBModeChangeCallback;
1967
1968 /*
1969 * Get the IDisplayPort interface of the above driver/device.
1970 */
1971 pData->pUpPort = (PPDMIDISPLAYPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_DISPLAY_PORT);
1972 if (!pData->pUpPort)
1973 {
1974 AssertMsgFailed(("Configuration error: No display port interface above!\n"));
1975 return VERR_PDM_MISSING_INTERFACE_ABOVE;
1976 }
1977
1978 /*
1979 * Get the Display object pointer and update the mpDrv member.
1980 */
1981 void *pv;
1982 rc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv);
1983 if (VBOX_FAILURE(rc))
1984 {
1985 AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Vrc\n", rc));
1986 return rc;
1987 }
1988 pData->pDisplay = (Display *)pv; /** @todo Check this cast! */
1989 pData->pDisplay->mpDrv = pData;
1990
1991 /*
1992 * Update our display information according to the framebuffer
1993 */
1994 pData->pDisplay->updateDisplayData();
1995
1996 /*
1997 * Start periodic screen refreshes
1998 */
1999 pData->pUpPort->pfnSetRefreshRate(pData->pUpPort, 20);
2000
2001 return VINF_SUCCESS;
2002}
2003
2004
2005/**
2006 * Display driver registration record.
2007 */
2008const PDMDRVREG Display::DrvReg =
2009{
2010 /* u32Version */
2011 PDM_DRVREG_VERSION,
2012 /* szDriverName */
2013 "MainDisplay",
2014 /* pszDescription */
2015 "Main display driver (Main as in the API).",
2016 /* fFlags */
2017 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2018 /* fClass. */
2019 PDM_DRVREG_CLASS_DISPLAY,
2020 /* cMaxInstances */
2021 ~0,
2022 /* cbInstance */
2023 sizeof(DRVMAINDISPLAY),
2024 /* pfnConstruct */
2025 Display::drvConstruct,
2026 /* pfnDestruct */
2027 Display::drvDestruct,
2028 /* pfnIOCtl */
2029 NULL,
2030 /* pfnPowerOn */
2031 NULL,
2032 /* pfnReset */
2033 NULL,
2034 /* pfnSuspend */
2035 NULL,
2036 /* pfnResume */
2037 NULL,
2038 /* pfnDetach */
2039 NULL
2040};
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette