summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage/DrvRamDisk.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Storage/DrvRamDisk.cpp')
-rw-r--r--src/VBox/Devices/Storage/DrvRamDisk.cpp1863
1 files changed, 1863 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/DrvRamDisk.cpp b/src/VBox/Devices/Storage/DrvRamDisk.cpp
new file mode 100644
index 00000000..bf43c511
--- /dev/null
+++ b/src/VBox/Devices/Storage/DrvRamDisk.cpp
@@ -0,0 +1,1863 @@
+/* $Id: DrvRamDisk.cpp $ */
+/** @file
+ * VBox storage devices: RAM disk driver.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_DISK_INTEGRITY
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/avl.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/memcache.h>
+#include <iprt/message.h>
+#include <iprt/sg.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/asm.h>
+#include <iprt/req.h>
+#include <iprt/thread.h>
+
+#include "VBoxDD.h"
+#include "IOBufMgmt.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Pointer to a ramdisk driver instance. */
+typedef struct DRVRAMDISK *PDRVRAMDISK;
+
+/**
+ * Disk segment.
+ */
+typedef struct DRVDISKSEGMENT
+{
+ /** AVL core. */
+ AVLRFOFFNODECORE Core;
+ /** Size of the segment */
+ size_t cbSeg;
+ /** Data for this segment */
+ uint8_t *pbSeg;
+} DRVDISKSEGMENT, *PDRVDISKSEGMENT;
+
+/**
+ * VD I/O request state.
+ */
+typedef enum VDIOREQSTATE
+{
+ /** Invalid. */
+ VDIOREQSTATE_INVALID = 0,
+ /** The request is not in use and resides on the free list. */
+ VDIOREQSTATE_FREE,
+ /** The request was just allocated and is not active. */
+ VDIOREQSTATE_ALLOCATED,
+ /** The request was allocated and is in use. */
+ VDIOREQSTATE_ACTIVE,
+ /** The request was suspended and is not actively processed. */
+ VDIOREQSTATE_SUSPENDED,
+ /** The request is in the last step of completion and syncs memory. */
+ VDIOREQSTATE_COMPLETING,
+ /** The request completed. */
+ VDIOREQSTATE_COMPLETED,
+ /** The request was aborted but wasn't returned as complete from the storage
+ * layer below us. */
+ VDIOREQSTATE_CANCELED,
+ /** 32bit hack. */
+ VDIOREQSTATE_32BIT_HACK = 0x7fffffff
+} VDIOREQSTATE;
+
+/**
+ * VD I/O Request.
+ */
+typedef struct PDMMEDIAEXIOREQINT
+{
+ /** List node for the list of allocated requests. */
+ RTLISTNODE NdAllocatedList;
+ /** List for requests waiting for I/O memory or on the redo list. */
+ RTLISTNODE NdLstWait;
+ /** I/O request type. */
+ PDMMEDIAEXIOREQTYPE enmType;
+ /** Request state. */
+ volatile VDIOREQSTATE enmState;
+ /** I/O request ID. */
+ PDMMEDIAEXIOREQID uIoReqId;
+ /** Pointer to the disk container. */
+ PDRVRAMDISK pDisk;
+ /** Flags. */
+ uint32_t fFlags;
+ /** Timestamp when the request was submitted. */
+ uint64_t tsSubmit;
+ /** Type dependent data. */
+ union
+ {
+ /** Read/Write request sepcific data. */
+ struct
+ {
+ /** Start offset of the request. */
+ uint64_t offStart;
+ /** Size of the request. */
+ size_t cbReq;
+ /** Size left for this request. */
+ size_t cbReqLeft;
+ /** Size of the allocated I/O buffer. */
+ size_t cbIoBuf;
+ /** I/O buffer descriptor. */
+ IOBUFDESC IoBuf;
+ } ReadWrite;
+ /** Discard specific data. */
+ struct
+ {
+ /** Pointer to array of ranges to discard. */
+ PRTRANGE paRanges;
+ /** Number of ranges to discard. */
+ unsigned cRanges;
+ } Discard;
+ };
+ /** Allocator specific memory - variable size. */
+ uint8_t abAlloc[1];
+} PDMMEDIAEXIOREQINT;
+/** Pointer to a VD I/O request. */
+typedef PDMMEDIAEXIOREQINT *PPDMMEDIAEXIOREQINT;
+
+/**
+ * Structure for holding a list of allocated requests.
+ */
+typedef struct VDLSTIOREQALLOC
+{
+ /** Mutex protecting the table of allocated requests. */
+ RTSEMFASTMUTEX hMtxLstIoReqAlloc;
+ /** List anchor. */
+ RTLISTANCHOR LstIoReqAlloc;
+} VDLSTIOREQALLOC;
+typedef VDLSTIOREQALLOC *PVDLSTIOREQALLOC;
+
+/** Number of bins for allocated requests. */
+#define DRVVD_VDIOREQ_ALLOC_BINS 8
+
+/**
+ * Disk integrity driver instance data.
+ *
+ * @implements PDMIMEDIA
+ */
+typedef struct DRVRAMDISK
+{
+ /** Pointer driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the media driver below us.
+ * This is NULL if the media is not mounted. */
+ PPDMIMEDIA pDrvMedia;
+ /** Our media interface */
+ PDMIMEDIA IMedia;
+
+ /** The media port interface above. */
+ PPDMIMEDIAPORT pDrvMediaPort;
+ /** Media port interface */
+ PDMIMEDIAPORT IMediaPort;
+
+ /** Flag whether the RAM disk was pre allocated. */
+ bool fPreallocRamDisk;
+ /** Flag whether to report a non totating medium. */
+ bool fNonRotational;
+ /** AVL tree containing the disk blocks to check. */
+ PAVLRFOFFTREE pTreeSegments;
+ /** Size of the disk. */
+ uint64_t cbDisk;
+ /** Size of one sector. */
+ uint32_t cbSector;
+
+ /** Worker request queue. */
+ RTREQQUEUE hReqQ;
+ /** Worker thread for async requests. */
+ RTTHREAD hThrdWrk;
+
+ /** @name IMEDIAEX interface support specific members.
+ * @{ */
+ /** Pointer to the IMEDIAEXPORT interface above us. */
+ PPDMIMEDIAEXPORT pDrvMediaExPort;
+ /** Our extended media interface. */
+ PDMIMEDIAEX IMediaEx;
+ /** Memory cache for the I/O requests. */
+ RTMEMCACHE hIoReqCache;
+ /** I/O buffer manager. */
+ IOBUFMGR hIoBufMgr;
+ /** Active request counter. */
+ volatile uint32_t cIoReqsActive;
+ /** Bins for allocated requests. */
+ VDLSTIOREQALLOC aIoReqAllocBins[DRVVD_VDIOREQ_ALLOC_BINS];
+ /** List of requests for I/O memory to be available - VDIOREQ::NdLstWait. */
+ RTLISTANCHOR LstIoReqIoBufWait;
+ /** Critical section protecting the list of requests waiting for I/O memory. */
+ RTCRITSECT CritSectIoReqsIoBufWait;
+ /** Number of requests waiting for a I/O buffer. */
+ volatile uint32_t cIoReqsWaiting;
+ /** Flag whether we have to resubmit requests on resume because the
+ * VM was suspended due to a recoverable I/O error.
+ */
+ volatile bool fRedo;
+ /** List of requests we have to redo. */
+ RTLISTANCHOR LstIoReqRedo;
+ /** Criticial section protecting the list of waiting requests. */
+ RTCRITSECT CritSectIoReqRedo;
+ /** Number of errors logged so far. */
+ unsigned cErrors;
+ /** @} */
+
+} DRVRAMDISK;
+
+
+static void drvramdiskMediaExIoReqComplete(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq,
+ int rcReq);
+
+/**
+ * Record a successful write to the virtual disk.
+ *
+ * @returns VBox status code.
+ * @param pThis Disk integrity driver instance data.
+ * @param pSgBuf The S/G buffer holding the data to write.
+ * @param off Start offset.
+ * @param cbWrite Number of bytes to record.
+ */
+static int drvramdiskWriteWorker(PDRVRAMDISK pThis, PRTSGBUF pSgBuf,
+ uint64_t off, size_t cbWrite)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pSgBuf=%#p off=%llx cbWrite=%u\n",
+ pThis, pSgBuf, off, cbWrite));
+
+ /* Update the segments */
+ size_t cbLeft = cbWrite;
+ RTFOFF offCurr = (RTFOFF)off;
+
+ while (cbLeft)
+ {
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetRangeGet(pThis->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ bool fSet = false;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetGetBestFit(pThis->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ Assert(cbRange % 512 == 0);
+
+ /* Create new segment */
+ pSeg = (PDRVDISKSEGMENT)RTMemAllocZ(sizeof(DRVDISKSEGMENT));
+ if (pSeg)
+ {
+ pSeg->Core.Key = offCurr;
+ pSeg->Core.KeyLast = offCurr + (RTFOFF)cbRange - 1;
+ pSeg->cbSeg = cbRange;
+ pSeg->pbSeg = (uint8_t *)RTMemAllocZ(cbRange);
+ if (!pSeg->pbSeg)
+ RTMemFree(pSeg);
+ else
+ {
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ AssertMsg(fInserted, ("Bug!\n")); RT_NOREF(fInserted);
+ fSet = true;
+ }
+ }
+ }
+ else
+ {
+ fSet = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+ }
+
+ if (fSet)
+ {
+ AssertPtr(pSeg);
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, pSeg->pbSeg + offSeg, cbRange);
+ Assert(cbCopied == cbRange); RT_NOREF(cbCopied);
+ }
+ else
+ RTSgBufAdvance(pSgBuf, cbRange);
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return rc;
+}
+
+/**
+ * Read data from the ram disk.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk driver instance data.
+ * @param pSgBuf The S/G buffer to store the data.
+ * @param off Start offset.
+ * @param cbRead Number of bytes to read.
+ */
+static int drvramdiskReadWorker(PDRVRAMDISK pThis, PRTSGBUF pSgBuf,
+ uint64_t off, size_t cbRead)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p pSgBuf=%#p off=%llx cbRead=%u\n",
+ pThis, pSgBuf, off, cbRead));
+
+ Assert(off % 512 == 0);
+ Assert(cbRead % 512 == 0);
+
+ /* Compare read data */
+ size_t cbLeft = cbRead;
+ RTFOFF offCurr = (RTFOFF)off;
+
+ while (cbLeft)
+ {
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetRangeGet(pThis->pTreeSegments, offCurr);
+ size_t cbRange = 0;
+ bool fCmp = false;
+ unsigned offSeg = 0;
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetGetBestFit(pThis->pTreeSegments, offCurr, true);
+ if ( !pSeg
+ || offCurr + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offCurr;
+
+ /* No segment means everything should be 0 for this part. */
+ RTSgBufSet(pSgBuf, 0, cbRange);
+ }
+ else
+ {
+ fCmp = true;
+ offSeg = offCurr - pSeg->Core.Key;
+ cbRange = RT_MIN(cbLeft, (size_t)(pSeg->Core.KeyLast + 1 - offCurr));
+
+ RTSGSEG Seg;
+ RTSGBUF SgBufSrc;
+
+ Seg.cbSeg = cbRange;
+ Seg.pvSeg = pSeg->pbSeg + offSeg;
+
+ RTSgBufInit(&SgBufSrc, &Seg, 1);
+ RTSgBufCopy(pSgBuf, &SgBufSrc, cbRange);
+ }
+
+ offCurr += cbRange;
+ cbLeft -= cbRange;
+ }
+
+ return rc;
+}
+
+/**
+ * Discards the given ranges from the disk.
+ *
+ * @returns VBox status code.
+ * @param pThis Disk integrity driver instance data.
+ * @param paRanges Array of ranges to discard.
+ * @param cRanges Number of ranges in the array.
+ */
+static int drvramdiskDiscardRecords(PDRVRAMDISK pThis, PCRTRANGE paRanges, unsigned cRanges)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%#p paRanges=%#p cRanges=%u\n", pThis, paRanges, cRanges));
+
+ for (unsigned i = 0; i < cRanges; i++)
+ {
+ uint64_t offStart = paRanges[i].offStart;
+ size_t cbLeft = paRanges[i].cbRange;
+
+ LogFlowFunc(("Discarding off=%llu cbRange=%zu\n", offStart, cbLeft));
+
+ while (cbLeft)
+ {
+ size_t cbRange;
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetRangeGet(pThis->pTreeSegments, offStart);
+
+ if (!pSeg)
+ {
+ /* Get next segment */
+ pSeg = (PDRVDISKSEGMENT)RTAvlrFileOffsetGetBestFit(pThis->pTreeSegments, offStart, true);
+ if ( !pSeg
+ || (RTFOFF)offStart + (RTFOFF)cbLeft <= pSeg->Core.Key)
+ cbRange = cbLeft;
+ else
+ cbRange = pSeg->Core.Key - offStart;
+
+ Assert(!(cbRange % 512));
+ }
+ else
+ {
+ size_t cbPreLeft, cbPostLeft;
+
+ cbRange = RT_MIN(cbLeft, pSeg->Core.KeyLast - offStart + 1);
+ cbPreLeft = offStart - pSeg->Core.Key;
+ cbPostLeft = pSeg->cbSeg - cbRange - cbPreLeft;
+
+ Assert(!(cbRange % 512));
+ Assert(!(cbPreLeft % 512));
+ Assert(!(cbPostLeft % 512));
+
+ LogFlowFunc(("cbRange=%zu cbPreLeft=%zu cbPostLeft=%zu\n",
+ cbRange, cbPreLeft, cbPostLeft));
+
+ RTAvlrFileOffsetRemove(pThis->pTreeSegments, pSeg->Core.Key);
+
+ if (!cbPreLeft && !cbPostLeft)
+ {
+ /* Just free the whole segment. */
+ LogFlowFunc(("Freeing whole segment pSeg=%#p\n", pSeg));
+ RTMemFree(pSeg->pbSeg);
+ RTMemFree(pSeg);
+ }
+ else if (cbPreLeft && !cbPostLeft)
+ {
+ /* Realloc to new size and insert. */
+ LogFlowFunc(("Realloc segment pSeg=%#p\n", pSeg));
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPreLeft);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, sizeof(DRVDISKSEGMENT));
+ pSeg->Core.KeyLast = pSeg->Core.Key + cbPreLeft - 1;
+ pSeg->cbSeg = cbPreLeft;
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ else if (!cbPreLeft && cbPostLeft)
+ {
+ /* Move data to the front and realloc. */
+ LogFlowFunc(("Move data and realloc segment pSeg=%#p\n", pSeg));
+ memmove(pSeg->pbSeg, pSeg->pbSeg + cbRange, cbPostLeft);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, sizeof(DRVDISKSEGMENT));
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPostLeft);
+ pSeg->Core.Key += cbRange;
+ pSeg->cbSeg = cbPostLeft;
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ else
+ {
+ /* Split the segment into 2 new segments. */
+ LogFlowFunc(("Split segment pSeg=%#p\n", pSeg));
+ PDRVDISKSEGMENT pSegPost = (PDRVDISKSEGMENT)RTMemAllocZ(sizeof(DRVDISKSEGMENT));
+ if (pSegPost)
+ {
+ pSegPost->Core.Key = pSeg->Core.Key + cbPreLeft + cbRange;
+ pSegPost->Core.KeyLast = pSeg->Core.KeyLast;
+ pSegPost->cbSeg = cbPostLeft;
+ pSegPost->pbSeg = (uint8_t *)RTMemAllocZ(cbPostLeft);
+ if (!pSegPost->pbSeg)
+ RTMemFree(pSegPost);
+ else
+ {
+ memcpy(pSegPost->pbSeg, pSeg->pbSeg + cbPreLeft + cbRange, cbPostLeft);
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSegPost->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ }
+ }
+
+ /* Shrink the current segment. */
+ pSeg->pbSeg = (uint8_t *)RTMemRealloc(pSeg->pbSeg, cbPreLeft);
+ pSeg = (PDRVDISKSEGMENT)RTMemRealloc(pSeg, sizeof(DRVDISKSEGMENT));
+ pSeg->Core.KeyLast = pSeg->Core.Key + cbPreLeft - 1;
+ pSeg->cbSeg = cbPreLeft;
+ bool fInserted = RTAvlrFileOffsetInsert(pThis->pTreeSegments, &pSeg->Core);
+ Assert(fInserted); RT_NOREF(fInserted);
+ } /* if (cbPreLeft && cbPostLeft) */
+ }
+
+ offStart += cbRange;
+ cbLeft -= cbRange;
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+/* -=-=-=-=- IMedia -=-=-=-=- */
+
+
+/*********************************************************************************************************************************
+* Media interface methods *
+*********************************************************************************************************************************/
+
+/** @copydoc PDMIMEDIA::pfnRead */
+static DECLCALLBACK(int) drvramdiskRead(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.cbSeg = cbRead;
+ Seg.pvSeg = pvBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return drvramdiskReadWorker(pThis, &SgBuf, off, cbRead);
+}
+
+/** @copydoc PDMIMEDIA::pfnWrite */
+static DECLCALLBACK(int) drvramdiskWrite(PPDMIMEDIA pInterface,
+ uint64_t off, const void *pvBuf,
+ size_t cbWrite)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.cbSeg = cbWrite;
+ Seg.pvSeg = (void *)pvBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return drvramdiskWriteWorker(pThis, &SgBuf, off, cbWrite);
+}
+
+/** @copydoc PDMIMEDIA::pfnFlush */
+static DECLCALLBACK(int) drvramdiskFlush(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ /* Nothing to do here. */
+ return VINF_SUCCESS;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetSize */
+static DECLCALLBACK(uint64_t) drvramdiskGetSize(PPDMIMEDIA pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return pThis->cbDisk;
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnBiosIsVisible} */
+static DECLCALLBACK(bool) drvramdiskBiosIsVisible(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return false;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetType */
+static DECLCALLBACK(PDMMEDIATYPE) drvramdiskGetType(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return PDMMEDIATYPE_HARD_DISK;
+}
+
+/** @copydoc PDMIMEDIA::pfnIsReadOnly */
+static DECLCALLBACK(bool) drvramdiskIsReadOnly(PPDMIMEDIA pInterface)
+{
+ RT_NOREF1(pInterface);
+ return false; /** @todo */
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetPCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosGetPCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF2(pInterface, pPCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetPCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosSetPCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pPCHSGeometry)
+{
+ RT_NOREF2(pInterface, pPCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosGetLCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosGetLCHSGeometry(PPDMIMEDIA pInterface,
+ PPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF2(pInterface, pLCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnBiosSetLCHSGeometry */
+static DECLCALLBACK(int) drvramdiskBiosSetLCHSGeometry(PPDMIMEDIA pInterface,
+ PCPDMMEDIAGEOMETRY pLCHSGeometry)
+{
+ RT_NOREF2(pInterface, pLCHSGeometry);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/** @copydoc PDMIMEDIA::pfnGetUuid */
+static DECLCALLBACK(int) drvramdiskGetUuid(PPDMIMEDIA pInterface, PRTUUID pUuid)
+{
+ RT_NOREF1(pInterface);
+ return RTUuidClear(pUuid);
+}
+
+/** @copydoc PDMIMEDIA::pfnGetSectorSize */
+static DECLCALLBACK(uint32_t) drvramdiskGetSectorSize(PPDMIMEDIA pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return pThis->cbSector;
+}
+
+/** @copydoc PDMIMEDIA::pfnDiscard */
+static DECLCALLBACK(int) drvramdiskDiscard(PPDMIMEDIA pInterface, PCRTRANGE paRanges, unsigned cRanges)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return drvramdiskDiscardRecords(pThis, paRanges, cRanges);
+}
+
+/** @copydoc PDMIMEDIA::pfnReadPcBios */
+static DECLCALLBACK(int) drvramdiskReadPcBios(PPDMIMEDIA pInterface,
+ uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ RTSGSEG Seg;
+ RTSGBUF SgBuf;
+
+ Seg.cbSeg = cbRead;
+ Seg.pvSeg = pvBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+ return drvramdiskReadWorker(pThis, &SgBuf, off, cbRead);
+}
+
+/** @interface_method_impl{PDMIMEDIA,pfnIsNonRotational} */
+static DECLCALLBACK(bool) drvramdiskIsNonRotational(PPDMIMEDIA pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMedia);
+ return pThis->fNonRotational;
+}
+
+
+/*********************************************************************************************************************************
+* Extended media interface methods *
+*********************************************************************************************************************************/
+
+static void drvramdiskMediaExIoReqWarningOutOfMemory(PPDMDRVINS pDrvIns)
+{
+ int rc;
+ LogRel(("RamDisk#%u: Out of memory\n", pDrvIns->iInstance));
+ rc = PDMDrvHlpVMSetRuntimeError(pDrvIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DrvRamDisk_OOM",
+ N_("There is not enough free memory for the ramdisk"));
+ AssertRC(rc);
+}
+
+/**
+ * Checks whether a given status code indicates a recoverable error
+ * suspending the VM if it is.
+ *
+ * @returns Flag indicating whether the status code is a recoverable error
+ * (full disk, broken network connection).
+ * @param pThis VBox disk container instance data.
+ * @param rc Status code to check.
+ */
+bool drvramdiskMediaExIoReqIsRedoSetWarning(PDRVRAMDISK pThis, int rc)
+{
+ if (rc == VERR_NO_MEMORY)
+ {
+ if (ASMAtomicCmpXchgBool(&pThis->fRedo, true, false))
+ drvramdiskMediaExIoReqWarningOutOfMemory(pThis->pDrvIns);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Syncs the memory buffers between the I/O request allocator and the internal buffer.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to sync.
+ * @param fToIoBuf Flag indicating the sync direction.
+ * true to copy data from the allocators buffer to our internal buffer.
+ * false for the other direction.
+ */
+DECLINLINE(int) drvramdiskMediaExIoReqBufSync(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fToIoBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE);
+
+ /* Make sure the buffer is reset. */
+ RTSgBufReset(&pIoReq->ReadWrite.IoBuf.SgBuf);
+
+ if (fToIoBuf)
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyToBuf(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ (uint32_t)(pIoReq->ReadWrite.cbReq - pIoReq->ReadWrite.cbReqLeft),
+ &pIoReq->ReadWrite.IoBuf.SgBuf,
+ RT_MIN(pIoReq->ReadWrite.cbIoBuf, pIoReq->ReadWrite.cbReqLeft));
+ else
+ rc = pThis->pDrvMediaExPort->pfnIoReqCopyFromBuf(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ (uint32_t)(pIoReq->ReadWrite.cbReq - pIoReq->ReadWrite.cbReqLeft),
+ &pIoReq->ReadWrite.IoBuf.SgBuf,
+ RT_MIN(pIoReq->ReadWrite.cbIoBuf, pIoReq->ReadWrite.cbReqLeft));
+
+ RTSgBufReset(&pIoReq->ReadWrite.IoBuf.SgBuf);
+ return rc;
+}
+
+/**
+ * Hashes the I/O request ID to an index for the allocated I/O request bin.
+ */
+DECLINLINE(unsigned) drvramdiskMediaExIoReqIdHash(PDMMEDIAEXIOREQID uIoReqId)
+{
+ return uIoReqId % DRVVD_VDIOREQ_ALLOC_BINS; /** @todo Find something better? */
+}
+
+/**
+ * Inserts the given I/O request in to the list of allocated I/O requests.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to insert.
+ */
+static int drvramdiskMediaExIoReqInsert(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxBin = drvramdiskMediaExIoReqIdHash(pIoReq->uIoReqId);
+
+ rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for conflicting I/O request ID. */
+ PPDMMEDIAEXIOREQINT pIt;
+ RTListForEach(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, pIt, PDMMEDIAEXIOREQINT, NdAllocatedList)
+ {
+ if (RT_UNLIKELY(pIt->uIoReqId == pIoReq->uIoReqId))
+ {
+ rc = VERR_PDM_MEDIAEX_IOREQID_CONFLICT;
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, &pIoReq->NdAllocatedList);
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * Removes the given I/O request from the list of allocated I/O requests.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to insert.
+ */
+static int drvramdiskMediaExIoReqRemove(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxBin = drvramdiskMediaExIoReqIdHash(pIoReq->uIoReqId);
+
+ rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pIoReq->NdAllocatedList);
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * I/O request completion worker.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to complete.
+ * @param rcReq The status code the request completed with.
+ * @param fUpNotify Flag whether to notify the driver/device above us about the completion.
+ */
+static int drvramdiskMediaExIoReqCompleteWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, int rcReq, bool fUpNotify)
+{
+ int rc;
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_COMPLETING, VDIOREQSTATE_ACTIVE);
+ if (fXchg)
+ ASMAtomicDecU32(&pThis->cIoReqsActive);
+ else
+ {
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ rcReq = VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_COMPLETED);
+
+ /*
+ * Leave a release log entry if the request was active for more than 25 seconds
+ * (30 seconds is the timeout of the guest).
+ */
+ uint64_t tsNow = RTTimeMilliTS();
+ if (tsNow - pIoReq->tsSubmit >= 25 * 1000)
+ {
+ const char *pcszReq = NULL;
+
+ switch (pIoReq->enmType)
+ {
+ case PDMMEDIAEXIOREQTYPE_READ:
+ pcszReq = "Read";
+ break;
+ case PDMMEDIAEXIOREQTYPE_WRITE:
+ pcszReq = "Write";
+ break;
+ case PDMMEDIAEXIOREQTYPE_FLUSH:
+ pcszReq = "Flush";
+ break;
+ case PDMMEDIAEXIOREQTYPE_DISCARD:
+ pcszReq = "Discard";
+ break;
+ default:
+ pcszReq = "<Invalid>";
+ }
+
+ LogRel(("RamDisk#%u: %s request was active for %llu seconds\n",
+ pThis->pDrvIns->iInstance, pcszReq, (tsNow - pIoReq->tsSubmit) / 1000));
+ }
+
+ if (RT_FAILURE(rcReq))
+ {
+ /* Log the error. */
+ if (pThis->cErrors++ < 100)
+ {
+ if (rcReq == VERR_PDM_MEDIAEX_IOREQ_CANCELED)
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("RamDisk#%u: Aborted flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ LogRel(("RamDisk#%u: Aborted %s (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "read"
+ : "write",
+ pIoReq->ReadWrite.cbReqLeft, rcReq));
+ }
+ else
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH)
+ LogRel(("RamDisk#%u: Flush returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance, rcReq));
+ else
+ LogRel(("RamDisk#%u: %s (%u bytes left) returned rc=%Rrc\n",
+ pThis->pDrvIns->iInstance,
+ pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ ? "Read"
+ : "Write",
+ pIoReq->ReadWrite.cbReqLeft, rcReq));
+ }
+ }
+ }
+
+ if (fUpNotify)
+ {
+ rc = pThis->pDrvMediaExPort->pfnIoReqCompleteNotify(pThis->pDrvMediaExPort,
+ pIoReq, &pIoReq->abAlloc[0], rcReq);
+ AssertRC(rc);
+ }
+
+ return rcReq;
+}
+
+/**
+ * Allocates a memory buffer suitable for I/O for the given request.
+ *
+ * @returns VBox status code.
+ * @retval VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS if there is no I/O memory available to allocate and
+ * the request was placed on a waiting list.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to allocate memory for.
+ * @param cb Size of the buffer.
+ */
+DECLINLINE(int) drvramdiskMediaExIoReqBufAlloc(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, size_t cb)
+{
+ int rc = IOBUFMgrAllocBuf(pThis->hIoBufMgr, &pIoReq->ReadWrite.IoBuf, cb, &pIoReq->ReadWrite.cbIoBuf);
+ if (rc == VERR_NO_MEMORY)
+ {
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ RTListAppend(&pThis->LstIoReqIoBufWait, &pIoReq->NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ ASMAtomicIncU32(&pThis->cIoReqsWaiting);
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+
+ return rc;
+}
+
+/**
+ * Worker for a read request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The read request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqReadWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+ int rc = drvramdiskReadWorker(pThis, &pIoReq->ReadWrite.IoBuf.SgBuf, pIoReq->ReadWrite.offStart,
+ cbReqIo);
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, rc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for a read request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The read request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqWriteWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+ int rc = drvramdiskWriteWorker(pThis, &pIoReq->ReadWrite.IoBuf.SgBuf, pIoReq->ReadWrite.offStart,
+ cbReqIo);
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, rc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Processes a read/write request.
+ *
+ * @returns VBox status code.
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request to process.
+ * @param fUpNotify Flag whether to notify the driver/device above us about the completion.
+ */
+static int drvramdiskMediaExIoReqReadWriteProcess(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq, bool fUpNotify)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE);
+
+ while ( pIoReq->ReadWrite.cbReqLeft
+ && rc == VINF_SUCCESS)
+ {
+ if (pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ rc = RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqReadWorker, 2, pThis, pIoReq);
+ else
+ {
+ /* Sync memory buffer from the request initiator. */
+ rc = drvramdiskMediaExIoReqBufSync(pThis, pIoReq, true /* fToIoBuf */);
+ if (RT_SUCCESS(rc))
+ rc = RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqWriteWorker, 2, pThis, pIoReq);
+ }
+
+ if (rc == VINF_SUCCESS)
+ rc = VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS;
+ }
+
+ if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ {
+ Assert(!pIoReq->ReadWrite.cbReqLeft || RT_FAILURE(rc));
+ rc = drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReq, rc, fUpNotify);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Frees a I/O memory buffer allocated previously.
+ *
+ * @param pThis VBox disk container instance data.
+ * @param pIoReq I/O request for which to free memory.
+ */
+DECLINLINE(void) drvramdiskMediaExIoReqBufFree(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ {
+ IOBUFMgrFreeBuf(&pIoReq->ReadWrite.IoBuf);
+
+ if (ASMAtomicReadU32(&pThis->cIoReqsWaiting) > 0)
+ {
+ /* Try to process as many requests as possible. */
+ RTCritSectEnter(&pThis->CritSectIoReqsIoBufWait);
+ PPDMMEDIAEXIOREQINT pIoReqCur, pIoReqNext;
+
+ RTListForEachSafe(&pThis->LstIoReqIoBufWait, pIoReqCur, pIoReqNext, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ /* Allocate a suitable I/O buffer for this request. */
+ int rc = IOBUFMgrAllocBuf(pThis->hIoBufMgr, &pIoReqCur->ReadWrite.IoBuf, pIoReqCur->ReadWrite.cbReq,
+ &pIoReqCur->ReadWrite.cbIoBuf);
+ if (rc == VINF_SUCCESS)
+ {
+ ASMAtomicDecU32(&pThis->cIoReqsWaiting);
+ RTListNodeRemove(&pIoReqCur->NdLstWait);
+
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReqCur->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReqCur, VERR_PDM_MEDIAEX_IOREQ_CANCELED, true /* fUpNotify */);
+ }
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+ rc = drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReqCur, true /* fUpNotify */);
+ }
+ else
+ {
+ Assert(rc == VERR_NO_MEMORY);
+ break;
+ }
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqsIoBufWait);
+ }
+ }
+}
+
+
+/**
+ * Returns whether the VM is in a running state.
+ *
+ * @returns Flag indicating whether the VM is currently in a running state.
+ * @param pThis VBox disk container instance data.
+ */
+DECLINLINE(bool) drvramdiskMediaExIoReqIsVmRunning(PDRVRAMDISK pThis)
+{
+ VMSTATE enmVmState = PDMDrvHlpVMState(pThis->pDrvIns);
+ if ( enmVmState == VMSTATE_RESUMING
+ || enmVmState == VMSTATE_RUNNING
+ || enmVmState == VMSTATE_RUNNING_LS
+ || enmVmState == VMSTATE_RESETTING
+ || enmVmState == VMSTATE_RESETTING_LS
+ || enmVmState == VMSTATE_SOFT_RESETTING
+ || enmVmState == VMSTATE_SOFT_RESETTING_LS
+ || enmVmState == VMSTATE_SUSPENDING
+ || enmVmState == VMSTATE_SUSPENDING_LS
+ || enmVmState == VMSTATE_SUSPENDING_EXT_LS)
+ return true;
+
+ return false;
+}
+
+/**
+ * @copydoc FNVDASYNCTRANSFERCOMPLETE
+ */
+static void drvramdiskMediaExIoReqComplete(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq,
+ int rcReq)
+{
+ /*
+ * For a read we need to sync the memory before continuing to process
+ * the request further.
+ */
+ if ( RT_SUCCESS(rcReq)
+ && pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ)
+ rcReq = drvramdiskMediaExIoReqBufSync(pThis, pIoReq, false /* fToIoBuf */);
+
+ /*
+ * When the request owner instructs us to handle recoverable errors like full disks
+ * do it. Mark the request as suspended, notify the owner and put the request on the
+ * redo list.
+ */
+ if ( RT_FAILURE(rcReq)
+ && (pIoReq->fFlags & PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR)
+ && drvramdiskMediaExIoReqIsRedoSetWarning(pThis, rcReq))
+ {
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_SUSPENDED, VDIOREQSTATE_ACTIVE);
+ if (fXchg)
+ {
+ /* Put on redo list and adjust active request counter. */
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListAppend(&pThis->LstIoReqRedo, &pIoReq->NdLstWait);
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+ ASMAtomicDecU32(&pThis->cIoReqsActive);
+ pThis->pDrvMediaExPort->pfnIoReqStateChanged(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ PDMMEDIAEXIOREQSTATE_SUSPENDED);
+ }
+ else
+ {
+ /* Request was canceled inbetween, so don't care and notify the owner about the completed request. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReq, rcReq, true /* fUpNotify */);
+ }
+ }
+ else
+ {
+ /* Adjust the remaining amount to transfer. */
+ size_t cbReqIo = RT_MIN(pIoReq->ReadWrite.cbReqLeft, pIoReq->ReadWrite.cbIoBuf);
+ pIoReq->ReadWrite.offStart += cbReqIo;
+ pIoReq->ReadWrite.cbReqLeft -= cbReqIo;
+
+ if ( RT_FAILURE(rcReq)
+ || !pIoReq->ReadWrite.cbReqLeft
+ || ( pIoReq->enmType != PDMMEDIAEXIOREQTYPE_READ
+ && pIoReq->enmType != PDMMEDIAEXIOREQTYPE_WRITE))
+ drvramdiskMediaExIoReqCompleteWorker(pThis, pIoReq, rcReq, true /* fUpNotify */);
+ else
+ drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReq, true /* fUpNotify */);
+ }
+}
+
+/**
+ * Worker for a flush request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The flush request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqFlushWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ /* Nothing to do for a ram disk. */
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker for a discard request.
+ *
+ * @returns VBox status code.
+ * @param pThis RAM disk container instance data.
+ * @param pIoReq The discard request.
+ */
+static DECLCALLBACK(int) drvramdiskIoReqDiscardWorker(PDRVRAMDISK pThis, PPDMMEDIAEXIOREQINT pIoReq)
+{
+ int rc = drvramdiskDiscardRecords(pThis, pIoReq->Discard.paRanges, pIoReq->Discard.cRanges);
+ drvramdiskMediaExIoReqComplete(pThis, pIoReq, rc);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnQueryFeatures}
+ */
+static DECLCALLBACK(int) drvramdiskQueryFeatures(PPDMIMEDIAEX pInterface, uint32_t *pfFeatures)
+{
+ RT_NOREF1(pInterface);
+ *pfFeatures = PDMIMEDIAEX_FEATURE_F_ASYNC | PDMIMEDIAEX_FEATURE_F_DISCARD;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnNotifySuspend}
+ */
+static DECLCALLBACK(void) drvramdiskNotifySuspend(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAllocSizeSet}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqAllocSizeSet(PPDMIMEDIAEX pInterface, size_t cbIoReqAlloc)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ if (RT_UNLIKELY(pThis->hIoReqCache != NIL_RTMEMCACHE))
+ return VERR_INVALID_STATE;
+
+ return RTMemCacheCreate(&pThis->hIoReqCache, sizeof(PDMMEDIAEXIOREQINT) + cbIoReqAlloc, 0, UINT32_MAX,
+ NULL, NULL, NULL, 0);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqAlloc}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqAlloc(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq, void **ppvIoReqAlloc,
+ PDMMEDIAEXIOREQID uIoReqId, uint32_t fFlags)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ AssertReturn(!(fFlags & ~PDMIMEDIAEX_F_VALID), VERR_INVALID_PARAMETER);
+
+ PPDMMEDIAEXIOREQINT pIoReq = (PPDMMEDIAEXIOREQINT)RTMemCacheAlloc(pThis->hIoReqCache);
+
+ if (RT_UNLIKELY(!pIoReq))
+ return VERR_NO_MEMORY;
+
+ pIoReq->uIoReqId = uIoReqId;
+ pIoReq->fFlags = fFlags;
+ pIoReq->pDisk = pThis;
+ pIoReq->enmState = VDIOREQSTATE_ALLOCATED;
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_INVALID;
+
+ int rc = drvramdiskMediaExIoReqInsert(pThis, pIoReq);
+ if (RT_SUCCESS(rc))
+ {
+ *phIoReq = pIoReq;
+ *ppvIoReqAlloc = &pIoReq->abAlloc[0];
+ }
+ else
+ RTMemCacheFree(pThis->hIoReqCache, pIoReq);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFree}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqFree(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ if ( pIoReq->enmState != VDIOREQSTATE_COMPLETED
+ && pIoReq->enmState != VDIOREQSTATE_ALLOCATED)
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ /* Remove from allocated list. */
+ int rc = drvramdiskMediaExIoReqRemove(pThis, pIoReq);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Free any associated I/O memory. */
+ drvramdiskMediaExIoReqBufFree(pThis, pIoReq);
+
+ /* For discard request discard the range array. */
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD
+ && pIoReq->Discard.paRanges)
+ {
+ RTMemFree(pIoReq->Discard.paRanges);
+ pIoReq->Discard.paRanges = NULL;
+ }
+
+ pIoReq->enmState = VDIOREQSTATE_FREE;
+ RTMemCacheFree(pThis->hIoReqCache, pIoReq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryResidual}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQueryResidual(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbResidual)
+{
+ RT_NOREF2(pInterface, hIoReq);
+
+ *pcbResidual = 0;
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQueryXferSize}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQueryXferSize(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, size_t *pcbXfer)
+{
+ RT_NOREF1(pInterface);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ if ( pIoReq->enmType == PDMMEDIAEXIOREQTYPE_READ
+ || pIoReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE)
+ *pcbXfer = pIoReq->ReadWrite.cbReq;
+ else
+ *pcbXfer = 0;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancelAll}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqCancelAll(PPDMIMEDIAEX pInterface)
+{
+ RT_NOREF1(pInterface);
+ return VINF_SUCCESS; /** @todo */
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqCancel}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqCancel(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQID uIoReqId)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ unsigned idxBin = drvramdiskMediaExIoReqIdHash(uIoReqId);
+
+ int rc = RTSemFastMutexRequest(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for I/O request with ID. */
+ PPDMMEDIAEXIOREQINT pIt;
+ rc = VERR_PDM_MEDIAEX_IOREQID_NOT_FOUND;
+
+ RTListForEach(&pThis->aIoReqAllocBins[idxBin].LstIoReqAlloc, pIt, PDMMEDIAEXIOREQINT, NdAllocatedList)
+ {
+ if (pIt->uIoReqId == uIoReqId)
+ {
+ bool fXchg = true;
+ VDIOREQSTATE enmStateOld = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIt->enmState);
+
+ /*
+ * We might have to try canceling the request multiple times if it transitioned from
+ * ALLOCATED to ACTIVE or to SUSPENDED between reading the state and trying to change it.
+ */
+ while ( ( enmStateOld == VDIOREQSTATE_ALLOCATED
+ || enmStateOld == VDIOREQSTATE_ACTIVE
+ || enmStateOld == VDIOREQSTATE_SUSPENDED)
+ && !fXchg)
+ {
+ fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIt->enmState, VDIOREQSTATE_CANCELED, enmStateOld);
+ if (!fXchg)
+ enmStateOld = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIt->enmState);
+ }
+
+ if (fXchg)
+ {
+ ASMAtomicDecU32(&pThis->cIoReqsActive);
+ rc = VINF_SUCCESS;
+ }
+ break;
+ }
+ }
+ RTSemFastMutexRelease(pThis->aIoReqAllocBins[idxBin].hMtxLstIoReqAlloc);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqRead}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqRead(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbRead)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_READ;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+ pIoReq->ReadWrite.offStart = off;
+ pIoReq->ReadWrite.cbReq = cbRead;
+ pIoReq->ReadWrite.cbReqLeft = cbRead;
+ /* Allocate a suitable I/O buffer for this request. */
+ int rc = drvramdiskMediaExIoReqBufAlloc(pThis, pIoReq, cbRead);
+ if (rc == VINF_SUCCESS)
+ {
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ rc = drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReq, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqWrite}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqWrite(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint64_t off, size_t cbWrite)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_WRITE;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+ pIoReq->ReadWrite.offStart = off;
+ pIoReq->ReadWrite.cbReq = cbWrite;
+ pIoReq->ReadWrite.cbReqLeft = cbWrite;
+ /* Allocate a suitable I/O buffer for this request. */
+ int rc = drvramdiskMediaExIoReqBufAlloc(pThis, pIoReq, cbWrite);
+ if (rc == VINF_SUCCESS)
+ {
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ rc = drvramdiskMediaExIoReqReadWriteProcess(pThis, pIoReq, false /* fUpNotify */);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqFlush}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqFlush(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_FLUSH;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+ return RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqFlushWorker, 2, pThis, pIoReq);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqDiscard}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqDiscard(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, unsigned cRangesMax)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+ VDIOREQSTATE enmState = (VDIOREQSTATE)ASMAtomicReadU32((volatile uint32_t *)&pIoReq->enmState);
+
+ if (RT_UNLIKELY(enmState == VDIOREQSTATE_CANCELED))
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+
+ if (RT_UNLIKELY(enmState != VDIOREQSTATE_ALLOCATED))
+ return VERR_PDM_MEDIAEX_IOREQ_INVALID_STATE;
+
+ /* Copy the ranges over now, this can be optimized in the future. */
+ pIoReq->Discard.paRanges = (PRTRANGE)RTMemAllocZ(cRangesMax * sizeof(RTRANGE));
+ if (RT_UNLIKELY(!pIoReq->Discard.paRanges))
+ return VERR_NO_MEMORY;
+
+ int rc = pThis->pDrvMediaExPort->pfnIoReqQueryDiscardRanges(pThis->pDrvMediaExPort, pIoReq, &pIoReq->abAlloc[0],
+ 0, cRangesMax, pIoReq->Discard.paRanges,
+ &pIoReq->Discard.cRanges);
+ if (RT_SUCCESS(rc))
+ {
+ pIoReq->enmType = PDMMEDIAEXIOREQTYPE_DISCARD;
+ pIoReq->tsSubmit = RTTimeMilliTS();
+
+ bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pIoReq->enmState, VDIOREQSTATE_ACTIVE, VDIOREQSTATE_ALLOCATED);
+ if (RT_UNLIKELY(!fXchg))
+ {
+ /* Must have been canceled inbetween. */
+ Assert(pIoReq->enmState == VDIOREQSTATE_CANCELED);
+ return VERR_PDM_MEDIAEX_IOREQ_CANCELED;
+ }
+
+ ASMAtomicIncU32(&pThis->cIoReqsActive);
+
+ rc = RTReqQueueCallEx(pThis->hReqQ, NULL, 0, RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvramdiskIoReqDiscardWorker, 2, pThis, pIoReq);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetActiveCount}
+ */
+static DECLCALLBACK(uint32_t) drvramdiskIoReqGetActiveCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ return ASMAtomicReadU32(&pThis->cIoReqsActive);
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqGetSuspendedCount}
+ */
+static DECLCALLBACK(uint32_t) drvramdiskIoReqGetSuspendedCount(PPDMIMEDIAEX pInterface)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), 0);
+
+ uint32_t cIoReqSuspended = 0;
+ PPDMMEDIAEXIOREQINT pIoReq;
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ RTListForEach(&pThis->LstIoReqRedo, pIoReq, PDMMEDIAEXIOREQINT, NdLstWait)
+ {
+ cIoReqSuspended++;
+ }
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return cIoReqSuspended;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedStart}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQuerySuspendedStart(PPDMIMEDIAEX pInterface, PPDMMEDIAEXIOREQ phIoReq,
+ void **ppvIoReqAlloc)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertReturn(!RTListIsEmpty(&pThis->LstIoReqRedo), VERR_NOT_FOUND);
+
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ PPDMMEDIAEXIOREQINT pIoReq = RTListGetFirst(&pThis->LstIoReqRedo, PDMMEDIAEXIOREQINT, NdLstWait);
+ *phIoReq = pIoReq;
+ *ppvIoReqAlloc = &pIoReq->abAlloc[0];
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqQuerySuspendedNext}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqQuerySuspendedNext(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq,
+ PPDMMEDIAEXIOREQ phIoReqNext, void **ppvIoReqAllocNext)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(!RTListNodeIsLast(&pThis->LstIoReqRedo, &pIoReq->NdLstWait), VERR_NOT_FOUND);
+
+ RTCritSectEnter(&pThis->CritSectIoReqRedo);
+ PPDMMEDIAEXIOREQINT pIoReqNext = RTListNodeGetNext(&pIoReq->NdLstWait, PDMMEDIAEXIOREQINT, NdLstWait);
+ *phIoReqNext = pIoReqNext;
+ *ppvIoReqAllocNext = &pIoReqNext->abAlloc[0];
+ RTCritSectLeave(&pThis->CritSectIoReqRedo);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedSave}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqSuspendedSave(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ RT_NOREF1(pSSM);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(pIoReq->enmState == VDIOREQSTATE_SUSPENDED, VERR_INVALID_STATE);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{PDMIMEDIAEX,pfnIoReqSuspendedLoad}
+ */
+static DECLCALLBACK(int) drvramdiskIoReqSuspendedLoad(PPDMIMEDIAEX pInterface, PSSMHANDLE pSSM, PDMMEDIAEXIOREQ hIoReq)
+{
+ PDRVRAMDISK pThis = RT_FROM_MEMBER(pInterface, DRVRAMDISK, IMediaEx);
+ PPDMMEDIAEXIOREQINT pIoReq = hIoReq;
+
+ RT_NOREF1(pSSM);
+
+ AssertReturn(!drvramdiskMediaExIoReqIsVmRunning(pThis), VERR_INVALID_STATE);
+ AssertPtrReturn(pIoReq, VERR_INVALID_HANDLE);
+ AssertReturn(pIoReq->enmState == VDIOREQSTATE_ALLOCATED, VERR_INVALID_STATE);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(int) drvramdiskIoReqWorker(RTTHREAD hThrdSelf, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ PDRVRAMDISK pThis = (PDRVRAMDISK)pvUser;
+
+ RT_NOREF1(hThrdSelf);
+
+ do
+ {
+ rc = RTReqQueueProcess(pThis->hReqQ, RT_INDEFINITE_WAIT);
+ } while (RT_SUCCESS(rc));
+
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvramdiskQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIA, &pThis->IMedia);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEX, &pThis->IMediaEx);
+
+ return NULL;
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+static DECLCALLBACK(int) drvramdiskTreeDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
+{
+ PDRVDISKSEGMENT pSeg = (PDRVDISKSEGMENT)pNode;
+
+ RT_NOREF1(pvUser);
+
+ RTMemFree(pSeg->pbSeg);
+ RTMemFree(pSeg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvramdiskDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+
+ if (pThis->pTreeSegments)
+ {
+ RTAvlrFileOffsetDestroy(pThis->pTreeSegments, drvramdiskTreeDestroy, NULL);
+ RTMemFree(pThis->pTreeSegments);
+ }
+ RTReqQueueDestroy(pThis->hReqQ);
+}
+
+/**
+ * Construct a disk integrity driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvramdiskConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF1(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVRAMDISK pThis = PDMINS_2_DATA(pDrvIns, PDRVRAMDISK);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("drvdiskintConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Initialize most of the data members.
+ */
+ pThis->pDrvIns = pDrvIns;
+
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvramdiskQueryInterface;
+
+ /* IMedia */
+ pThis->IMedia.pfnRead = drvramdiskRead;
+ pThis->IMedia.pfnWrite = drvramdiskWrite;
+ pThis->IMedia.pfnFlush = drvramdiskFlush;
+ pThis->IMedia.pfnGetSize = drvramdiskGetSize;
+ pThis->IMedia.pfnBiosIsVisible = drvramdiskBiosIsVisible;
+ pThis->IMedia.pfnGetType = drvramdiskGetType;
+ pThis->IMedia.pfnIsReadOnly = drvramdiskIsReadOnly;
+ pThis->IMedia.pfnBiosGetPCHSGeometry = drvramdiskBiosGetPCHSGeometry;
+ pThis->IMedia.pfnBiosSetPCHSGeometry = drvramdiskBiosSetPCHSGeometry;
+ pThis->IMedia.pfnBiosGetLCHSGeometry = drvramdiskBiosGetLCHSGeometry;
+ pThis->IMedia.pfnBiosSetLCHSGeometry = drvramdiskBiosSetLCHSGeometry;
+ pThis->IMedia.pfnGetUuid = drvramdiskGetUuid;
+ pThis->IMedia.pfnGetSectorSize = drvramdiskGetSectorSize;
+ pThis->IMedia.pfnReadPcBios = drvramdiskReadPcBios;
+ pThis->IMedia.pfnDiscard = drvramdiskDiscard;
+ pThis->IMedia.pfnIsNonRotational = drvramdiskIsNonRotational;
+
+ /* IMediaEx */
+ pThis->IMediaEx.pfnQueryFeatures = drvramdiskQueryFeatures;
+ pThis->IMediaEx.pfnNotifySuspend = drvramdiskNotifySuspend;
+ pThis->IMediaEx.pfnIoReqAllocSizeSet = drvramdiskIoReqAllocSizeSet;
+ pThis->IMediaEx.pfnIoReqAlloc = drvramdiskIoReqAlloc;
+ pThis->IMediaEx.pfnIoReqFree = drvramdiskIoReqFree;
+ pThis->IMediaEx.pfnIoReqQueryResidual = drvramdiskIoReqQueryResidual;
+ pThis->IMediaEx.pfnIoReqQueryXferSize = drvramdiskIoReqQueryXferSize;
+ pThis->IMediaEx.pfnIoReqCancelAll = drvramdiskIoReqCancelAll;
+ pThis->IMediaEx.pfnIoReqCancel = drvramdiskIoReqCancel;
+ pThis->IMediaEx.pfnIoReqRead = drvramdiskIoReqRead;
+ pThis->IMediaEx.pfnIoReqWrite = drvramdiskIoReqWrite;
+ pThis->IMediaEx.pfnIoReqFlush = drvramdiskIoReqFlush;
+ pThis->IMediaEx.pfnIoReqDiscard = drvramdiskIoReqDiscard;
+ pThis->IMediaEx.pfnIoReqGetActiveCount = drvramdiskIoReqGetActiveCount;
+ pThis->IMediaEx.pfnIoReqGetSuspendedCount = drvramdiskIoReqGetSuspendedCount;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedStart = drvramdiskIoReqQuerySuspendedStart;
+ pThis->IMediaEx.pfnIoReqQuerySuspendedNext = drvramdiskIoReqQuerySuspendedNext;
+ pThis->IMediaEx.pfnIoReqSuspendedSave = drvramdiskIoReqSuspendedSave;
+ pThis->IMediaEx.pfnIoReqSuspendedLoad = drvramdiskIoReqSuspendedLoad;
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Size"
+ "|PreAlloc"
+ "|IoBufMax"
+ "|SectorSize"
+ "|NonRotational",
+ "");
+
+ int rc = pHlp->pfnCFGMQueryU64(pCfg, "Size", &pThis->cbDisk);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying the media size"));
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "PreAlloc", &pThis->fPreallocRamDisk, false);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying \"PreAlloc\""));
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "NonRotational", &pThis->fNonRotational, true);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc,
+ N_("RamDisk: Error querying \"NonRotational\""));
+
+ uint32_t cbIoBufMax;
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "IoBufMax", &cbIoBufMax, 5 * _1M);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IoBufMax\" from the config"));
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "SectorSize", &pThis->cbSector, 512);
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"SectorSize\" from the config"));
+
+ /* Query the media port interface above us. */
+ pThis->pDrvMediaPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAPORT);
+ if (!pThis->pDrvMediaPort)
+ return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("No media port interface above"));
+
+ /* Try to attach extended media port interface above.*/
+ pThis->pDrvMediaExPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMEDIAEXPORT);
+ if (pThis->pDrvMediaExPort)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIoReqAllocBins); i++)
+ {
+ rc = RTSemFastMutexCreate(&pThis->aIoReqAllocBins[i].hMtxLstIoReqAlloc);
+ if (RT_FAILURE(rc))
+ break;
+ RTListInit(&pThis->aIoReqAllocBins[i].LstIoReqAlloc);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->CritSectIoReqsIoBufWait);
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->CritSectIoReqRedo);
+
+ if (RT_FAILURE(rc))
+ return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Creating Mutex failed"));
+
+ RTListInit(&pThis->LstIoReqIoBufWait);
+ RTListInit(&pThis->LstIoReqRedo);
+ }
+
+ /* Create the AVL tree. */
+ pThis->pTreeSegments = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
+ if (!pThis->pTreeSegments)
+ rc = VERR_NO_MEMORY;
+
+ if (pThis->pDrvMediaExPort)
+ {
+ rc = RTReqQueueCreate(&pThis->hReqQ);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin up the worker thread. */
+ rc = RTThreadCreate(&pThis->hThrdWrk, drvramdiskIoReqWorker, pThis, 0,
+ RTTHREADTYPE_IO, 0, "RAMDSK");
+ }
+ }
+
+ if (pThis->pDrvMediaExPort)
+ rc = IOBUFMgrCreate(&pThis->hIoBufMgr, cbIoBufMax, IOBUFMGR_F_DEFAULT);
+
+ /* Read in all data before the start if requested. */
+ if ( RT_SUCCESS(rc)
+ && pThis->fPreallocRamDisk)
+ {
+ LogRel(("RamDisk: Preallocating RAM disk...\n"));
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Block driver registration record.
+ */
+const PDMDRVREG g_DrvRamDisk =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "RamDisk",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "RAM disk driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_BLOCK,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVRAMDISK),
+ /* pfnConstruct */
+ drvramdiskConstruct,
+ /* pfnDestruct */
+ drvramdiskDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+